INSTINCT Code Coverage Report


Directory: src/
File: Nodes/DataProvider/IMU/Sensors/VectorNavSensor.cpp
Date: 2025-06-02 15:19:59
Exec Total Coverage
Lines: 29 3819 0.8%
Functions: 5 202 2.5%
Branches: 40 8810 0.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 "VectorNavSensor.hpp"
10
11 #include "util/Logger.hpp"
12 #include "vn/searcher.h"
13 #include "vn/util.h"
14 #include "util/Vendor/VectorNav/VectorNavTypes.hpp"
15
16 #include "internal/gui/widgets/HelpMarker.hpp"
17
18 #include "internal/NodeManager.hpp"
19 #include <vn/types.h>
20 namespace nm = NAV::NodeManager;
21 #include "internal/FlowManager.hpp"
22
23 #include <imgui_internal.h>
24
25 #include "NodeData/General/StringObs.hpp"
26
27 #include "util/Time/TimeBase.hpp"
28 #include "Navigation/Transformations/CoordinateFrames.hpp"
29 #include "Navigation/Transformations/Units.hpp"
30
31 #include <map>
32
33 // to_json / from_json
34 namespace vn
35 {
36 namespace math
37 {
38 static void to_json(json& j, const vn::math::vec3f& vec) // NOLINT(misc-use-anonymous-namespace)
39 {
40 j = vec.c;
41 }
42 static void from_json(const json& j, vn::math::vec3f& vec) // NOLINT(misc-use-anonymous-namespace)
43 {
44 j.get_to(vec.c);
45 }
46 static void to_json(json& j, const vn::math::vec3d& vec) // NOLINT(misc-use-anonymous-namespace)
47 {
48 j = vec.c;
49 }
50 static void from_json(const json& j, vn::math::vec3d& vec) // NOLINT(misc-use-anonymous-namespace)
51 {
52 j.get_to(vec.c);
53 }
54 static void to_json(json& j, const vn::math::mat3f& mat) // NOLINT(misc-use-anonymous-namespace)
55 {
56 j = mat.e;
57 }
58 static void from_json(const json& j, vn::math::mat3f& mat) // NOLINT(misc-use-anonymous-namespace)
59 {
60 j.get_to(mat.e);
61 }
62 } // namespace math
63
64 namespace sensors
65 {
66 // ###########################################################################################################
67 // SYSTEM MODULE
68 // ###########################################################################################################
69
70 static void to_json(json& j, const SynchronizationControlRegister& synchronizationControlRegister) // NOLINT(misc-use-anonymous-namespace)
71 {
72 j = json{
73 { "syncInMode", synchronizationControlRegister.syncInMode },
74 { "syncInEdge", synchronizationControlRegister.syncInEdge },
75 { "syncInSkipFactor", synchronizationControlRegister.syncInSkipFactor },
76 { "syncOutMode", synchronizationControlRegister.syncOutMode },
77 { "syncOutPolarity", synchronizationControlRegister.syncOutPolarity },
78 { "syncOutSkipFactor", synchronizationControlRegister.syncOutSkipFactor },
79 { "syncOutPulseWidth", synchronizationControlRegister.syncOutPulseWidth },
80 };
81 }
82 static void from_json(const json& j, SynchronizationControlRegister& synchronizationControlRegister) // NOLINT(misc-use-anonymous-namespace)
83 {
84 if (j.contains("syncInMode"))
85 {
86 j.at("syncInMode").get_to(synchronizationControlRegister.syncInMode);
87 }
88 if (j.contains("syncInEdge"))
89 {
90 j.at("syncInEdge").get_to(synchronizationControlRegister.syncInEdge);
91 }
92 if (j.contains("syncInSkipFactor"))
93 {
94 j.at("syncInSkipFactor").get_to(synchronizationControlRegister.syncInSkipFactor);
95 }
96 if (j.contains("syncOutMode"))
97 {
98 j.at("syncOutMode").get_to(synchronizationControlRegister.syncOutMode);
99 }
100 if (j.contains("syncOutPolarity"))
101 {
102 j.at("syncOutPolarity").get_to(synchronizationControlRegister.syncOutPolarity);
103 }
104 if (j.contains("syncOutSkipFactor"))
105 {
106 j.at("syncOutSkipFactor").get_to(synchronizationControlRegister.syncOutSkipFactor);
107 }
108 if (j.contains("syncOutPulseWidth"))
109 {
110 j.at("syncOutPulseWidth").get_to(synchronizationControlRegister.syncOutPulseWidth);
111 }
112 }
113
114 static void to_json(json& j, const CommunicationProtocolControlRegister& communicationProtocolControlRegister) // NOLINT(misc-use-anonymous-namespace)
115 {
116 j = json{
117 { "serialCount", communicationProtocolControlRegister.serialCount },
118 { "serialStatus", communicationProtocolControlRegister.serialStatus },
119 { "spiCount", communicationProtocolControlRegister.spiCount },
120 { "spiStatus", communicationProtocolControlRegister.spiStatus },
121 { "serialChecksum", communicationProtocolControlRegister.serialChecksum },
122 { "spiChecksum", communicationProtocolControlRegister.spiChecksum },
123 { "errorMode", communicationProtocolControlRegister.errorMode },
124 };
125 }
126 static void from_json(const json& j, CommunicationProtocolControlRegister& communicationProtocolControlRegister) // NOLINT(misc-use-anonymous-namespace)
127 {
128 if (j.contains("serialCount"))
129 {
130 j.at("serialCount").get_to(communicationProtocolControlRegister.serialCount);
131 }
132 if (j.contains("serialStatus"))
133 {
134 j.at("serialStatus").get_to(communicationProtocolControlRegister.serialStatus);
135 }
136 if (j.contains("spiCount"))
137 {
138 j.at("spiCount").get_to(communicationProtocolControlRegister.spiCount);
139 }
140 if (j.contains("spiStatus"))
141 {
142 j.at("spiStatus").get_to(communicationProtocolControlRegister.spiStatus);
143 }
144 if (j.contains("serialChecksum"))
145 {
146 j.at("serialChecksum").get_to(communicationProtocolControlRegister.serialChecksum);
147 }
148 if (j.contains("spiChecksum"))
149 {
150 j.at("spiChecksum").get_to(communicationProtocolControlRegister.spiChecksum);
151 }
152 if (j.contains("errorMode"))
153 {
154 j.at("errorMode").get_to(communicationProtocolControlRegister.errorMode);
155 }
156 }
157
158 static void to_json(json& j, const BinaryOutputRegister& binaryOutputRegister) // NOLINT(misc-use-anonymous-namespace)
159 {
160 j = json{
161 { "asyncMode", binaryOutputRegister.asyncMode },
162 { "rateDivisor", binaryOutputRegister.rateDivisor },
163 { "commonField", binaryOutputRegister.commonField },
164 { "timeField", binaryOutputRegister.timeField },
165 { "imuField", binaryOutputRegister.imuField },
166 { "gpsField", binaryOutputRegister.gpsField },
167 { "attitudeField", binaryOutputRegister.attitudeField },
168 { "insField", binaryOutputRegister.insField },
169 { "gps2Field", binaryOutputRegister.gps2Field },
170 };
171 }
172 static void from_json(const json& j, BinaryOutputRegister& binaryOutputRegister) // NOLINT(misc-use-anonymous-namespace)
173 {
174 if (j.contains("asyncMode"))
175 {
176 j.at("asyncMode").get_to(binaryOutputRegister.asyncMode);
177 }
178 if (j.contains("rateDivisor"))
179 {
180 j.at("rateDivisor").get_to(binaryOutputRegister.rateDivisor);
181 }
182 if (j.contains("commonField"))
183 {
184 j.at("commonField").get_to(binaryOutputRegister.commonField);
185 }
186 if (j.contains("timeField"))
187 {
188 j.at("timeField").get_to(binaryOutputRegister.timeField);
189 }
190 if (j.contains("imuField"))
191 {
192 j.at("imuField").get_to(binaryOutputRegister.imuField);
193 }
194 if (j.contains("gpsField"))
195 {
196 j.at("gpsField").get_to(binaryOutputRegister.gpsField);
197 }
198 if (j.contains("attitudeField"))
199 {
200 j.at("attitudeField").get_to(binaryOutputRegister.attitudeField);
201 }
202 if (j.contains("insField"))
203 {
204 j.at("insField").get_to(binaryOutputRegister.insField);
205 }
206 if (j.contains("gps2Field"))
207 {
208 j.at("gps2Field").get_to(binaryOutputRegister.gps2Field);
209 }
210 }
211
212 // ###########################################################################################################
213 // IMU SUBSYSTEM
214 // ###########################################################################################################
215
216 static void to_json(json& j, const ImuFilteringConfigurationRegister& imuFilteringConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
217 {
218 j = json{
219 { "magWindowSize", imuFilteringConfigurationRegister.magWindowSize },
220 { "accelWindowSize", imuFilteringConfigurationRegister.accelWindowSize },
221 { "gyroWindowSize", imuFilteringConfigurationRegister.gyroWindowSize },
222 { "tempWindowSize", imuFilteringConfigurationRegister.tempWindowSize },
223 { "presWindowSize", imuFilteringConfigurationRegister.presWindowSize },
224 { "magFilterMode", imuFilteringConfigurationRegister.magFilterMode },
225 { "accelFilterMode", imuFilteringConfigurationRegister.accelFilterMode },
226 { "gyroFilterMode", imuFilteringConfigurationRegister.gyroFilterMode },
227 { "tempFilterMode", imuFilteringConfigurationRegister.tempFilterMode },
228 { "presFilterMode", imuFilteringConfigurationRegister.presFilterMode },
229 };
230 }
231 static void from_json(const json& j, ImuFilteringConfigurationRegister& imuFilteringConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
232 {
233 if (j.contains("magWindowSize"))
234 {
235 j.at("magWindowSize").get_to(imuFilteringConfigurationRegister.magWindowSize);
236 }
237 if (j.contains("accelWindowSize"))
238 {
239 j.at("accelWindowSize").get_to(imuFilteringConfigurationRegister.accelWindowSize);
240 }
241 if (j.contains("gyroWindowSize"))
242 {
243 j.at("gyroWindowSize").get_to(imuFilteringConfigurationRegister.gyroWindowSize);
244 }
245 if (j.contains("tempWindowSize"))
246 {
247 j.at("tempWindowSize").get_to(imuFilteringConfigurationRegister.tempWindowSize);
248 }
249 if (j.contains("presWindowSize"))
250 {
251 j.at("presWindowSize").get_to(imuFilteringConfigurationRegister.presWindowSize);
252 }
253 if (j.contains("magFilterMode"))
254 {
255 j.at("magFilterMode").get_to(imuFilteringConfigurationRegister.magFilterMode);
256 }
257 if (j.contains("accelFilterMode"))
258 {
259 j.at("accelFilterMode").get_to(imuFilteringConfigurationRegister.accelFilterMode);
260 }
261 if (j.contains("gyroFilterMode"))
262 {
263 j.at("gyroFilterMode").get_to(imuFilteringConfigurationRegister.gyroFilterMode);
264 }
265 if (j.contains("tempFilterMode"))
266 {
267 j.at("tempFilterMode").get_to(imuFilteringConfigurationRegister.tempFilterMode);
268 }
269 if (j.contains("presFilterMode"))
270 {
271 j.at("presFilterMode").get_to(imuFilteringConfigurationRegister.presFilterMode);
272 }
273 }
274
275 static void to_json(json& j, const DeltaThetaAndDeltaVelocityConfigurationRegister& deltaThetaAndDeltaVelocityConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
276 {
277 j = json{
278 { "integrationFrame", deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame },
279 { "gyroCompensation", deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation },
280 { "accelCompensation", deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation },
281 { "earthRateCorrection", deltaThetaAndDeltaVelocityConfigurationRegister.earthRateCorrection },
282 };
283 }
284 static void from_json(const json& j, DeltaThetaAndDeltaVelocityConfigurationRegister& deltaThetaAndDeltaVelocityConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
285 {
286 if (j.contains("integrationFrame"))
287 {
288 j.at("integrationFrame").get_to(deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame);
289 }
290 if (j.contains("gyroCompensation"))
291 {
292 j.at("gyroCompensation").get_to(deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation);
293 }
294 if (j.contains("accelCompensation"))
295 {
296 j.at("accelCompensation").get_to(deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation);
297 }
298 if (j.contains("earthRateCorrection"))
299 {
300 j.at("earthRateCorrection").get_to(deltaThetaAndDeltaVelocityConfigurationRegister.earthRateCorrection);
301 }
302 }
303
304 // ###########################################################################################################
305 // GNSS SUBSYSTEM
306 // ###########################################################################################################
307
308 static void to_json(json& j, const GpsConfigurationRegister& gpsConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
309 {
310 j = json{
311 { "mode", gpsConfigurationRegister.mode },
312 { "ppsSource", gpsConfigurationRegister.ppsSource },
313 { "rate", gpsConfigurationRegister.rate },
314 { "antPow", gpsConfigurationRegister.antPow },
315 };
316 }
317 static void from_json(const json& j, GpsConfigurationRegister& gpsConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
318 {
319 if (j.contains("mode"))
320 {
321 j.at("mode").get_to(gpsConfigurationRegister.mode);
322 }
323 if (j.contains("ppsSource"))
324 {
325 j.at("ppsSource").get_to(gpsConfigurationRegister.ppsSource);
326 }
327 if (j.contains("rate"))
328 {
329 j.at("rate").get_to(gpsConfigurationRegister.rate);
330 }
331 if (j.contains("antPow"))
332 {
333 j.at("antPow").get_to(gpsConfigurationRegister.antPow);
334 }
335 }
336
337 static void to_json(json& j, const GpsCompassBaselineRegister& gpsCompassBaselineRegister) // NOLINT(misc-use-anonymous-namespace)
338 {
339 j = json{
340 { "position", gpsCompassBaselineRegister.position },
341 { "uncertainty", gpsCompassBaselineRegister.uncertainty },
342 };
343 }
344 static void from_json(const json& j, GpsCompassBaselineRegister& gpsCompassBaselineRegister) // NOLINT(misc-use-anonymous-namespace)
345 {
346 if (j.contains("position"))
347 {
348 j.at("position").get_to(gpsCompassBaselineRegister.position);
349 }
350 if (j.contains("uncertainty"))
351 {
352 j.at("uncertainty").get_to(gpsCompassBaselineRegister.uncertainty);
353 }
354 }
355
356 // ###########################################################################################################
357 // ATTITUDE SUBSYSTEM
358 // ###########################################################################################################
359
360 static void to_json(json& j, const VpeBasicControlRegister& vpeBasicControlRegister) // NOLINT(misc-use-anonymous-namespace)
361 {
362 j = json{
363 { "enable", vpeBasicControlRegister.enable },
364 { "headingMode", vpeBasicControlRegister.headingMode },
365 { "filteringMode", vpeBasicControlRegister.filteringMode },
366 { "tuningMode", vpeBasicControlRegister.tuningMode },
367 };
368 }
369 static void from_json(const json& j, VpeBasicControlRegister& vpeBasicControlRegister) // NOLINT(misc-use-anonymous-namespace)
370 {
371 if (j.contains("enable"))
372 {
373 j.at("enable").get_to(vpeBasicControlRegister.enable);
374 }
375 if (j.contains("headingMode"))
376 {
377 j.at("headingMode").get_to(vpeBasicControlRegister.headingMode);
378 }
379 if (j.contains("filteringMode"))
380 {
381 j.at("filteringMode").get_to(vpeBasicControlRegister.filteringMode);
382 }
383 if (j.contains("tuningMode"))
384 {
385 j.at("tuningMode").get_to(vpeBasicControlRegister.tuningMode);
386 }
387 }
388
389 static void to_json(json& j, const VpeMagnetometerBasicTuningRegister& vpeMagnetometerBasicTuningRegister) // NOLINT(misc-use-anonymous-namespace)
390 {
391 j = json{
392 { "baseTuning", vpeMagnetometerBasicTuningRegister.baseTuning },
393 { "adaptiveTuning", vpeMagnetometerBasicTuningRegister.adaptiveTuning },
394 { "adaptiveFiltering", vpeMagnetometerBasicTuningRegister.adaptiveFiltering },
395 };
396 }
397 static void from_json(const json& j, VpeMagnetometerBasicTuningRegister& vpeMagnetometerBasicTuningRegister) // NOLINT(misc-use-anonymous-namespace)
398 {
399 if (j.contains("baseTuning"))
400 {
401 j.at("baseTuning").get_to(vpeMagnetometerBasicTuningRegister.baseTuning);
402 }
403 if (j.contains("adaptiveTuning"))
404 {
405 j.at("adaptiveTuning").get_to(vpeMagnetometerBasicTuningRegister.adaptiveTuning);
406 }
407 if (j.contains("adaptiveFiltering"))
408 {
409 j.at("adaptiveFiltering").get_to(vpeMagnetometerBasicTuningRegister.adaptiveFiltering);
410 }
411 }
412
413 static void to_json(json& j, const VpeAccelerometerBasicTuningRegister& vpeAccelerometerBasicTuningRegister) // NOLINT(misc-use-anonymous-namespace)
414 {
415 j = json{
416 { "baseTuning", vpeAccelerometerBasicTuningRegister.baseTuning },
417 { "adaptiveTuning", vpeAccelerometerBasicTuningRegister.adaptiveTuning },
418 { "adaptiveFiltering", vpeAccelerometerBasicTuningRegister.adaptiveFiltering },
419 };
420 }
421 static void from_json(const json& j, VpeAccelerometerBasicTuningRegister& vpeAccelerometerBasicTuningRegister) // NOLINT(misc-use-anonymous-namespace)
422 {
423 if (j.contains("baseTuning"))
424 {
425 j.at("baseTuning").get_to(vpeAccelerometerBasicTuningRegister.baseTuning);
426 }
427 if (j.contains("adaptiveTuning"))
428 {
429 j.at("adaptiveTuning").get_to(vpeAccelerometerBasicTuningRegister.adaptiveTuning);
430 }
431 if (j.contains("adaptiveFiltering"))
432 {
433 j.at("adaptiveFiltering").get_to(vpeAccelerometerBasicTuningRegister.adaptiveFiltering);
434 }
435 }
436
437 static void to_json(json& j, const VpeGyroBasicTuningRegister& vpeGyroBasicTuningRegister) // NOLINT(misc-use-anonymous-namespace)
438 {
439 j = json{
440 { "angularWalkVariance", vpeGyroBasicTuningRegister.angularWalkVariance },
441 { "baseTuning", vpeGyroBasicTuningRegister.baseTuning },
442 { "adaptiveTuning", vpeGyroBasicTuningRegister.adaptiveTuning },
443 };
444 }
445 static void from_json(const json& j, VpeGyroBasicTuningRegister& vpeGyroBasicTuningRegister) // NOLINT(misc-use-anonymous-namespace)
446 {
447 if (j.contains("angularWalkVariance"))
448 {
449 j.at("angularWalkVariance").get_to(vpeGyroBasicTuningRegister.angularWalkVariance);
450 }
451 if (j.contains("baseTuning"))
452 {
453 j.at("baseTuning").get_to(vpeGyroBasicTuningRegister.baseTuning);
454 }
455 if (j.contains("adaptiveTuning"))
456 {
457 j.at("adaptiveTuning").get_to(vpeGyroBasicTuningRegister.adaptiveTuning);
458 }
459 }
460
461 // ###########################################################################################################
462 // INS SUBSYSTEM
463 // ###########################################################################################################
464
465 static void to_json(json& j, const InsBasicConfigurationRegisterVn300& insBasicConfigurationRegisterVn300) // NOLINT(misc-use-anonymous-namespace)
466 {
467 j = json{
468 { "scenario", insBasicConfigurationRegisterVn300.scenario },
469 { "ahrsAiding", insBasicConfigurationRegisterVn300.ahrsAiding },
470 { "estBaseline", insBasicConfigurationRegisterVn300.estBaseline },
471 };
472 }
473 static void from_json(const json& j, InsBasicConfigurationRegisterVn300& insBasicConfigurationRegisterVn300) // NOLINT(misc-use-anonymous-namespace)
474 {
475 if (j.contains("scenario"))
476 {
477 j.at("scenario").get_to(insBasicConfigurationRegisterVn300.scenario);
478 }
479 if (j.contains("ahrsAiding"))
480 {
481 j.at("ahrsAiding").get_to(insBasicConfigurationRegisterVn300.ahrsAiding);
482 }
483 if (j.contains("estBaseline"))
484 {
485 j.at("estBaseline").get_to(insBasicConfigurationRegisterVn300.estBaseline);
486 }
487 }
488
489 static void to_json(json& j, const StartupFilterBiasEstimateRegister& startupFilterBiasEstimateRegister) // NOLINT(misc-use-anonymous-namespace)
490 {
491 j = json{
492 { "gyroBias", startupFilterBiasEstimateRegister.gyroBias },
493 { "accelBias", startupFilterBiasEstimateRegister.accelBias },
494 { "pressureBias", startupFilterBiasEstimateRegister.pressureBias },
495 };
496 }
497 static void from_json(const json& j, StartupFilterBiasEstimateRegister& startupFilterBiasEstimateRegister) // NOLINT(misc-use-anonymous-namespace)
498 {
499 if (j.contains("gyroBias"))
500 {
501 j.at("gyroBias").get_to(startupFilterBiasEstimateRegister.gyroBias);
502 }
503 if (j.contains("accelBias"))
504 {
505 j.at("accelBias").get_to(startupFilterBiasEstimateRegister.accelBias);
506 }
507 if (j.contains("pressureBias"))
508 {
509 j.at("pressureBias").get_to(startupFilterBiasEstimateRegister.pressureBias);
510 }
511 }
512
513 // ###########################################################################################################
514 // HARD/SOFT IRON ESTIMATOR SUBSYSTEM
515 // ###########################################################################################################
516
517 static void to_json(json& j, const MagnetometerCalibrationControlRegister& magnetometerCalibrationControlRegister) // NOLINT(misc-use-anonymous-namespace)
518 {
519 j = json{
520 { "hsiMode", magnetometerCalibrationControlRegister.hsiMode },
521 { "hsiOutput", magnetometerCalibrationControlRegister.hsiOutput },
522 { "convergeRate", magnetometerCalibrationControlRegister.convergeRate },
523 };
524 }
525 static void from_json(const json& j, MagnetometerCalibrationControlRegister& magnetometerCalibrationControlRegister) // NOLINT(misc-use-anonymous-namespace)
526 {
527 if (j.contains("hsiMode"))
528 {
529 j.at("hsiMode").get_to(magnetometerCalibrationControlRegister.hsiMode);
530 }
531 if (j.contains("hsiOutput"))
532 {
533 j.at("hsiOutput").get_to(magnetometerCalibrationControlRegister.hsiOutput);
534 }
535 if (j.contains("convergeRate"))
536 {
537 j.at("convergeRate").get_to(magnetometerCalibrationControlRegister.convergeRate);
538 }
539 }
540
541 // ###########################################################################################################
542 // WORLD MAGNETIC & GRAVITY MODULE
543 // ###########################################################################################################
544
545 static void to_json(json& j, const MagneticAndGravityReferenceVectorsRegister& magneticAndGravityReferenceVectorsRegister) // NOLINT(misc-use-anonymous-namespace)
546 {
547 j = json{
548 { "magRef", magneticAndGravityReferenceVectorsRegister.magRef },
549 { "accRef", magneticAndGravityReferenceVectorsRegister.accRef },
550 };
551 }
552 static void from_json(const json& j, MagneticAndGravityReferenceVectorsRegister& magneticAndGravityReferenceVectorsRegister) // NOLINT(misc-use-anonymous-namespace)
553 {
554 if (j.contains("magRef"))
555 {
556 j.at("magRef").get_to(magneticAndGravityReferenceVectorsRegister.magRef);
557 }
558 if (j.contains("accRef"))
559 {
560 j.at("accRef").get_to(magneticAndGravityReferenceVectorsRegister.accRef);
561 }
562 }
563
564 static void to_json(json& j, const ReferenceVectorConfigurationRegister& referenceVectorConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
565 {
566 j = json{
567 { "useMagModel", referenceVectorConfigurationRegister.useMagModel },
568 { "useGravityModel", referenceVectorConfigurationRegister.useGravityModel },
569 { "recalcThreshold", referenceVectorConfigurationRegister.recalcThreshold },
570 { "year", referenceVectorConfigurationRegister.year },
571 { "position", referenceVectorConfigurationRegister.position },
572 };
573 }
574 static void from_json(const json& j, ReferenceVectorConfigurationRegister& referenceVectorConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
575 {
576 if (j.contains("useMagModel"))
577 {
578 j.at("useMagModel").get_to(referenceVectorConfigurationRegister.useMagModel);
579 }
580 if (j.contains("useGravityModel"))
581 {
582 j.at("useGravityModel").get_to(referenceVectorConfigurationRegister.useGravityModel);
583 }
584 if (j.contains("recalcThreshold"))
585 {
586 j.at("recalcThreshold").get_to(referenceVectorConfigurationRegister.recalcThreshold);
587 }
588 if (j.contains("year"))
589 {
590 j.at("year").get_to(referenceVectorConfigurationRegister.year);
591 }
592 if (j.contains("position"))
593 {
594 j.at("position").get_to(referenceVectorConfigurationRegister.position);
595 }
596 }
597
598 // ###########################################################################################################
599 // VELOCITY AIDING
600 // ###########################################################################################################
601
602 static void to_json(json& j, const VelocityCompensationControlRegister& velocityCompensationControlRegister) // NOLINT(misc-use-anonymous-namespace)
603 {
604 j = json{
605 { "mode", velocityCompensationControlRegister.mode },
606 { "velocityTuning", velocityCompensationControlRegister.velocityTuning },
607 { "rateTuning", velocityCompensationControlRegister.rateTuning },
608 };
609 }
610 static void from_json(const json& j, VelocityCompensationControlRegister& velocityCompensationControlRegister) // NOLINT(misc-use-anonymous-namespace)
611 {
612 if (j.contains("mode"))
613 {
614 j.at("mode").get_to(velocityCompensationControlRegister.mode);
615 }
616 if (j.contains("velocityTuning"))
617 {
618 j.at("velocityTuning").get_to(velocityCompensationControlRegister.velocityTuning);
619 }
620 if (j.contains("rateTuning"))
621 {
622 j.at("rateTuning").get_to(velocityCompensationControlRegister.rateTuning);
623 }
624 }
625
626 } // namespace sensors
627 } // namespace vn
628
629 void NAV::VectorNavSensor::coupleImuFilterRates(NAV::VectorNavSensor* sensor, vn::sensors::BinaryOutputRegister& bor, uint32_t& /* binaryField */)
630 {
631 // Set 'TimeStartup' and 'TimeStatus' always
632 (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS);
633
634 if (sensor->_sensorModel == VectorNavModel::VN310)
635 {
636 (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK);
637 }
638
639 if (sensor->_coupleImuRateOutput)
640 {
641 if (bor.imuField & (vn::protocol::uart::IMUGROUP_ACCEL | vn::protocol::uart::IMUGROUP_UNCOMPACCEL))
642 {
643 sensor->_imuFilteringConfigurationRegister.accelWindowSize = bor.rateDivisor;
644 }
645 if (bor.imuField & (vn::protocol::uart::IMUGROUP_ANGULARRATE | vn::protocol::uart::IMUGROUP_UNCOMPGYRO))
646 {
647 sensor->_imuFilteringConfigurationRegister.gyroWindowSize = bor.rateDivisor;
648 }
649 if (bor.imuField & (vn::protocol::uart::IMUGROUP_MAG | vn::protocol::uart::IMUGROUP_UNCOMPMAG))
650 {
651 sensor->_imuFilteringConfigurationRegister.magWindowSize = bor.rateDivisor;
652 }
653 if (bor.imuField & (vn::protocol::uart::IMUGROUP_MAG | vn::protocol::uart::IMUGROUP_UNCOMPMAG))
654 {
655 sensor->_imuFilteringConfigurationRegister.magWindowSize = bor.rateDivisor;
656 }
657 if (bor.imuField & vn::protocol::uart::IMUGROUP_TEMP)
658 {
659 sensor->_imuFilteringConfigurationRegister.tempWindowSize = bor.rateDivisor;
660 }
661 if (bor.imuField & vn::protocol::uart::IMUGROUP_PRES)
662 {
663 sensor->_imuFilteringConfigurationRegister.presWindowSize = bor.rateDivisor;
664 }
665 LOG_DATA("Frequency of ImuFilter matches output frequency (coupled 'rateDivisor' and 'windowSize' of moving average filter.)");
666
667 if (sensor->isInitialized() && sensor->_vs.isConnected() && sensor->_vs.verifySensorConnectivity())
668 {
669 try
670 {
671 sensor->_vs.writeImuFilteringConfiguration(sensor->_imuFilteringConfigurationRegister);
672 }
673 catch (const std::exception& e)
674 {
675 LOG_ERROR("Could not configure the imuFilteringConfigurationRegister: {}", e.what());
676 sensor->doDeinitialize();
677 }
678 }
679 else
680 {
681 sensor->doDeinitialize();
682 }
683 }
684 };
685
686 // NOLINTBEGIN
687
688 // const std::array<NAV::VectorNavSensor::BinaryGroupData, 15> NAV::VectorNavSensor::_binaryGroupCommon = { {
689 // { /* 0 */ "TimeStartup", vn::protocol::uart::CommonGroup::COMMONGROUP_TIMESTARTUP,
690 // []() { ImGui::TextUnformatted("Time since startup.\n\nThe system time since startup measured in nano seconds. The time since startup is based upon the internal\nTXCO oscillator for the MCU. The accuracy of the internal TXCO is +/- 20ppm (-40C to 85C). This field is\nequivalent to the TimeStartup field in group 2."); } },
691 // { /* 1 */ "TimeGps", vn::protocol::uart::CommonGroup::COMMONGROUP_TIMEGPS,
692 // []() { ImGui::TextUnformatted("GPS time.\n\nThe absolute GPS time since start of GPS epoch 1980 expressed in nano seconds. This field is equivalent to\nthe TimeGps field in group 2."); },
693 // [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
694 // [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& /* binaryField */) { (bor.commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_TIMEGPS) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS); } },
695 // { /* 2 */ "TimeSyncIn", vn::protocol::uart::CommonGroup::COMMONGROUP_TIMESYNCIN,
696 // []() { ImGui::TextUnformatted("Time since last SyncIn trigger.\n\nThe time since the last SyncIn trigger event expressed in nano seconds. This field is equivalent to the\nTimeSyncIn field in group 2."); } },
697 // { /* 3 */ "YawPitchRoll", vn::protocol::uart::CommonGroup::COMMONGROUP_YAWPITCHROLL,
698 // []() { ImGui::TextUnformatted("Estimated attitude as yaw pitch and roll angles.\n\nThe estimated attitude Yaw, Pitch, and Roll angles measured in degrees. The attitude is given as a 3,2,1 Euler\nangle sequence describing the body frame with respect to the local North East Down (NED) frame. This field\nis equivalent to the YawPitchRoll field in group 5.\n\nYaw [+/- 180°]\nPitch [+/- 90°]\nRoll [+/- 180°]"); } },
699 // { /* 4 */ "Quaternion", vn::protocol::uart::CommonGroup::COMMONGROUP_QUATERNION,
700 // []() { ImGui::TextUnformatted("Estimated attitude as a quaternion.\n\nThe estimated attitude quaternion. The last term is the scalar value. The attitude is given as the body frame\nwith respect to the local North East Down (NED) frame. This field is equivalent to the Quaternion field in\ngroup 5."); } },
701 // { /* 5 */ "AngularRate", vn::protocol::uart::CommonGroup::COMMONGROUP_ANGULARRATE,
702 // []() { ImGui::TextUnformatted("Compensated angular rate.\n\nThe estimated angular rate measured in rad/s. The angular rates are compensated by the onboard filter bias\nestimates. The angular rate is expressed in the body frame. This field is equivalent to the AngularRate field\nin group 3."); } },
703 // { /* 6 */ "Position", vn::protocol::uart::CommonGroup::COMMONGROUP_POSITION,
704 // []() { ImGui::TextUnformatted("Estimated position. (LLA)\n\nThe estimated position given as latitude, longitude, and altitude given in [deg, deg, m] respectively. This field\nis equivalent to the PosLla field in group 6."); },
705 // [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
706 // [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& /* binaryField */) { (bor.commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_POSITION) && (bor.commonField |= vn::protocol::uart::CommonGroup::COMMONGROUP_INSSTATUS); } },
707 // { /* 7 */ "Velocity", vn::protocol::uart::CommonGroup::COMMONGROUP_VELOCITY,
708 // []() { ImGui::TextUnformatted("Estimated velocity. (NED)\n\nThe estimated velocity in the North East Down (NED) frame, given in m/s. This field is equivalent to the\nVelNed field in group 6."); },
709 // [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
710 // [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& /* binaryField */) { (bor.commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_VELOCITY) && (bor.commonField |= vn::protocol::uart::CommonGroup::COMMONGROUP_INSSTATUS); } },
711 // { /* 8 */ "Accel", vn::protocol::uart::CommonGroup::COMMONGROUP_ACCEL,
712 // []() { ImGui::TextUnformatted("Estimated acceleration (compensated). (Body)\n\nThe estimated acceleration in the body frame, given in m/s^2. This acceleration includes gravity and has\nbeen bias compensated by the onboard INS Kalman filter. This field is equivalent to the Accel field in group 3."); } },
713 // { /* 9 */ "Imu", vn::protocol::uart::CommonGroup::COMMONGROUP_IMU,
714 // []() { ImGui::TextUnformatted("Calibrated uncompensated gyro and accelerometer measurements.\n\nThe uncompensated IMU acceleration and angular rate measurements. The acceleration is given in m/s^2,\nand the angular rate is given in rad/s. These measurements correspond to the calibrated angular rate and\nacceleration measurements straight from the IMU. The measurements have not been corrected for bias\noffset by the onboard Kalman filter. These are equivalent to the UncompAccel and UncompGyro fields in\ngroup 3."); } },
715 // { /* 10 */ "MagPres", vn::protocol::uart::CommonGroup::COMMONGROUP_MAGPRES,
716 // []() { ImGui::TextUnformatted("Calibrated magnetic (compensated), temperature, and pressure measurements.\n\nThe compensated magnetic, temperature, and pressure measurements from the IMU. The magnetic\nmeasurement is given in Gauss, and has been corrected for hard/soft iron corrections (if enabled). The\ntemperature measurement is given in Celsius. The pressure measurement is given in kPa. This field is\nequivalent to the Mag, Temp, and Pres fields in group 3.\n\nThe IP-68 enclosure on the tactical series forms an airtight (hermetic) seal isolating the internal\nsensors from the external environment. The pressure sensor is internal to this seal, and as such\nwill not measure the outside environment atmospheric pressure. It will instead read the pressure\ninside the sealed enclosure. The purpose of this sensor is to provide a means of ensuring the\nseal integrity over the lifetime of the product. Based on the Ideal Gas Law the ratio of pressure\ndivided by temperature should remain constant over both time and environmental temperature.\nWhen this is no longer the case, it can be assumed that the seal integrity has been compromised."); } },
717 // { /* 11 */ "DeltaTheta", vn::protocol::uart::CommonGroup::COMMONGROUP_DELTATHETA,
718 // []() { ImGui::TextUnformatted("Delta time, theta, and velocity.\n\nThe delta time, angle, and velocity measurements. The delta time (dtime) is the time interval that the delta\nangle and velocities are integrated over. The delta theta (dtheta) is the delta rotation angles incurred due to\nrotation, by the local body reference frame, since the last time the values were outputted by the device. The\ndelta velocity (dvel) is the delta velocity incurred due to motion, by the local body reference frame, since the\nlast time the values were outputted by the device. The frame of reference of these delta measurements are\ndetermined by the IntegrationFrame field in the Delta Theta and Delta Velocity Configuration Register\n(Register 82). These delta angles and delta velocities are calculated based upon the onboard coning and\nsculling integration performed onboard the sensor at the full IMU rate (default 800Hz). The integration for\nboth the delta angles and velocities are reset each time either of the values are either polled or sent out due\nto a scheduled asynchronous ASCII or binary output. Delta Theta and Delta Velocity values correctly capture\nthe nonlinearities involved in measuring motion from a rotating strapdown platform (as opposed to the older\nmechanically inertial navigation systems), thus providing you with the ability to integrate velocity and angular\nrate at much lower speeds (say for example 10 Hz, reducing bandwidth and computational complexity), while\nstill maintaining the same numeric precision as if you had performed the integration at the full IMU\nmeasurement rate of 800Hz. This field is equivalent to the DeltaTheta and DeltaVel fields in group 3 with the\ninclusion of the additional delta time parameter."); } },
719 // { /* 12 */ "Ins/VpeStatus", vn::protocol::uart::CommonGroup::COMMONGROUP_INSSTATUS,
720 // []() { ImGui::TextUnformatted("INS status (VN310).\n\nThe INS status bitfield. This field is equivalent to the InsSatus field in group 6.\nSee INS Solution LLA Register for more information on the individual bits in this field.");
721 // ImGui::Separator();
722 // ImGui::TextUnformatted("VPE Status (VN100).\n\nThe VPE status bitfield. This field is equivalent to the VpeStatus field in group 5.\nSee Group 5 - VPE for more information on the individual bits in this field."); },
723 // [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t binaryField) { return !(static_cast<vn::protocol::uart::CommonGroup>(binaryField) & vn::protocol::uart::CommonGroup::COMMONGROUP_POSITION) && !(static_cast<vn::protocol::uart::CommonGroup>(binaryField) & vn::protocol::uart::CommonGroup::COMMONGROUP_VELOCITY); } },
724 // { /* 13 */ "SyncInCnt", vn::protocol::uart::CommonGroup::COMMONGROUP_SYNCINCNT,
725 // []() { ImGui::TextUnformatted("SyncIn count.\n\nThe number of SyncIn trigger events that have occurred. This field is equivalent to the SyncInCnt field in\ngroup 2."); } },
726 // { /* 14 */ "TimeGpsPps", vn::protocol::uart::CommonGroup::COMMONGROUP_TIMEGPSPPS,
727 // []() { ImGui::TextUnformatted("Time since last GNSS PPS trigger.\n\nThe time since the last GPS PPS trigger event expressed in nano seconds. This field is equivalent to the\nTimePPS field in group 2."); },
728 // [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; } },
729 // } };
730
731 const std::array<NAV::VectorNavSensor::BinaryGroupData, 10> NAV::VectorNavSensor::_binaryGroupTime = { {
732 { /* 0 */ "TimeStartup", vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP,
733 []() { ImGui::TextUnformatted("Time since startup.\n\nThe system time since startup measured in nano seconds. The time since startup is based upon the internal\nTXCO oscillator for the MCU. The accuracy of the internal TXCO is +/- 20ppm (-40C to 85C)."); },
734 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& bor, uint32_t /* binaryField */) { return (sensorModel != VectorNavModel::VN310
735 || !((bor.gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
736 || (bor.gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
737 || (bor.gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
738 || (bor.gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)))
739 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPMAG)
740 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL)
741 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO)
742 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_TEMP)
743 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_PRES)
744 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA)
745 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL)
746 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_MAG)
747 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL)
748 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE)
749 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YAWPITCHROLL)
750 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_QUATERNION)
751 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_DCM)
752 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_MAGNED)
753 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_ACCELNED)
754 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELBODY)
755 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELNED)
756 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YPRU)
757 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_INSSTATUS)
758 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSLLA)
759 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSECEF)
760 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELBODY)
761 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELNED)
762 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELECEF)
763 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_MAGECEF)
764 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_ACCELECEF)
765 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_LINEARACCELECEF)
766 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSU)
767 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELU); } },
768 { /* 1 */ "TimeGps", vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPS,
769 []() { ImGui::TextUnformatted("Absolute GPS time.\n\nThe absolute GPS time since start of GPS epoch 1980 expressed in nano seconds."); },
770 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
771 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& /* binaryField */) { (bor.timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPS) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS); } },
772 { /* 2 */ "GpsTow", vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW,
773 []() { ImGui::TextUnformatted("Time since start of GPS week.\n\nThe time since the start of the current GPS time week expressed in nano seconds."); },
774 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& bor, uint32_t /* binaryField */) { return (sensorModel == VectorNavModel::VN310)
775 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPMAG)
776 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL)
777 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO)
778 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_TEMP)
779 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_PRES)
780 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA)
781 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL)
782 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_MAG)
783 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL)
784 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE)
785 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YAWPITCHROLL)
786 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_QUATERNION)
787 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_DCM)
788 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_MAGNED)
789 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_ACCELNED)
790 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELBODY)
791 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELNED)
792 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YPRU)
793 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_INSSTATUS)
794 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSLLA)
795 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSECEF)
796 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELBODY)
797 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELNED)
798 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELECEF)
799 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_MAGECEF)
800 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_ACCELECEF)
801 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_LINEARACCELECEF)
802 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSU)
803 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELU); },
804 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& /* binaryField */) { (bor.timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS); } },
805 { /* 3 */ "GpsWeek", vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK,
806 []() { ImGui::TextUnformatted("GPS week.\n\nThe current GPS week."); },
807 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& bor, uint32_t /* binaryField */) { return (sensorModel == VectorNavModel::VN310)
808 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPMAG)
809 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL)
810 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO)
811 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_TEMP)
812 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_PRES)
813 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA)
814 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL)
815 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_MAG)
816 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL)
817 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE)
818 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YAWPITCHROLL)
819 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_QUATERNION)
820 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_DCM)
821 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_MAGNED)
822 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_ACCELNED)
823 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELBODY)
824 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELNED)
825 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YPRU)
826 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_INSSTATUS)
827 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSLLA)
828 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSECEF)
829 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELBODY)
830 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELNED)
831 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELECEF)
832 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_MAGECEF)
833 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_ACCELECEF)
834 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_LINEARACCELECEF)
835 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSU)
836 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELU); },
837 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& /* binaryField */) { (bor.timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS); } },
838 { /* 4 */ "TimeSyncIn", vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESYNCIN,
839 []() { ImGui::TextUnformatted("Time since last SyncIn trigger.\n\nThe time since the last SyncIn event trigger expressed in nano seconds."); } },
840 { /* 5 */ "TimeGpsPps", vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPSPPS,
841 []() { ImGui::TextUnformatted("Time since last GPS PPS trigger.\n\nThe time since the last GPS PPS trigger event expressed in nano seconds."); },
842 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; } },
843 { /* 6 */ "TimeUTC", vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEUTC,
844 []() { ImGui::TextUnformatted("UTC time.\n\nThe current UTC time. The year is given as a signed byte year offset from the year 2000. For example the\nyear 2013 would be given as year 13."); },
845 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return false; },
846 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& /* binaryField */) { (bor.timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEUTC) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS); } },
847 { /* 7 */ "SyncInCnt", vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCINCNT,
848 []() { ImGui::TextUnformatted("SyncIn trigger count.\n\nThe number of SyncIn trigger events that have occurred."); } },
849 { /* 8 */ "SyncOutCnt", vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCOUTCNT,
850 []() { ImGui::TextUnformatted("SyncOut trigger count.\n\nThe number of SyncOut trigger events that have occurred."); } },
851 { /* 9 */ "TimeStatus", vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS,
852 []() { ImGui::TextUnformatted("Time valid status flags.");
853 if (ImGui::BeginTable("VectorNavTimeStatusTooltip", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
854 {
855 ImGui::TableSetupColumn("Bit Offset", ImGuiTableColumnFlags_WidthFixed);
856 ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthFixed);
857 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthFixed);
858 ImGui::TableHeadersRow();
859
860 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
861 ImGui::TableNextColumn(); ImGui::TextUnformatted("timeOk");
862 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 - GpsTow is valid");
863
864 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
865 ImGui::TableNextColumn(); ImGui::TextUnformatted("dateOk");
866 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 - TimeGps and GpsWeek are valid");
867
868 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
869 ImGui::TableNextColumn(); ImGui::TextUnformatted("utcTimeValid");
870 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 - UTC time is valid");
871
872 ImGui::TableNextColumn(); ImGui::TextUnformatted("3 - 7");
873 ImGui::TableNextColumn(); ImGui::TextUnformatted("resv");
874 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved for future use");
875
876 ImGui::EndTable();
877 } },
878 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& bor, uint32_t /* binaryField */) { return !(bor.commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_TIMEGPS)
879 && !(bor.timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)
880 && !(bor.timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK)
881 && !(bor.timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPS)
882 && !(bor.timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEUTC)
883 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPMAG)
884 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL)
885 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO)
886 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_TEMP)
887 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_PRES)
888 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA)
889 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL)
890 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_MAG)
891 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL)
892 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE); } },
893 } };
894
895 const std::array<NAV::VectorNavSensor::BinaryGroupData, 11> NAV::VectorNavSensor::_binaryGroupIMU{ {
896 { /* 0 */ "ImuStatus", vn::protocol::uart::ImuGroup::IMUGROUP_IMUSTATUS,
897 []() { ImGui::TextUnformatted("Status is reserved for future use. Not currently used in the current code, as such will always report 0."); },
898 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return false; } },
899 { /* 1 */ "UncompMag", vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPMAG,
900 []() { ImGui::TextUnformatted("Uncompensated magnetic measurement.\n\nThe IMU magnetic field measured in units of Gauss, given in the body frame. This measurement is\ncompensated by the static calibration (individual factory calibration stored in flash), and the user\ncompensation, however it is not compensated by the onboard Hard/Soft Iron estimator."); },
901 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
902 { /* 2 */ "UncompAccel", vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL,
903 []() { ImGui::TextUnformatted("Uncompensated acceleration measurement.\n\nThe IMU acceleration measured in units of m/s^2, given in the body frame. This measurement is\ncompensated by the static calibration (individual factory calibration stored in flash), however it is not\ncompensated by any dynamic calibration such as bias compensation from the onboard INS Kalman filter."); },
904 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
905 { /* 3 */ "UncompGyro", vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO,
906 []() { ImGui::TextUnformatted("Uncompensated angular rate measurement.\n\nThe IMU angular rate measured in units of rad/s, given in the body frame. This measurement is compensated\nby the static calibration (individual factory calibration stored in flash), however it is not compensated by any\ndynamic calibration such as the bias compensation from the onboard AHRS/INS Kalman filters."); },
907 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
908 { /* 4 */ "Temp", vn::protocol::uart::ImuGroup::IMUGROUP_TEMP,
909 []() { ImGui::TextUnformatted("Temperature measurement.\n\nThe IMU temperature measured in units of Celsius."); },
910 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
911 { /* 5 */ "Pres", vn::protocol::uart::ImuGroup::IMUGROUP_PRES,
912 []() { ImGui::TextUnformatted("Pressure measurement.\n\nThe IMU pressure measured in kilopascals. This is an absolute pressure measurement. Typical pressure at sea level would be around 100 kPa."); },
913 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
914 { /* 6 */ "DeltaTheta", vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA,
915 []() { ImGui::TextUnformatted("Delta theta angles.\n\nThe delta time (dtime) is the time interval that the delta angle and velocities are integrated over. The delta\ntheta (dtheta) is the delta rotation angles incurred due to rotation, by the local body reference frame, since\nthe last time the values were outputted by the device. The delta velocity (dvel) is the delta velocity incurred\ndue to motion, by the local body reference frame, since the last time the values were outputted by the device.\nThe frame of reference of these delta measurements are determined by the IntegrationFrame field in the\nDelta Theta and Delta Velocity Configuration Register (Register 82). These delta angles and delta velocities\nare calculated based upon the onboard coning and sculling integration performed onboard the sensor at the\nfull IMU rate (default 800Hz). The integration for both the delta angles and velocities are reset each time\neither of the values are either polled or sent out due to a scheduled asynchronous ASCII or binary output.\nDelta Theta and Delta Velocity values correctly capture the nonlinearities involved in measuring motion from\na rotating strapdown platform (as opposed to the older mechanically inertial navigation systems), thus\nproviding you with the ability to integrate velocity and angular rate at much lower speeds (say for example\n10 Hz, reducing bandwidth and computational complexity), while still maintaining the same numeric\nprecision as if you had performed the integration at the full IMU measurement rate of 800Hz. Time is given\nin seconds. Delta angles are given in degrees."); } },
916 { /* 7 */ "DeltaVel", vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL,
917 []() { ImGui::TextUnformatted("Delta velocity.\n\nThe delta velocity (dvel) is the delta velocity incurred due to motion, since the last time the values were output\nby the device. The delta velocities are calculated based upon the onboard conning and sculling integration\nperformed onboard the sensor at the IMU sampling rate (nominally 800Hz). The integration for the delta\nvelocities are reset each time the values are either polled or sent out due to a scheduled asynchronous ASCII\nor binary output. Delta velocity is given in meters per second."); } },
918 { /* 8 */ "Mag", vn::protocol::uart::ImuGroup::IMUGROUP_MAG,
919 []() { ImGui::TextUnformatted("Compensated magnetic measurement.\n\nThe IMU compensated magnetic field measured units of Gauss, and given in the body frame. This\nmeasurement is compensated by the static calibration (individual factory calibration stored in flash), the user\ncompensation, and the dynamic calibration from the onboard Hard/Soft Iron estimator."); },
920 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
921 { /* 9 */ "Accel", vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL,
922 []() { ImGui::TextUnformatted("Compensated acceleration measurement.\n\nThe compensated acceleration measured in units of m/s^2, and given in the body frame. This measurement\nis compensated by the static calibration (individual factory calibration stored in flash), the user\ncompensation, and the dynamic bias compensation from the onboard INS Kalman filter."); },
923 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
924 { /* 10 */ "AngularRate", vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE,
925 []() { ImGui::TextUnformatted("Compensated angular rate measurement.\n\nThe compensated angular rate measured in units of rad/s, and given in the body frame. This measurement\nis compensated by the static calibration (individual factor calibration stored in flash), the user compensation,\nand the dynamic bias compensation from the onboard INS Kalman filter."); },
926 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
927 } };
928
929 const std::array<NAV::VectorNavSensor::BinaryGroupData, 16> NAV::VectorNavSensor::_binaryGroupGNSS{ {
930 { /* 0 */ "UTC", vn::protocol::uart::GpsGroup::GPSGROUP_UTC,
931 []() { ImGui::TextUnformatted("GPS UTC Time\n\nThe current UTC time. The year is given as a signed byte year offset from the year 2000. For example the\nyear 2013 would be given as year 13."); },
932 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return false; },
933 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& /* bor */, uint32_t& binaryField) { (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_UTC) && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO); } },
934 { /* 1 */ "Tow", vn::protocol::uart::GpsGroup::GPSGROUP_TOW,
935 []() { ImGui::TextUnformatted("GPS time of week\n\nThe GPS time of week given in nano seconds."); },
936 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t binaryField) { return sensorModel == VectorNavModel::VN310
937 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA)
938 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF)
939 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED)
940 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF)
941 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSU)
942 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELU); },
943 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_TOW) && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO) && ((bor.commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_TIMESTARTUP) || (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP)); } },
944 { /* 2 */ "Week", vn::protocol::uart::GpsGroup::GPSGROUP_WEEK,
945 []() { ImGui::TextUnformatted("GPS week\n\nThe current GPS week."); },
946 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t binaryField) { return sensorModel == VectorNavModel::VN310
947 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA)
948 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF)
949 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED)
950 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF)
951 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSU)
952 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELU); },
953 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK) && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO) && ((bor.commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_TIMESTARTUP) || (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP)); } },
954 { /* 3 */ "NumSats", vn::protocol::uart::GpsGroup::GPSGROUP_NUMSATS,
955 []() { ImGui::TextUnformatted("Number of tracked satellites\n\nThe number of tracked GNSS satellites."); },
956 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; } },
957 { /* 4 */ "Fix", vn::protocol::uart::GpsGroup::GPSGROUP_FIX,
958 []() { ImGui::TextUnformatted("GNSS fix\n\nThe current GNSS fix.");
959 if (ImGui::BeginTable("VectorNavFixTooltip", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
960 {
961 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed);
962 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthFixed);
963 ImGui::TableHeadersRow();
964
965 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
966 ImGui::TableNextColumn(); ImGui::TextUnformatted("No fix");
967
968 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
969 ImGui::TableNextColumn(); ImGui::TextUnformatted("Time only");
970
971 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
972 ImGui::TableNextColumn(); ImGui::TextUnformatted("2D");
973
974 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
975 ImGui::TableNextColumn(); ImGui::TextUnformatted("3D");
976
977 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
978 ImGui::TableNextColumn(); ImGui::TextUnformatted("SBAS");
979
980 ImGui::TableNextColumn(); ImGui::TextUnformatted("7");
981 ImGui::TableNextColumn(); ImGui::TextUnformatted("RTK Float (only GNSS1)");
982
983 ImGui::TableNextColumn(); ImGui::TextUnformatted("8");
984 ImGui::TableNextColumn(); ImGui::TextUnformatted("RTK Fixed (only GNSS1)");
985
986 ImGui::EndTable();
987 } },
988 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t binaryField) { return sensorModel == VectorNavModel::VN310
989 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA)
990 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF)
991 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED)
992 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF)
993 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSU)
994 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELU); } },
995 { /* 5 */ "PosLla", vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA,
996 []() { ImGui::TextUnformatted("GNSS position (latitude, longitude, altitude)\n\nThe current GNSS position measurement given as the geodetic latitude, longitude and altitude above the\nellipsoid. The units are in [deg, deg, m] respectively."); },
997 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
998 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) {
999 (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA)
1000 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
1001 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
1002 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
1003 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO);
1004 bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP;
1005 } },
1006 { /* 6 */ "PosEcef", vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF,
1007 []() { ImGui::TextUnformatted("GNSS position (ECEF)\n\nThe current GNSS position given in the Earth centered Earth fixed (ECEF) coordinate frame, given in meters."); },
1008 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1009 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) {
1010 (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF)
1011 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
1012 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
1013 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
1014 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO);
1015 bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP;
1016 } },
1017 { /* 7 */ "VelNed", vn::protocol::uart::GpsGroup::GPSGROUP_VELNED,
1018 []() { ImGui::TextUnformatted("GNSS velocity (NED)\n\nThe current GNSS velocity in the North East Down (NED) coordinate frame, given in m/s."); },
1019 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1020 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) {
1021 (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED)
1022 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
1023 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
1024 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
1025 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO);
1026 bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP;
1027 } },
1028 { /* 8 */ "VelEcef", vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF,
1029 []() { ImGui::TextUnformatted("GNSS velocity (ECEF)\n\nThe current GNSS velocity in the Earth centered Earth fixed (ECEF) coordinate frame, given in m/s."); },
1030 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1031 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) {
1032 (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF)
1033 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
1034 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
1035 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
1036 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO);
1037 bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP;
1038 } },
1039 { /* 9 */ "PosU", vn::protocol::uart::GpsGroup::GPSGROUP_POSU,
1040 []() { ImGui::TextUnformatted("GNSS position uncertainty (NED)\n\nThe current GNSS position uncertainty in the North East Down (NED) coordinate frame, given in meters (1 Sigma)."); },
1041 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1042 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) {
1043 (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSU)
1044 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
1045 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
1046 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
1047 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO);
1048 bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP;
1049 } },
1050 { /* 10 */ "VelU", vn::protocol::uart::GpsGroup::GPSGROUP_VELU,
1051 []() { ImGui::TextUnformatted("GNSS velocity uncertainty\n\nThe current GNSS velocity uncertainty, given in m/s (1 Sigma)."); },
1052 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1053 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) {
1054 (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELU)
1055 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
1056 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
1057 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
1058 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO);
1059 bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP;
1060 } },
1061 { /* 11 */ "TimeU", vn::protocol::uart::GpsGroup::GPSGROUP_TIMEU,
1062 []() { ImGui::TextUnformatted("GNSS time uncertainty\n\nThe current GPS time uncertainty, given in seconds (1 Sigma)."); },
1063 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1064 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& /* bor */, uint32_t& binaryField) { (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEU) && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO); } },
1065 { /* 12 */ "TimeInfo", vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO,
1066 []() { ImGui::TextUnformatted("GNSS time status and leap seconds\n\nFlags for valid GPS TOW, week number and UTC and current leap seconds.");
1067 if (ImGui::BeginTable("VectorNavTimeInfoTooltip", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1068 {
1069 ImGui::TableSetupColumn("Bit Offset");
1070 ImGui::TableSetupColumn("Field");
1071 ImGui::TableSetupColumn("Description");
1072 ImGui::TableHeadersRow();
1073
1074 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1075 ImGui::TableNextColumn(); ImGui::TextUnformatted("timeOk");
1076 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 - GpsTow is valid");
1077
1078 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1079 ImGui::TableNextColumn(); ImGui::TextUnformatted("dateOk");
1080 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 - TimeGps and GpsWeek are valid");
1081
1082 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1083 ImGui::TableNextColumn(); ImGui::TextUnformatted("utcTimeValid");
1084 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 - UTC time is valid");
1085
1086 ImGui::TableNextColumn(); ImGui::TextUnformatted("3 - 7");
1087 ImGui::TableNextColumn(); ImGui::TextUnformatted("resv");
1088 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved for future use");
1089
1090 ImGui::EndTable();
1091 } },
1092 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t binaryField) { return sensorModel == VectorNavModel::VN310
1093 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
1094 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
1095 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_UTC)
1096 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEU)
1097 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_RAWMEAS); } },
1098 { /* 13 */ "DOP", vn::protocol::uart::GpsGroup::GPSGROUP_DOP,
1099 []() { ImGui::TextUnformatted("Dilution of precision"); },
1100 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; } },
1101 { /* 14 */ "SatInfo", vn::protocol::uart::GpsGroup::GPSGROUP_SATINFO,
1102 []() { ImGui::TextUnformatted("Satellite Information\n\nInformation and measurements pertaining to each GNSS satellite in view.\n\nSatInfo Element:");
1103 if (ImGui::BeginTable("VectorNavSatInfoTooltip", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1104 {
1105 ImGui::TableSetupColumn("Name");
1106 ImGui::TableSetupColumn("Description");
1107 ImGui::TableHeadersRow();
1108
1109 ImGui::TableNextColumn(); ImGui::TextUnformatted("sys");
1110 ImGui::TableNextColumn(); ImGui::TextUnformatted("GNSS constellation indicator. See table below for details.");
1111
1112 ImGui::TableNextColumn(); ImGui::TextUnformatted("svId");
1113 ImGui::TableNextColumn(); ImGui::TextUnformatted("Space vehicle Id");
1114
1115 ImGui::TableNextColumn(); ImGui::TextUnformatted("flags");
1116 ImGui::TableNextColumn(); ImGui::TextUnformatted("Tracking info flags. See table below for details.");
1117
1118 ImGui::TableNextColumn(); ImGui::TextUnformatted("cno");
1119 ImGui::TableNextColumn(); ImGui::TextUnformatted("Carrier-to-noise density ratio (signal strength) [dB-Hz]");
1120
1121 ImGui::TableNextColumn(); ImGui::TextUnformatted("qi");
1122 ImGui::TableNextColumn(); ImGui::TextUnformatted("Quality Indicator. See table below for details.");
1123
1124 ImGui::TableNextColumn(); ImGui::TextUnformatted("el");
1125 ImGui::TableNextColumn(); ImGui::TextUnformatted("Elevation in degrees");
1126
1127 ImGui::TableNextColumn(); ImGui::TextUnformatted("az");
1128 ImGui::TableNextColumn(); ImGui::TextUnformatted("Azimuth angle in degrees");
1129
1130 ImGui::EndTable();
1131 }
1132 ImGui::BeginChild("VectorNavSatInfoTooltipGNSSConstelationChild", ImVec2(230, 217));
1133 ImGui::TextUnformatted("\nGNSS constellation indicator:");
1134 if (ImGui::BeginTable("VectorNavSatInfoTooltipGNSSConstelation", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1135 {
1136 ImGui::TableSetupColumn("Value");
1137 ImGui::TableSetupColumn("Description");
1138 ImGui::TableHeadersRow();
1139
1140 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1141 ImGui::TableNextColumn(); ImGui::TextUnformatted("GPS");
1142
1143 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1144 ImGui::TableNextColumn(); ImGui::TextUnformatted("SBAS");
1145
1146 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1147 ImGui::TableNextColumn(); ImGui::TextUnformatted("Galileo");
1148
1149 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1150 ImGui::TableNextColumn(); ImGui::TextUnformatted("BeiDou");
1151
1152 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1153 ImGui::TableNextColumn(); ImGui::TextUnformatted("IMES");
1154
1155 ImGui::TableNextColumn(); ImGui::TextUnformatted("5");
1156 ImGui::TableNextColumn(); ImGui::TextUnformatted("QZSS");
1157
1158 ImGui::TableNextColumn(); ImGui::TextUnformatted("6");
1159 ImGui::TableNextColumn(); ImGui::TextUnformatted("GLONASS");
1160
1161 ImGui::EndTable();
1162 }
1163 ImGui::EndChild();
1164 ImGui::SameLine();
1165 ImGui::BeginChild("VectorNavSatInfoTooltipFlagsChild", ImVec2(260, 217));
1166 ImGui::TextUnformatted("\nTracking info flags:");
1167 if (ImGui::BeginTable("VectorNavSatInfoTooltipFlags", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1168 {
1169 ImGui::TableSetupColumn("Bit Offset");
1170 ImGui::TableSetupColumn("Description");
1171 ImGui::TableHeadersRow();
1172
1173 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1174 ImGui::TableNextColumn(); ImGui::TextUnformatted("Healthy");
1175
1176 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1177 ImGui::TableNextColumn(); ImGui::TextUnformatted("Almanac");
1178
1179 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1180 ImGui::TableNextColumn(); ImGui::TextUnformatted("Ephemeris");
1181
1182 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1183 ImGui::TableNextColumn(); ImGui::TextUnformatted("Differential Correction");
1184
1185 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1186 ImGui::TableNextColumn(); ImGui::TextUnformatted("Used for Navigation");
1187
1188 ImGui::TableNextColumn(); ImGui::TextUnformatted("5");
1189 ImGui::TableNextColumn(); ImGui::TextUnformatted("Azimuth / Elevation Valid");
1190
1191 ImGui::TableNextColumn(); ImGui::TextUnformatted("6");
1192 ImGui::TableNextColumn(); ImGui::TextUnformatted("Used for RTK");
1193
1194 ImGui::EndTable();
1195 }
1196 ImGui::EndChild();
1197 ImGui::TextUnformatted("\nQuality Indicators:");
1198 if (ImGui::BeginTable("VectorNavSatInfoTooltipQuality", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1199 {
1200 ImGui::TableSetupColumn("Value");
1201 ImGui::TableSetupColumn("Description");
1202 ImGui::TableHeadersRow();
1203
1204 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1205 ImGui::TableNextColumn(); ImGui::TextUnformatted("No signal");
1206
1207 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1208 ImGui::TableNextColumn(); ImGui::TextUnformatted("Searching signal");
1209
1210 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1211 ImGui::TableNextColumn(); ImGui::TextUnformatted("Signal acquired");
1212
1213 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1214 ImGui::TableNextColumn(); ImGui::TextUnformatted("Signal detected but unstable");
1215
1216 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1217 ImGui::TableNextColumn(); ImGui::TextUnformatted("Code locked and time synchronized");
1218
1219 ImGui::TableNextColumn(); ImGui::TextUnformatted("5, 6, 7");
1220 ImGui::TableNextColumn(); ImGui::TextUnformatted("Code and carrier locked and time synchronized");
1221
1222 ImGui::EndTable();
1223 } },
1224 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; } },
1225 { /* 15 */ "RawMeas", vn::protocol::uart::GpsGroup::GPSGROUP_RAWMEAS,
1226 []() { ImGui::TextUnformatted("GNSS Raw Measurements.\n\nSatRaw Element:");
1227 if (ImGui::BeginTable("VectorNavSatRawTooltip", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1228 {
1229 ImGui::TableSetupColumn("Name");
1230 ImGui::TableSetupColumn("Description");
1231 ImGui::TableHeadersRow();
1232
1233 ImGui::TableNextColumn(); ImGui::TextUnformatted("sys");
1234 ImGui::TableNextColumn(); ImGui::TextUnformatted("GNSS constellation indicator. See table below for details.");
1235
1236 ImGui::TableNextColumn(); ImGui::TextUnformatted("svId");
1237 ImGui::TableNextColumn(); ImGui::TextUnformatted("Space vehicle Id");
1238
1239 ImGui::TableNextColumn(); ImGui::TextUnformatted("freq");
1240 ImGui::TableNextColumn(); ImGui::TextUnformatted("Frequency indicator. See table below for details.");
1241
1242 ImGui::TableNextColumn(); ImGui::TextUnformatted("chan");
1243 ImGui::TableNextColumn(); ImGui::TextUnformatted("Channel Indicator. See table below for details.");
1244
1245 ImGui::TableNextColumn(); ImGui::TextUnformatted("slot");
1246 ImGui::TableNextColumn(); ImGui::TextUnformatted("Slot Id");
1247
1248 ImGui::TableNextColumn(); ImGui::TextUnformatted("cno");
1249 ImGui::TableNextColumn(); ImGui::TextUnformatted("Carrier-to-noise density ratio (signal strength) [dB-Hz]");
1250
1251 ImGui::TableNextColumn(); ImGui::TextUnformatted("flags");
1252 ImGui::TableNextColumn(); ImGui::TextUnformatted("Tracking info flags. See table below for details.");
1253
1254 ImGui::TableNextColumn(); ImGui::TextUnformatted("pr");
1255 ImGui::TableNextColumn(); ImGui::TextUnformatted("Pseudorange measurement in meters.");
1256
1257 ImGui::TableNextColumn(); ImGui::TextUnformatted("cp");
1258 ImGui::TableNextColumn(); ImGui::TextUnformatted("Carrier phase measurement in cycles.");
1259
1260 ImGui::TableNextColumn(); ImGui::TextUnformatted("dp");
1261 ImGui::TableNextColumn(); ImGui::TextUnformatted("Doppler measurement in Hz. Positive sign for approaching satellites.");
1262
1263 ImGui::EndTable();
1264 }
1265 ImGui::BeginChild("VectorNavSatRawTooltipGNSSConstelationChild", ImVec2(180, 217));
1266 ImGui::TextUnformatted("\nConstellation indicator:");
1267 if (ImGui::BeginTable("VectorNavSatRawTooltipGNSSConstelation", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1268 {
1269 ImGui::TableSetupColumn("Value");
1270 ImGui::TableSetupColumn("Description");
1271 ImGui::TableHeadersRow();
1272
1273 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1274 ImGui::TableNextColumn(); ImGui::TextUnformatted("GPS");
1275
1276 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1277 ImGui::TableNextColumn(); ImGui::TextUnformatted("SBAS");
1278
1279 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1280 ImGui::TableNextColumn(); ImGui::TextUnformatted("Galileo");
1281
1282 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1283 ImGui::TableNextColumn(); ImGui::TextUnformatted("BeiDou");
1284
1285 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1286 ImGui::TableNextColumn(); ImGui::TextUnformatted("IMES");
1287
1288 ImGui::TableNextColumn(); ImGui::TextUnformatted("5");
1289 ImGui::TableNextColumn(); ImGui::TextUnformatted("QZSS");
1290
1291 ImGui::TableNextColumn(); ImGui::TextUnformatted("6");
1292 ImGui::TableNextColumn(); ImGui::TextUnformatted("GLONASS");
1293
1294 ImGui::EndTable();
1295 }
1296 ImGui::EndChild();
1297 ImGui::SameLine();
1298 ImGui::BeginChild("VectorNavSatRawTooltipFreqChild", ImVec2(270, 235));
1299 ImGui::TextUnformatted("\nFrequency indicator:");
1300 if (ImGui::BeginTable("VectorNavSatRawTooltipFreq", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1301 {
1302 ImGui::TableSetupColumn("Value");
1303 ImGui::TableSetupColumn("Description");
1304 ImGui::TableHeadersRow();
1305
1306 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1307 ImGui::TableNextColumn(); ImGui::TextUnformatted("Rx Channel");
1308
1309 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1310 ImGui::TableNextColumn(); ImGui::TextUnformatted("L1(GPS,QZSS,SBAS), G1(GLO),\nE2-L1-E1(GAL), B1(BDS)");
1311
1312 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1313 ImGui::TableNextColumn(); ImGui::TextUnformatted("L2(GPS,QZSS), G2(GLO)");
1314
1315 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1316 ImGui::TableNextColumn(); ImGui::TextUnformatted("L5(GPS,QZSS,SBAS), E5a(GAL)");
1317
1318 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1319 ImGui::TableNextColumn(); ImGui::TextUnformatted("E6(GAL), LEX(QZSS), B3(BDS)");
1320
1321 ImGui::TableNextColumn(); ImGui::TextUnformatted("5");
1322 ImGui::TableNextColumn(); ImGui::TextUnformatted("E5b(GAL), B2(BDS)");
1323
1324 ImGui::TableNextColumn(); ImGui::TextUnformatted("6");
1325 ImGui::TableNextColumn(); ImGui::TextUnformatted("E5a+b(GAL)");
1326
1327 ImGui::EndTable();
1328 }
1329 ImGui::EndChild();
1330 ImGui::SameLine();
1331 ImGui::BeginChild("VectorNavSatRawTooltipFlagChild", ImVec2(255, 260));
1332 ImGui::TextUnformatted("\nTracking info flags:");
1333 if (ImGui::BeginTable("VectorNavSatRawTooltipFlags", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1334 {
1335 ImGui::TableSetupColumn("Bit Offset");
1336 ImGui::TableSetupColumn("Description");
1337 ImGui::TableHeadersRow();
1338
1339 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1340 ImGui::TableNextColumn(); ImGui::TextUnformatted("Searching");
1341
1342 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1343 ImGui::TableNextColumn(); ImGui::TextUnformatted("Tracking");
1344
1345 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1346 ImGui::TableNextColumn(); ImGui::TextUnformatted("Time Valid");
1347
1348 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1349 ImGui::TableNextColumn(); ImGui::TextUnformatted("Code Lock");
1350
1351 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1352 ImGui::TableNextColumn(); ImGui::TextUnformatted("Phase Lock");
1353
1354 ImGui::TableNextColumn(); ImGui::TextUnformatted("5");
1355 ImGui::TableNextColumn(); ImGui::TextUnformatted("Phase Half Ambiguity");
1356
1357 ImGui::TableNextColumn(); ImGui::TextUnformatted("6");
1358 ImGui::TableNextColumn(); ImGui::TextUnformatted("Phase Half Sub");
1359
1360 ImGui::TableNextColumn(); ImGui::TextUnformatted("7");
1361 ImGui::TableNextColumn(); ImGui::TextUnformatted("Phase Slip");
1362
1363 ImGui::TableNextColumn(); ImGui::TextUnformatted("8");
1364 ImGui::TableNextColumn(); ImGui::TextUnformatted("Pseudorange Smoothed");
1365
1366 ImGui::EndTable();
1367 }
1368 ImGui::EndChild();
1369 ImGui::TextUnformatted("\nChannel indicator:");
1370 if (ImGui::BeginTable("VectorNavSatRawTooltipChan", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1371 {
1372 ImGui::TableSetupColumn("Value");
1373 ImGui::TableSetupColumn("Description");
1374 ImGui::TableHeadersRow();
1375
1376 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1377 ImGui::TableNextColumn(); ImGui::TextUnformatted("P-code (GPS,GLO)");
1378
1379 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1380 ImGui::TableNextColumn(); ImGui::TextUnformatted("C/A-code (GPS,GLO,SBAS,QZSS), C chan (GAL)");
1381
1382 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1383 ImGui::TableNextColumn(); ImGui::TextUnformatted("semi-codeless (GPS)");
1384
1385 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1386 ImGui::TableNextColumn(); ImGui::TextUnformatted("Y-code (GPS)");
1387
1388 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1389 ImGui::TableNextColumn(); ImGui::TextUnformatted("M-code (GPS)");
1390
1391 ImGui::TableNextColumn(); ImGui::TextUnformatted("5");
1392 ImGui::TableNextColumn(); ImGui::TextUnformatted("codeless (GPS)");
1393
1394 ImGui::TableNextColumn(); ImGui::TextUnformatted("6");
1395 ImGui::TableNextColumn(); ImGui::TextUnformatted("A chan (GAL)");
1396
1397 ImGui::TableNextColumn(); ImGui::TextUnformatted("7");
1398 ImGui::TableNextColumn(); ImGui::TextUnformatted("B chan (GAL)");
1399
1400 ImGui::TableNextColumn(); ImGui::TextUnformatted("8");
1401 ImGui::TableNextColumn(); ImGui::TextUnformatted("I chan (GPS,GAL,QZSS,BDS)");
1402
1403 ImGui::TableNextColumn(); ImGui::TextUnformatted("9");
1404 ImGui::TableNextColumn(); ImGui::TextUnformatted("Q chan (GPS,GAL,QZSS,BDS)");
1405
1406 ImGui::TableNextColumn(); ImGui::TextUnformatted("10");
1407 ImGui::TableNextColumn(); ImGui::TextUnformatted("M chan (L2CGPS, L2CQZSS), D chan (GPS,QZSS)");
1408
1409 ImGui::TableNextColumn(); ImGui::TextUnformatted("11");
1410 ImGui::TableNextColumn(); ImGui::TextUnformatted("L chan (L2CGPS, L2CQZSS), P chan (GPS,QZSS)");
1411
1412 ImGui::TableNextColumn(); ImGui::TextUnformatted("12");
1413 ImGui::TableNextColumn(); ImGui::TextUnformatted("B+C chan (GAL), I+Q chan (GPS,GAL,QZSS,BDS),\nM+L chan (GPS,QZSS), D+P chan (GPS,QZSS)");
1414
1415 ImGui::TableNextColumn(); ImGui::TextUnformatted("13");
1416 ImGui::TableNextColumn(); ImGui::TextUnformatted("based on Z-tracking (GPS)");
1417
1418 ImGui::TableNextColumn(); ImGui::TextUnformatted("14");
1419 ImGui::TableNextColumn(); ImGui::TextUnformatted("A+B+C (GAL)");
1420
1421 ImGui::EndTable();
1422 } },
1423 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1424 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& /* bor */, uint32_t& binaryField) { (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_RAWMEAS) && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO); } },
1425 } };
1426
1427 const std::array<NAV::VectorNavSensor::BinaryGroupData, 9> NAV::VectorNavSensor::_binaryGroupAttitude{ {
1428 { /* 0 */ "VpeStatus", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_VPESTATUS,
1429 []() { ImGui::TextUnformatted("VPE Status bitfield\n\n");
1430 if (ImGui::BeginTable("VectorNavSatRawTooltipChan", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
1431 {
1432 ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
1433 ImGui::TableSetupColumn("Bit Offset", ImGuiTableColumnFlags_WidthFixed);
1434 ImGui::TableSetupColumn("Format", ImGuiTableColumnFlags_WidthFixed);
1435 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthFixed);
1436 ImGui::TableHeadersRow();
1437
1438 ImGui::TableNextColumn(); ImGui::TextUnformatted("AttitudeQuality");
1439 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1440 ImGui::TableNextColumn(); ImGui::TextUnformatted("2 bits");
1441 ImGui::TableNextColumn(); ImGui::TextUnformatted("Provides an indication of the quality of the attitude solution.\n0 - Excellent\n1 - Good\n2 - Bad\n3 - Not tracking");
1442
1443 ImGui::TableNextColumn(); ImGui::TextUnformatted("GyroSaturation");
1444 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1445 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1446 ImGui::TableNextColumn(); ImGui::TextUnformatted("At least one gyro axis is currently saturated.");
1447
1448 ImGui::TableNextColumn(); ImGui::TextUnformatted("GyroSaturationRecovery");
1449 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1450 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1451 ImGui::TableNextColumn(); ImGui::TextUnformatted("Filter is in the process of recovering from a gyro\nsaturation event.");
1452
1453 ImGui::TableNextColumn(); ImGui::TextUnformatted("MagDisturbance");
1454 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1455 ImGui::TableNextColumn(); ImGui::TextUnformatted("2 bits");
1456 ImGui::TableNextColumn(); ImGui::TextUnformatted("A magnetic DC disturbance has been detected.\n0 - No magnetic disturbance\n1 to 3 - Magnetic disturbance is present.");
1457
1458 ImGui::TableNextColumn(); ImGui::TextUnformatted("MagSaturation");
1459 ImGui::TableNextColumn(); ImGui::TextUnformatted("6");
1460 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1461 ImGui::TableNextColumn(); ImGui::TextUnformatted("At least one magnetometer axis is currently saturated.");
1462
1463 ImGui::TableNextColumn(); ImGui::TextUnformatted("AccDisturbance");
1464 ImGui::TableNextColumn(); ImGui::TextUnformatted("7");
1465 ImGui::TableNextColumn(); ImGui::TextUnformatted("2 bits");
1466 ImGui::TableNextColumn(); ImGui::TextUnformatted("A strong acceleration disturbance has been detected.\n0 - No acceleration disturbance.\n1 to 3 - Acceleration disturbance has been detected.");
1467
1468 ImGui::TableNextColumn(); ImGui::TextUnformatted("AccSaturation");
1469 ImGui::TableNextColumn(); ImGui::TextUnformatted("9");
1470 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1471 ImGui::TableNextColumn(); ImGui::TextUnformatted("At least one accelerometer axis is currently saturated.");
1472
1473 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved");
1474 ImGui::TableNextColumn(); ImGui::TextUnformatted("10");
1475 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1476 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved for internal use. May change state at run- time.");
1477
1478 ImGui::TableNextColumn(); ImGui::TextUnformatted("KnownMagDisturbance");
1479 ImGui::TableNextColumn(); ImGui::TextUnformatted("11");
1480 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1481 ImGui::TableNextColumn(); ImGui::TextUnformatted("A known magnetic disturbance has been reported by\nthe user and the magnetometer is currently tuned out.");
1482
1483 ImGui::TableNextColumn(); ImGui::TextUnformatted("KnownAccelDisturbance");
1484 ImGui::TableNextColumn(); ImGui::TextUnformatted("12");
1485 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1486 ImGui::TableNextColumn(); ImGui::TextUnformatted("A known acceleration disturbance has been reported by\nthe user and the accelerometer is currently tuned out.");
1487
1488 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved");
1489 ImGui::TableNextColumn(); ImGui::TextUnformatted("13");
1490 ImGui::TableNextColumn(); ImGui::TextUnformatted("3 bits");
1491 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved for future use.");
1492
1493 ImGui::EndTable();
1494 } },
1495 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN100_VN110; } },
1496 { /* 1 */ "YawPitchRoll", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YAWPITCHROLL,
1497 []() { ImGui::TextUnformatted("Yaw Pitch Roll\n\nThe estimated attitude Yaw, Pitch, and Roll angles measured in degrees. The attitude is given as a 3,2,1\nEuler angle sequence describing the body frame with respect to the local North East Down (NED) frame.\n\nYaw [+/- 180°]\nPitch [+/- 90°]\nRoll [+/- 180°]"); },
1498 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1499 [](VectorNavSensor* sensor, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::AttitudeGroup>(binaryField) & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YAWPITCHROLL) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS)
1500 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1501 { /* 2 */ "Quaternion", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_QUATERNION,
1502 []() { ImGui::TextUnformatted("Quaternion\n\nThe estimated attitude quaternion. The last term is the scalar value. The attitude is given as the body\nframe with respect to the local North East Down (NED) frame."); },
1503 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1504 [](VectorNavSensor* sensor, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::AttitudeGroup>(binaryField) & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_QUATERNION) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS)
1505 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1506 { /* 3 */ "DCM", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_DCM,
1507 []() { ImGui::TextUnformatted("Directional Cosine Matrix\n\nThe estimated attitude directional cosine matrix given in column major order. The DCM maps vectors\nfrom the North East Down (NED) frame into the body frame."); },
1508 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1509 [](VectorNavSensor* sensor, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::AttitudeGroup>(binaryField) & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_DCM) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS)
1510 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1511 { /* 4 */ "MagNed", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_MAGNED,
1512 []() { ImGui::TextUnformatted("Compensated magnetic (NED)\n\nThe current estimated magnetic field (Gauss), given in the North East Down (NED) frame. The current\nattitude solution is used to map the measurement from the measured body frame to the inertial (NED)\nframe. This measurement is compensated by both the static calibration (individual factory calibration\nstored in flash), and the dynamic calibration such as the user or onboard Hard/Soft Iron compensation\nregisters."); },
1513 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1514 [](VectorNavSensor* sensor, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::AttitudeGroup>(binaryField) & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_MAGNED) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS)
1515 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1516 { /* 5 */ "AccelNed", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_ACCELNED,
1517 []() { ImGui::TextUnformatted("Compensated acceleration (NED)\n\nThe estimated acceleration (with gravity) reported in m/s^2, given in the North East Down (NED) frame.\nThe acceleration measurement has been bias compensated by the onboard INS filter. This measurement\nis attitude dependent, since the attitude is used to map the measurement from the body frame into the\ninertial (NED) frame. If the device is stationary and the INS filter is tracking, the measurement should be\nnominally equivalent to the gravity reference vector in the inertial frame (NED)."); },
1518 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1519 [](VectorNavSensor* sensor, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::AttitudeGroup>(binaryField) & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_ACCELNED) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS)
1520 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1521 { /* 6 */ "LinearAccelBody", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELBODY,
1522 []() { ImGui::TextUnformatted("Compensated linear acceleration (no gravity)\n\nThe estimated linear acceleration (without gravity) reported in m/s^2, and given in the body frame. The\nacceleration measurement has been bias compensated by the onboard INS filter, and the gravity\ncomponent has been removed using the current gravity reference vector model. This measurement is\nattitude dependent, since the attitude solution is required to map the gravity reference vector (known in\nthe inertial NED frame), into the body frame so that it can be removed from the measurement. If the\ndevice is stationary and the onboard INS filter is tracking, the measurement nominally will read 0 in all\nthree axes."); },
1523 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1524 [](VectorNavSensor* sensor, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::AttitudeGroup>(binaryField) & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELBODY) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS)
1525 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1526 { /* 7 */ "LinearAccelNed", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELNED,
1527 []() { ImGui::TextUnformatted("Compensated linear acceleration (no gravity) (NED)\n\nThe estimated linear acceleration (without gravity) reported in m/s^2, and given in the North East Down\n(NED) frame. This measurement is attitude dependent as the attitude solution is used to map the\nmeasurement from the body frame into the inertial (NED) frame. This acceleration measurement has\nbeen bias compensated by the onboard INS filter, and the gravity component has been removed using the\ncurrent gravity reference vector estimate. If the device is stationary and the onboard INS filter is tracking,\nthe measurement nominally will read 0 in all three axes."); },
1528 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1529 [](VectorNavSensor* sensor, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::AttitudeGroup>(binaryField) & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELNED) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS)
1530 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1531 { /* 8 */ "YprU", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YPRU,
1532 []() { ImGui::TextUnformatted("Yaw Pitch Roll uncertainty\n\nThe estimated attitude (Yaw, Pitch, Roll) uncertainty (1 Sigma), reported in degrees.\n\nThe estimated attitude (YprU) field is not valid when the INS Scenario mode in the INS Basic\nConfiguration register is set to AHRS mode. See the INS Basic Configuration Register in the INS\nsection for more details."); },
1533 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1534 [](VectorNavSensor* sensor, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::AttitudeGroup>(binaryField) & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YPRU) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS)
1535 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1536 } };
1537
1538 const std::array<NAV::VectorNavSensor::BinaryGroupData, 11> NAV::VectorNavSensor::_binaryGroupINS{ {
1539 { /* 0 */ "InsStatus", vn::protocol::uart::InsGroup::INSGROUP_INSSTATUS,
1540 []() { ImGui::TextUnformatted("Ins Status bitfield:");
1541 if (ImGui::BeginTable("VectorNavInsStatusTooltip", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
1542 {
1543 ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
1544 ImGui::TableSetupColumn("Bit Offset", ImGuiTableColumnFlags_WidthFixed);
1545 ImGui::TableSetupColumn("Format", ImGuiTableColumnFlags_WidthFixed);
1546 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthFixed);
1547 ImGui::TableHeadersRow();
1548
1549 ImGui::TableNextColumn(); ImGui::TextUnformatted("Mode");
1550 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1551 ImGui::TableNextColumn(); ImGui::TextUnformatted("2 bits");
1552 ImGui::TableNextColumn(); ImGui::TextUnformatted("Indicates the current mode of the INS filter.\n\n0 = Not tracking. GNSS Compass is initializing. Output heading is based on\nmagnetometer measurements.\n1 = Aligning.\nINS Filter is dynamically aligning.\nFor a stationary startup: GNSS Compass has initialized and INS Filter is\naligning from the magnetic heading to the GNSS Compass heading.\nFor a dynamic startup: INS Filter has initialized and is dynamically aligning to\nTrue North heading.\nIn operation, if the INS Filter drops from INS Mode 2 back down to 1, the\nattitude uncertainty has increased above 2 degrees.\n2 = Tracking. The INS Filter is tracking and operating within specification.\n3 = Loss of GNSS. A GNSS outage has lasted more than 45 seconds. The INS\nFilter will no longer update the position and velocity outputs, but the attitude\nremains valid.");
1553
1554 ImGui::TableNextColumn(); ImGui::TextUnformatted("GpsFix");
1555 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1556 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1557 ImGui::TableNextColumn(); ImGui::TextUnformatted("Indicates whether the GNSS has a proper fix.");
1558
1559 ImGui::TableNextColumn(); ImGui::TextUnformatted("Error");
1560 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1561 ImGui::TableNextColumn(); ImGui::TextUnformatted("4 bits");
1562 ImGui::TableNextColumn(); ImGui::TextUnformatted("Sensor measurement error code. See table below.\n0 = No errors detected.");
1563
1564 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved");
1565 ImGui::TableNextColumn(); ImGui::TextUnformatted("7");
1566 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1567 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved for internal use. May toggle state during runtime and should be ignored.");
1568
1569 ImGui::TableNextColumn(); ImGui::TextUnformatted("GpsHeadingIns");
1570 ImGui::TableNextColumn(); ImGui::TextUnformatted("8");
1571 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1572 ImGui::TableNextColumn(); ImGui::TextUnformatted("In stationary operation, if set the INS Filter has fully aligned to the GNSS\nCompass solution.\nIn dynamic operation, the GNSS Compass solution is currently aiding the INS\nFilter heading solution.");
1573
1574 ImGui::TableNextColumn(); ImGui::TextUnformatted("GpsCompass");
1575 ImGui::TableNextColumn(); ImGui::TextUnformatted("9");
1576 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1577 ImGui::TableNextColumn(); ImGui::TextUnformatted("Indicates if the GNSS compass is operational and reporting a heading\nsolution.");
1578
1579 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved");
1580 ImGui::TableNextColumn(); ImGui::TextUnformatted("10");
1581 ImGui::TableNextColumn(); ImGui::TextUnformatted("6 bits");
1582 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved for internal use. These bits will toggle state and should be ignored.");
1583
1584 ImGui::EndTable();
1585 }
1586 ImGui::TextUnformatted("\nError Bitfield:");
1587 if (ImGui::BeginTable("VectorNavInsStatusTooltipError", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1588 {
1589 ImGui::TableSetupColumn("Name");
1590 ImGui::TableSetupColumn("Description");
1591 ImGui::TableHeadersRow();
1592
1593 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved");
1594 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved for future use and not currently used.");
1595
1596 ImGui::TableNextColumn(); ImGui::TextUnformatted("IMU Error");
1597 ImGui::TableNextColumn(); ImGui::TextUnformatted("High if IMU communication error is detected.");
1598
1599 ImGui::TableNextColumn(); ImGui::TextUnformatted("Mag/Pres Error");
1600 ImGui::TableNextColumn(); ImGui::TextUnformatted("High if Magnetometer or Pressure sensor error is detected.");
1601
1602 ImGui::TableNextColumn(); ImGui::TextUnformatted("GNSS Error");
1603 ImGui::TableNextColumn(); ImGui::TextUnformatted("High if GNSS communication error is detected.");
1604
1605 ImGui::EndTable();
1606 } },
1607 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1608 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::InsGroup>(binaryField) & vn::protocol::uart::InsGroup::INSGROUP_INSSTATUS) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW); } },
1609 { /* 1 */ "PosLla", vn::protocol::uart::InsGroup::INSGROUP_POSLLA,
1610 []() { ImGui::TextUnformatted("Ins Position (latitude, longitude, altitude)\n\nThe estimated position given as latitude, longitude, and altitude given in [deg, deg, m] respectively."); },
1611 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1612 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::InsGroup>(binaryField) & vn::protocol::uart::InsGroup::INSGROUP_POSLLA) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW); } },
1613 { /* 2 */ "PosEcef", vn::protocol::uart::InsGroup::INSGROUP_POSECEF,
1614 []() { ImGui::TextUnformatted("Ins Position (ECEF)\n\nThe estimated position given in the Earth centered Earth fixed (ECEF) frame, reported in meters."); },
1615 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1616 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::InsGroup>(binaryField) & vn::protocol::uart::InsGroup::INSGROUP_POSECEF) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW); } },
1617 { /* 3 */ "VelBody", vn::protocol::uart::InsGroup::INSGROUP_VELBODY,
1618 []() { ImGui::TextUnformatted("Ins Velocity (Body)\n\nThe estimated velocity in the body frame, given in m/s."); },
1619 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1620 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::InsGroup>(binaryField) & vn::protocol::uart::InsGroup::INSGROUP_VELBODY) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW); } },
1621 { /* 4 */ "VelNed", vn::protocol::uart::InsGroup::INSGROUP_VELNED,
1622 []() { ImGui::TextUnformatted("Ins Velocity (NED)\n\nThe estimated velocity in the North East Down (NED) frame, given in m/s."); },
1623 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1624 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::InsGroup>(binaryField) & vn::protocol::uart::InsGroup::INSGROUP_VELNED) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW); } },
1625 { /* 5 */ "VelEcef", vn::protocol::uart::InsGroup::INSGROUP_VELECEF,
1626 []() { ImGui::TextUnformatted("Ins Velocity (ECEF)\n\nThe estimated velocity in the Earth centered Earth fixed (ECEF) frame, given in m/s."); },
1627 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1628 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::InsGroup>(binaryField) & vn::protocol::uart::InsGroup::INSGROUP_VELECEF) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW); } },
1629 { /* 6 */ "MagEcef", vn::protocol::uart::InsGroup::INSGROUP_MAGECEF,
1630 []() { ImGui::TextUnformatted("Compensated magnetic (ECEF)\n\nThe compensated magnetic measurement in the Earth centered Earth fixed (ECEF) frame, given in Gauss."); },
1631 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1632 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::InsGroup>(binaryField) & vn::protocol::uart::InsGroup::INSGROUP_MAGECEF) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW); } },
1633 { /* 7 */ "AccelEcef", vn::protocol::uart::InsGroup::INSGROUP_ACCELECEF,
1634 []() { ImGui::TextUnformatted("Compensated acceleration (ECEF)\n\nThe estimated acceleration (with gravity) reported in m/s^2, given in the Earth centered Earth fixed (ECEF)\nframe. The acceleration measurement has been bias compensated by the onboard INS filter. This\nmeasurement is attitude dependent, since the attitude is used to map the measurement from the body frame\ninto the inertial (ECEF) frame. If the device is stationary and the INS filter is tracking, the measurement\nshould be nominally equivalent to the gravity reference vector in the inertial frame (ECEF)."); },
1635 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1636 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::InsGroup>(binaryField) & vn::protocol::uart::InsGroup::INSGROUP_ACCELECEF) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW); } },
1637 { /* 8 */ "LinearAccelEcef", vn::protocol::uart::InsGroup::INSGROUP_LINEARACCELECEF,
1638 []() { ImGui::TextUnformatted("Compensated linear acceleration (no gravity) (ECEF)\n\nThe estimated linear acceleration (without gravity) reported in m/s^2, and given in the Earth centered Earth\nfixed (ECEF) frame. This measurement is attitude dependent as the attitude solution is used to map the\nmeasurement from the body frame into the inertial (ECEF) frame. This acceleration measurement has been\nbias compensated by the onboard INS filter, and the gravity component has been removed using the current\ngravity reference vector estimate. If the device is stationary and the onboard INS filter is tracking, the\nmeasurement will nominally read 0 in all three axes."); },
1639 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1640 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::InsGroup>(binaryField) & vn::protocol::uart::InsGroup::INSGROUP_LINEARACCELECEF) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW); } },
1641 { /* 9 */ "PosU", vn::protocol::uart::InsGroup::INSGROUP_POSU,
1642 []() { ImGui::TextUnformatted("Ins Position Uncertainty\n\nThe estimated uncertainty (1 Sigma) in the current position estimate, given in meters."); },
1643 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1644 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::InsGroup>(binaryField) & vn::protocol::uart::InsGroup::INSGROUP_POSU) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW); } },
1645 { /* 10 */ "VelU", vn::protocol::uart::InsGroup::INSGROUP_VELU,
1646 []() { ImGui::TextUnformatted("Ins Velocity Uncertainty\n\nThe estimated uncertainty (1 Sigma) in the current velocity estimate, given in m/s."); },
1647 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1648 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) { (static_cast<vn::protocol::uart::InsGroup>(binaryField) & vn::protocol::uart::InsGroup::INSGROUP_VELU) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW); } },
1649 } };
1650
1651 // NOLINTEND
1652
1653 112 NAV::VectorNavSensor::VectorNavSensor()
1654
4/8
✓ Branch 1 taken 112 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 112 times.
✗ Branch 5 not taken.
✓ Branch 9 taken 112 times.
✗ Branch 10 not taken.
✓ Branch 15 taken 112 times.
✗ Branch 16 not taken.
112 : Imu(typeStatic())
1655 {
1656 LOG_TRACE("{}: called", name);
1657
1658 112 _onlyRealTime = true;
1659 112 _hasConfig = true;
1660 112 _guiConfigDefaultWindowSize = { 954, 783 };
1661
1662
4/8
✓ Branch 2 taken 112 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 112 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 112 times.
✓ Branch 10 taken 112 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
448 nm::CreateOutputPin(this, "Ascii Output", Pin::Type::Flow, { NAV::StringObs::type() });
1663
4/8
✓ Branch 2 taken 112 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 112 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 112 times.
✓ Branch 10 taken 112 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
448 nm::CreateOutputPin(this, "Binary Output 1", Pin::Type::Flow, { NAV::VectorNavBinaryOutput::type() });
1664
4/8
✓ Branch 2 taken 112 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 112 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 112 times.
✓ Branch 10 taken 112 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
448 nm::CreateOutputPin(this, "Binary Output 2", Pin::Type::Flow, { NAV::VectorNavBinaryOutput::type() });
1665
4/8
✓ Branch 2 taken 112 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 112 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 112 times.
✓ Branch 10 taken 112 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
448 nm::CreateOutputPin(this, "Binary Output 3", Pin::Type::Flow, { NAV::VectorNavBinaryOutput::type() });
1666
1667 _dividerFrequency = []() {
1668 112 std::map<int, int, std::greater<>> divFreq;
1669
2/2
✓ Branch 0 taken 89600 times.
✓ Branch 1 taken 112 times.
89712 for (int freq = 1; freq <= IMU_DEFAULT_FREQUENCY; freq++)
1670 {
1671 89600 int divider = static_cast<int>(std::round(IMU_DEFAULT_FREQUENCY / freq));
1672
1673
1/2
✓ Branch 1 taken 89600 times.
✗ Branch 2 not taken.
89600 if (!divFreq.contains(divider)
1674
7/8
✓ Branch 0 taken 83328 times.
✓ Branch 1 taken 6272 times.
✓ Branch 4 taken 83328 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 51296 times.
✓ Branch 8 taken 32032 times.
✓ Branch 9 taken 57568 times.
✓ Branch 10 taken 32032 times.
89600 || std::abs(divider - IMU_DEFAULT_FREQUENCY / freq) < std::abs(divider - IMU_DEFAULT_FREQUENCY / divFreq.at(divider)))
1675 {
1676
1/2
✓ Branch 1 taken 57568 times.
✗ Branch 2 not taken.
57568 divFreq[divider] = freq;
1677 }
1678 }
1679 112 std::vector<uint16_t> divs;
1680 112 std::vector<std::string> freqs;
1681
2/2
✓ Branch 6 taken 6272 times.
✓ Branch 7 taken 112 times.
6384 for (auto& [divider, freq] : divFreq)
1682 {
1683
1/2
✓ Branch 1 taken 6272 times.
✗ Branch 2 not taken.
6272 divs.push_back(static_cast<uint16_t>(divider));
1684
2/4
✓ Branch 2 taken 6272 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 6272 times.
✗ Branch 6 not taken.
6272 freqs.push_back(std::to_string(freq) + " Hz");
1685 LOG_DATA("VectorNavSensor: RateDivisor {} = {}", divs.back(), freqs.back());
1686 }
1687
1/2
✓ Branch 1 taken 112 times.
✗ Branch 2 not taken.
224 return std::make_pair(divs, freqs);
1688
1/2
✓ Branch 1 taken 112 times.
✗ Branch 2 not taken.
224 }();
1689 560 }
1690
1691 224 NAV::VectorNavSensor::~VectorNavSensor()
1692 {
1693 LOG_TRACE("{}: called", nameId());
1694 224 }
1695
1696 224 std::string NAV::VectorNavSensor::typeStatic()
1697 {
1698
1/2
✓ Branch 1 taken 224 times.
✗ Branch 2 not taken.
448 return "VectorNavSensor";
1699 }
1700
1701 std::string NAV::VectorNavSensor::type() const
1702 {
1703 return typeStatic();
1704 }
1705
1706 112 std::string NAV::VectorNavSensor::category()
1707 {
1708
1/2
✓ Branch 1 taken 112 times.
✗ Branch 2 not taken.
224 return "Data Provider";
1709 }
1710
1711 void NAV::VectorNavSensor::guiConfig()
1712 {
1713 if (auto sensorModel = static_cast<int>(_sensorModel);
1714 ImGui::Combo("Sensor", &sensorModel, "VN-100 / VN-110\0VN-310\0\0"))
1715 {
1716 _sensorModel = static_cast<decltype(_sensorModel)>(sensorModel);
1717 LOG_DEBUG("{}: Sensor changed to {}", nameId(), fmt::underlying(_sensorModel));
1718 flow::ApplyChanges();
1719 doDeinitialize();
1720
1721 if (_sensorModel != VectorNavModel::VN310
1722 && (_asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNGPS
1723 || _asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNGPE
1724 || _asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNINS
1725 || _asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNINE
1726 || _asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNISL
1727 || _asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNISE
1728 || _asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNG2S
1729 || _asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNG2E))
1730 {
1731 _asyncDataOutputType = vn::protocol::uart::AsciiAsync::VNOFF;
1732 }
1733
1734 if (_sensorModel == VectorNavModel::VN310)
1735 {
1736 _communicationProtocolControlRegister.spiCount = vn::protocol::uart::CountMode::COUNTMODE_NONE;
1737 _communicationProtocolControlRegister.spiStatus = vn::protocol::uart::StatusMode::STATUSMODE_OFF;
1738 _communicationProtocolControlRegister.spiChecksum = vn::protocol::uart::ChecksumMode::CHECKSUMMODE_OFF;
1739 }
1740
1741 for (auto& binaryOutput : _binaryOutputRegister)
1742 {
1743 // for (const auto& item : _binaryGroupCommon)
1744 // {
1745 // if (!item.isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.commonField)))
1746 // {
1747 // binaryOutput.commonField &= ~vn::protocol::uart::CommonGroup(item.flagsValue);
1748 // }
1749 // }
1750 for (size_t i = 0; i < _binaryGroupTime.size(); i++)
1751 {
1752 const auto& item = _binaryGroupTime.at(i);
1753
1754 if (i == 0 /* TimeStartup */ || i == 9 /* TimeStatus */) { continue; } // Do not reset the field
1755
1756 // Check the fields if conditions are met
1757 if ((i == 2 /* GpsTow */ || i == 3 /* GpsWeek */)
1758 && _sensorModel == VectorNavModel::VN310
1759 && binaryOutput.timeField & vn::protocol::uart::TIMEGROUP_TIMESTARTUP
1760 && !_binaryGroupTime.at(0).isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.timeField)))
1761 {
1762 binaryOutput.timeField |= vn::protocol::uart::TimeGroup(item.flagsValue);
1763 continue;
1764 }
1765
1766 if (!item.isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.timeField)))
1767 {
1768 binaryOutput.timeField &= ~vn::protocol::uart::TimeGroup(item.flagsValue);
1769 }
1770 }
1771 for (const auto& item : _binaryGroupIMU)
1772 {
1773 if (!item.isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.imuField)))
1774 {
1775 binaryOutput.imuField &= ~vn::protocol::uart::ImuGroup(item.flagsValue);
1776 }
1777 }
1778 for (const auto& item : _binaryGroupGNSS)
1779 {
1780 if (!item.isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.gpsField)))
1781 {
1782 binaryOutput.gpsField &= ~vn::protocol::uart::GpsGroup(item.flagsValue);
1783 }
1784 if (!item.isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.gps2Field)))
1785 {
1786 binaryOutput.gps2Field &= ~vn::protocol::uart::GpsGroup(item.flagsValue);
1787 }
1788 }
1789 for (const auto& item : _binaryGroupAttitude)
1790 {
1791 if (!item.isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.attitudeField)))
1792 {
1793 binaryOutput.attitudeField &= ~vn::protocol::uart::AttitudeGroup(item.flagsValue);
1794 }
1795 }
1796 for (const auto& item : _binaryGroupINS)
1797 {
1798 if (!item.isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.insField)))
1799 {
1800 binaryOutput.insField &= ~vn::protocol::uart::InsGroup(item.flagsValue);
1801 }
1802 }
1803 }
1804 }
1805
1806 if (ImGui::InputTextWithHint("SensorPort", _connectedSensorPort.empty() ? "/dev/ttyUSB0" : _connectedSensorPort.c_str(), &_sensorPort))
1807 {
1808 LOG_DEBUG("{}: SensorPort changed to {}", nameId(), _sensorPort);
1809 flow::ApplyChanges();
1810 if (isInitialized())
1811 {
1812 doDeinitialize();
1813 }
1814 }
1815 ImGui::SameLine();
1816 gui::widgets::HelpMarker("COM port where the sensor is attached to\n"
1817 "- \"COM1\" (Windows format for physical and virtual (USB) serial port)\n"
1818 "- \"/dev/ttyS1\" (Linux format for physical serial port)\n"
1819 "- \"/dev/ttyUSB0\" (Linux format for virtual (USB) serial port)\n"
1820 "- \"/dev/tty.usbserial-FTXXXXXX\" (Mac OS X format for virtual (USB) serial port)\n"
1821 "- \"/dev/ttyS0\" (CYGWIN format. Usually the Windows COM port number minus 1. This would connect to COM1)");
1822
1823 bool isNodeInitialized = isInitialized();
1824 if (!isNodeInitialized)
1825 {
1826 ImGui::BeginDisabled();
1827 }
1828 if (ImGui::Button("Write settings"))
1829 {
1830 try
1831 {
1832 _vs.writeSettings();
1833 }
1834 catch (const std::exception& e)
1835 {
1836 LOG_ERROR("{}: Write settings threw an exception: {}", nameId(), e.what());
1837 }
1838 }
1839 if (!isNodeInitialized)
1840 {
1841 ImGui::EndDisabled();
1842 }
1843 if (ImGui::IsItemHovered())
1844 {
1845 ImGui::SetTooltip("This command will write the current register settings into non-volatile memory. Once the settings are stored\n"
1846 "in non-volatile (Flash) memory, the VN-310E module can be power cycled or reset, and the register will be\n"
1847 "reloaded from non-volatile memory.\n\n"
1848 "Due to limitations in the flash write speed the write settings command takes ~ 500ms to\n"
1849 "complete. Any commands that are sent to the sensor during this time will be responded to after\n"
1850 "the operation is complete.\n\n"
1851 "The sensor must be stationary when issuing a Write Settings Command otherwise a Reset\n"
1852 "command must also be issued to prevent the Kalman Filter from diverging during the write\n"
1853 "settings process.\n\n"
1854 "A write settings command is automatically send after initializing the node.");
1855 }
1856 ImGui::SameLine();
1857 if (!isNodeInitialized)
1858 {
1859 ImGui::BeginDisabled();
1860 }
1861 if (ImGui::Button("Restore factory settings"))
1862 {
1863 try
1864 {
1865 _vs.restoreFactorySettings();
1866 }
1867 catch (const std::exception& e)
1868 {
1869 LOG_ERROR("{}: Restore factory settings threw an exception: {}", nameId(), e.what());
1870 }
1871 }
1872 if (!isNodeInitialized)
1873 {
1874 ImGui::EndDisabled();
1875 }
1876 if (ImGui::IsItemHovered())
1877 {
1878 ImGui::SetTooltip("This command will restore the VN-310E module's factory default settings and will reset the module. There\n"
1879 "are no parameters for this command. The module will respond to this command before restoring the factory\n"
1880 "settings.");
1881 }
1882 ImGui::SameLine();
1883 if (!isNodeInitialized)
1884 {
1885 ImGui::BeginDisabled();
1886 }
1887 if (ImGui::Button("Reset sensor"))
1888 {
1889 try
1890 {
1891 _vs.reset();
1892 }
1893 catch (const std::exception& e)
1894 {
1895 LOG_ERROR("{}: Resetting threw an exception: {}", nameId(), e.what());
1896 }
1897 }
1898 if (!isNodeInitialized)
1899 {
1900 ImGui::EndDisabled();
1901 }
1902 if (ImGui::IsItemHovered())
1903 {
1904 ImGui::SetTooltip("This command will reset the module. There are no parameters required for this command. The module will\n"
1905 "first respond to the command and will then perform a reset. Upon a reset all registers will be reloaded with\n"
1906 "the values saved in non-volatile memory. If no values are stored in non-volatile memory, the device will default\n"
1907 "to factory settings. Also upon reset the VN-310E will re-initialize its Kalman filter, thus the filter will take a\n"
1908 "few seconds to completely converge on the correct attitude and correct for gyro bias.");
1909 }
1910
1911 // ###########################################################################################################
1912 // SYSTEM MODULE
1913 // ###########################################################################################################
1914
1915 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
1916 if (ImGui::CollapsingHeader(fmt::format("System Module##{}", size_t(id)).c_str()))
1917 {
1918 // ------------------------------------------- Serial Baud Rate ----------------------------------------------
1919
1920 std::array<const char*, 10> items = { "Fastest", "9600", "19200", "38400", "57600", "115200", "128000", "230400", "460800", "921600" };
1921 if (ImGui::Combo("Baudrate", &_selectedBaudrate, items.data(), items.size()))
1922 {
1923 LOG_DEBUG("{}: Baudrate changed to {}", nameId(), sensorBaudrate());
1924 flow::ApplyChanges();
1925 doDeinitialize();
1926 }
1927
1928 if (ImGui::TreeNode(fmt::format("Async Ascii Output##{}", size_t(id)).c_str()))
1929 {
1930 std::vector<std::pair<vn::protocol::uart::AsciiAsync, const char*>> asciiAsyncItems{
1931 { vn::protocol::uart::AsciiAsync::VNOFF, "Asynchronous output turned off" },
1932 { vn::protocol::uart::AsciiAsync::VNYPR, "Yaw, Pitch, Roll" },
1933 { vn::protocol::uart::AsciiAsync::VNQTN, "Quaternion" },
1934 { vn::protocol::uart::AsciiAsync::VNQMR, "Quaternion, Magnetic, Acceleration and Angular Rates" },
1935 { vn::protocol::uart::AsciiAsync::VNMAG, "Magnetic Measurements" },
1936 { vn::protocol::uart::AsciiAsync::VNACC, "Acceleration Measurements" },
1937 { vn::protocol::uart::AsciiAsync::VNGYR, "Angular Rate Measurements" },
1938 { vn::protocol::uart::AsciiAsync::VNMAR, "Magnetic, Acceleration, and Angular Rate Measurements" },
1939 { vn::protocol::uart::AsciiAsync::VNYMR, "Yaw, Pitch, Roll, Magnetic, Acceleration, and Angular Rate Measurements" },
1940 { vn::protocol::uart::AsciiAsync::VNYBA, "Yaw, Pitch, Roll, Body True Acceleration, and Angular Rates" },
1941 { vn::protocol::uart::AsciiAsync::VNYIA, "Yaw, Pitch, Roll, Inertial True Acceleration, and Angular Rates" },
1942 { vn::protocol::uart::AsciiAsync::VNIMU, "IMU Measurements" }
1943 };
1944 if (_sensorModel == VectorNavModel::VN310)
1945 {
1946 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNGPS, "GNSS LLA");
1947 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNGPE, "GNSS ECEF");
1948 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNINS, "INS LLA");
1949 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNINE, "INS ECEF");
1950 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNISL, "INS LLA 2");
1951 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNISE, "INS ECEF 2");
1952 }
1953 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNDTV, "Delta theta and delta velocity");
1954 if (_sensorModel == VectorNavModel::VN310)
1955 {
1956 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNG2S, "GNSS2 LLA");
1957 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNG2E, "GNSS2 ECEF");
1958 }
1959
1960 if (ImGui::BeginCombo(fmt::format("Async Ascii Output Type##{}", size_t(id)).c_str(), vn::protocol::uart::str(_asyncDataOutputType).c_str()))
1961 {
1962 for (const auto& asciiAsyncItem : asciiAsyncItems)
1963 {
1964 const bool isSelected = (_asyncDataOutputType == asciiAsyncItem.first);
1965 if (ImGui::Selectable(vn::protocol::uart::str(asciiAsyncItem.first).c_str(), isSelected))
1966 {
1967 _asyncDataOutputType = asciiAsyncItem.first;
1968 LOG_DEBUG("{}: _asyncDataOutputType changed to {}", nameId(), vn::protocol::uart::str(_asyncDataOutputType));
1969 flow::ApplyChanges();
1970 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
1971 {
1972 try
1973 {
1974 _vs.writeAsyncDataOutputType(_asyncDataOutputType);
1975 }
1976 catch (const std::exception& e)
1977 {
1978 LOG_ERROR("{}: Could not configure the asyncDataOutputType: {}", nameId(), e.what());
1979 doDeinitialize();
1980 }
1981 }
1982 else
1983 {
1984 doDeinitialize();
1985 }
1986 }
1987 if (ImGui::IsItemHovered())
1988 {
1989 ImGui::BeginTooltip();
1990 ImGui::TextUnformatted(asciiAsyncItem.second);
1991 ImGui::EndTooltip();
1992 }
1993
1994 // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
1995 if (isSelected)
1996 {
1997 ImGui::SetItemDefaultFocus();
1998 }
1999 }
2000 ImGui::EndCombo();
2001 }
2002 ImGui::SameLine();
2003 gui::widgets::HelpMarker("This register controls the type of data that will be asynchronously outputted by the module. With this "
2004 "register, the user can specify which data register will be automatically outputted when it gets updated "
2005 "with a new reading. The table below lists which registers can be set to asynchronously output, the value "
2006 "to specify which register to output, and the header of the asynchronous data packet. Asynchronous data "
2007 "output can be disabled by setting this register to zero. The asynchronous data output will be sent out "
2008 "automatically at a frequency specified by the Async Data Output Frequency Register.");
2009
2010 if (ImGui::SliderInt(fmt::format("Async Ascii Output Frequency##{}", size_t(id)).c_str(),
2011 &_asyncDataOutputFrequencySelected,
2012 0, _possibleAsyncDataOutputFrequency.size() - 1,
2013 fmt::format("{} Hz", _possibleAsyncDataOutputFrequency.at(static_cast<size_t>(_asyncDataOutputFrequencySelected))).c_str()))
2014 {
2015 _asyncDataOutputFrequency = static_cast<uint32_t>(_possibleAsyncDataOutputFrequency.at(static_cast<size_t>(_asyncDataOutputFrequencySelected)));
2016 LOG_DEBUG("{}: asyncDataOutputFrequency changed to {} Hz", nameId(), _asyncDataOutputFrequency);
2017 flow::ApplyChanges();
2018 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2019 {
2020 try
2021 {
2022 _vs.writeAsyncDataOutputFrequency(_asyncDataOutputFrequency);
2023 }
2024 catch (const std::exception& e)
2025 {
2026 LOG_ERROR("{}: Could not configure the asyncDataOutputFrequency: {}", nameId(), e.what());
2027 doDeinitialize();
2028 }
2029 }
2030 else
2031 {
2032 doDeinitialize();
2033 }
2034 }
2035 ImGui::SameLine();
2036 gui::widgets::HelpMarker("Asynchronous data output frequency.\nThe ADOF will be changed for the active serial port.");
2037
2038 if (ImGui::DragInt(fmt::format("Async Ascii Output buffer size##{}", size_t(id)).c_str(), &_asciiOutputBufferSize, 1.0F, 0, INT32_MAX / 2))
2039 {
2040 _asciiOutputBuffer.resize(static_cast<size_t>(_asciiOutputBufferSize));
2041 LOG_DEBUG("{}: asciiOutputBufferSize changed to {}", nameId(), _asciiOutputBufferSize);
2042 flow::ApplyChanges();
2043 }
2044
2045 std::string messages;
2046 for (const auto& msg : _asciiOutputBuffer)
2047 {
2048 messages.append(msg);
2049 }
2050 ImGui::TextUnformatted("Async Ascii Messages:");
2051 ImGui::BeginChild(fmt::format("##Ascii Mesages {}", size_t(id)).c_str(), ImVec2(0, 300), true);
2052 ImGui::PushTextWrapPos();
2053 ImGui::TextUnformatted(messages.c_str());
2054 ImGui::PopTextWrapPos();
2055 ImGui::EndChild();
2056
2057 ImGui::TreePop();
2058 }
2059
2060 if (ImGui::TreeNode(fmt::format("Synchronization Control##{}", size_t(id)).c_str()))
2061 {
2062 ImGui::TextUnformatted("Contains parameters which allow the timing of the VN-310E to be\n"
2063 "synchronized with external devices.");
2064
2065 if (ImGui::Checkbox(fmt::format("Show SyncIn Pin##{}", size_t(id)).c_str(), &_syncInPin))
2066 {
2067 LOG_DEBUG("{}: syncInPin changed to {}", nameId(), _syncInPin);
2068 if (_syncInPin)
2069 {
2070 _synchronizationControlRegister.syncInMode = vn::protocol::uart::SyncInMode::SYNCINMODE_COUNT;
2071 LOG_DEBUG("{}: synchronizationControlRegister.syncInMode changed to {}", nameId(), vn::protocol::uart::str(_synchronizationControlRegister.syncInMode));
2072 }
2073 flow::ApplyChanges();
2074 if (_syncInPin && inputPins.empty())
2075 {
2076 nm::CreateInputPin(this, "SyncIn", Pin::Type::Object, { "TimeSync" });
2077 }
2078 else if (!_syncInPin && !inputPins.empty())
2079 {
2080 nm::DeleteInputPin(inputPins.front());
2081 }
2082 }
2083
2084 static constexpr std::array<std::pair<vn::protocol::uart::SyncInMode, const char*>, 4> synchronizationControlSyncInModes = {
2085 { { vn::protocol::uart::SyncInMode::SYNCINMODE_COUNT, "Count number of trigger events on SYNC_IN" },
2086 { vn::protocol::uart::SyncInMode::SYNCINMODE_IMU, "Start IMU sampling on trigger of SYNC_IN" },
2087 { vn::protocol::uart::SyncInMode::SYNCINMODE_ASYNC, "Output asynchronous message on trigger of SYNC_IN" },
2088 { vn::protocol::uart::SyncInMode::SYNCINMODE_ASYNC3, "Output asynchronous or binary messages configured with a rate of 0 to output on trigger of SYNC_IN\n\n"
2089 "In ASYNC3 mode messages configured with an output rate = 0 are output each time the appropriate\n"
2090 "transistion edge of the SyncIn pin is captured according to the edge settings in the SyncInEdge field.\n"
2091 "Messages configured with output rate > 0 are not affected by the SyncIn pulse. This applies to the ASCII\n"
2092 "Async message set by the Async Data Output Register, the user configurate binary output messages set\n"
2093 "by the Binary Output Registers, as well as the NMEA messages configured by the NMEA Output Registers." } }
2094 };
2095 if (_syncInPin)
2096 {
2097 ImGui::BeginDisabled();
2098 }
2099
2100 if (ImGui::BeginCombo(fmt::format("SyncIn Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_synchronizationControlRegister.syncInMode).c_str()))
2101 {
2102 for (const auto& synchronizationControlSyncInMode : synchronizationControlSyncInModes)
2103 {
2104 const bool isSelected = (_synchronizationControlRegister.syncInMode == synchronizationControlSyncInMode.first);
2105 if (ImGui::Selectable(vn::protocol::uart::str(synchronizationControlSyncInMode.first).c_str(), isSelected))
2106 {
2107 _synchronizationControlRegister.syncInMode = synchronizationControlSyncInMode.first;
2108 LOG_DEBUG("{}: synchronizationControlRegister.syncInMode changed to {}", nameId(), vn::protocol::uart::str(_synchronizationControlRegister.syncInMode));
2109 flow::ApplyChanges();
2110 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2111 {
2112 try
2113 {
2114 _vs.writeSynchronizationControl(_synchronizationControlRegister);
2115 }
2116 catch (const std::exception& e)
2117 {
2118 LOG_ERROR("{}: Could not configure the synchronizationControlRegister: {}", nameId(), e.what());
2119 doDeinitialize();
2120 }
2121 }
2122 else
2123 {
2124 doDeinitialize();
2125 }
2126 }
2127 if (ImGui::IsItemHovered())
2128 {
2129 ImGui::BeginTooltip();
2130 ImGui::TextUnformatted(synchronizationControlSyncInMode.second);
2131 ImGui::EndTooltip();
2132 }
2133
2134 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2135 {
2136 ImGui::SetItemDefaultFocus();
2137 }
2138 }
2139 ImGui::EndCombo();
2140 }
2141 ImGui::SameLine();
2142 gui::widgets::HelpMarker("The SyncInMode register controls the behavior of the SyncIn event. If the mode is set to COUNT then the "
2143 "internal clock will be used to control the IMU sampling. If SyncInMode is set to IMU then the IMU sampling "
2144 "loop will run on a SyncIn event. The relationship between the SyncIn event and a SyncIn trigger is defined "
2145 "by the SyncInEdge and SyncInSkipFactor parameters. If set to ASYNC then the VN-100 will output "
2146 "asynchronous serial messages upon each trigger event.");
2147 if (_syncInPin)
2148 {
2149 ImGui::EndDisabled();
2150 }
2151
2152 static constexpr std::array<std::pair<vn::protocol::uart::SyncInEdge, const char*>, 2> synchronizationControlSyncInEdges = {
2153 { { vn::protocol::uart::SyncInEdge::SYNCINEDGE_RISING, "Trigger on rising edge" },
2154 { vn::protocol::uart::SyncInEdge::SYNCINEDGE_FALLING, "Trigger on falling edge" } }
2155 };
2156 if (ImGui::BeginCombo(fmt::format("SyncIn Edge##{}", size_t(id)).c_str(), vn::protocol::uart::str(_synchronizationControlRegister.syncInEdge).c_str()))
2157 {
2158 for (const auto& synchronizationControlSyncInEdge : synchronizationControlSyncInEdges)
2159 {
2160 const bool isSelected = (_synchronizationControlRegister.syncInEdge == synchronizationControlSyncInEdge.first);
2161 if (ImGui::Selectable(vn::protocol::uart::str(synchronizationControlSyncInEdge.first).c_str(), isSelected))
2162 {
2163 _synchronizationControlRegister.syncInEdge = synchronizationControlSyncInEdge.first;
2164 LOG_DEBUG("{}: synchronizationControlRegister.syncInEdge changed to {}", nameId(), vn::protocol::uart::str(_synchronizationControlRegister.syncInEdge));
2165 flow::ApplyChanges();
2166 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2167 {
2168 try
2169 {
2170 _vs.writeSynchronizationControl(_synchronizationControlRegister);
2171 }
2172 catch (const std::exception& e)
2173 {
2174 LOG_ERROR("{}: Could not configure the synchronizationControlRegister: {}", nameId(), e.what());
2175 doDeinitialize();
2176 }
2177 }
2178 else
2179 {
2180 doDeinitialize();
2181 }
2182 }
2183 if (ImGui::IsItemHovered())
2184 {
2185 ImGui::BeginTooltip();
2186 ImGui::TextUnformatted(synchronizationControlSyncInEdge.second);
2187 ImGui::EndTooltip();
2188 }
2189
2190 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2191 {
2192 ImGui::SetItemDefaultFocus();
2193 }
2194 }
2195 ImGui::EndCombo();
2196 }
2197 ImGui::SameLine();
2198 gui::widgets::HelpMarker("The SyncInEdge register controls the type of edge the signal is set to trigger on.\n"
2199 "The factory default state is to trigger on a rising edge.");
2200
2201 int syncInSkipFactor = _synchronizationControlRegister.syncInSkipFactor;
2202 if (ImGui::InputInt(fmt::format("SyncIn Skip Factor##{}", size_t(id)).c_str(), &syncInSkipFactor))
2203 {
2204 if (syncInSkipFactor < 0)
2205 {
2206 syncInSkipFactor = 0;
2207 }
2208 else if (syncInSkipFactor > std::numeric_limits<uint16_t>::max())
2209 {
2210 syncInSkipFactor = std::numeric_limits<uint16_t>::max();
2211 }
2212 _synchronizationControlRegister.syncInSkipFactor = static_cast<uint16_t>(syncInSkipFactor);
2213 LOG_DEBUG("{}: synchronizationControlRegister.syncInSkipFactor changed to {}", nameId(), _synchronizationControlRegister.syncInSkipFactor);
2214 flow::ApplyChanges();
2215 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2216 {
2217 try
2218 {
2219 _vs.writeSynchronizationControl(_synchronizationControlRegister);
2220 }
2221 catch (const std::exception& e)
2222 {
2223 LOG_ERROR("{}: Could not configure the synchronizationControlRegister: {}", nameId(), e.what());
2224 doDeinitialize();
2225 }
2226 }
2227 else
2228 {
2229 doDeinitialize();
2230 }
2231 }
2232 ImGui::SameLine();
2233 gui::widgets::HelpMarker("The SyncInSkipFactor defines how many times trigger edges defined by SyncInEdge should occur prior to "
2234 "triggering a SyncIn event. The action performed on a SyncIn event is determined by the SyncIn mode. As an "
2235 "example if the SyncInSkipFactor was set to 4 and a 1 kHz signal was attached to the SyncIn pin, then the "
2236 "SyncIn event would only occur at 200 Hz.");
2237
2238 static constexpr std::array<std::pair<vn::protocol::uart::SyncOutMode, const char*>, 5> synchronizationControlSyncOutModes = {
2239 { { vn::protocol::uart::SyncOutMode::SYNCOUTMODE_NONE, "None" },
2240 { vn::protocol::uart::SyncOutMode::SYNCOUTMODE_IMUSTART, "Trigger at start of IMU sampling" },
2241 { vn::protocol::uart::SyncOutMode::SYNCOUTMODE_IMUREADY, "Trigger when IMU measurements are available" },
2242 { vn::protocol::uart::SyncOutMode::SYNCOUTMODE_INS, "Trigger when attitude measurements are available" },
2243 { vn::protocol::uart::SyncOutMode::SYNCOUTMODE_GPSPPS, "Trigger on a GPS PPS event (1 Hz) when a 3D fix is valid." } }
2244 };
2245 if (ImGui::BeginCombo(fmt::format("SyncOut Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_synchronizationControlRegister.syncOutMode).c_str()))
2246 {
2247 for (const auto& synchronizationControlSyncOutMode : synchronizationControlSyncOutModes)
2248 {
2249 const bool isSelected = (_synchronizationControlRegister.syncOutMode == synchronizationControlSyncOutMode.first);
2250 if (ImGui::Selectable(vn::protocol::uart::str(synchronizationControlSyncOutMode.first).c_str(), isSelected))
2251 {
2252 _synchronizationControlRegister.syncOutMode = synchronizationControlSyncOutMode.first;
2253 LOG_DEBUG("{}: synchronizationControlRegister.syncOutMode changed to {}", nameId(), vn::protocol::uart::str(_synchronizationControlRegister.syncOutMode));
2254
2255 if (_synchronizationControlRegister.syncOutMode == vn::protocol::uart::SyncOutMode::SYNCOUTMODE_GPSPPS
2256 && outputPins.size() <= 4)
2257 {
2258 nm::CreateOutputPin(this, "SyncOut", Pin::Type::Object, { "TimeSync" }, &_timeSyncOut);
2259 }
2260 else if (_synchronizationControlRegister.syncOutMode != vn::protocol::uart::SyncOutMode::SYNCOUTMODE_GPSPPS
2261 && outputPins.size() == 5)
2262 {
2263 nm::DeleteOutputPin(outputPins.at(4));
2264 }
2265
2266 flow::ApplyChanges();
2267 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2268 {
2269 try
2270 {
2271 _vs.writeSynchronizationControl(_synchronizationControlRegister);
2272 }
2273 catch (const std::exception& e)
2274 {
2275 LOG_ERROR("{}: Could not configure the synchronizationControlRegister: {}", nameId(), e.what());
2276 doDeinitialize();
2277 }
2278 }
2279 else
2280 {
2281 doDeinitialize();
2282 }
2283 }
2284 if (ImGui::IsItemHovered())
2285 {
2286 ImGui::BeginTooltip();
2287 ImGui::TextUnformatted(synchronizationControlSyncOutMode.second);
2288 ImGui::EndTooltip();
2289 }
2290
2291 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2292 {
2293 ImGui::SetItemDefaultFocus();
2294 }
2295 }
2296 ImGui::EndCombo();
2297 }
2298 ImGui::SameLine();
2299 gui::widgets::HelpMarker("The SyncOutMode register controls the behavior of the SyncOut pin. If this is set to IMU then the SyncOut "
2300 "will start the pulse when the internal IMU sample loop starts. This mode is used to make a sensor the Master "
2301 "in a multi-sensor network array. If this is set to IMU_READY mode then the pulse will start when IMU "
2302 "measurements become available. If this is set to INS mode then the pulse will start when attitude "
2303 "measurements are made available. Changes to this register take effect immediately.");
2304
2305 static constexpr std::array<std::pair<vn::protocol::uart::SyncOutPolarity, const char*>, 2> synchronizationControlSyncOutPolarities = {
2306 { { vn::protocol::uart::SyncOutPolarity::SYNCOUTPOLARITY_NEGATIVE, "Negative Pulse" },
2307 { vn::protocol::uart::SyncOutPolarity::SYNCOUTPOLARITY_POSITIVE, "Positive Pulse" } }
2308 };
2309 if (ImGui::BeginCombo(fmt::format("SyncOut Polarity##{}", size_t(id)).c_str(), vn::protocol::uart::str(_synchronizationControlRegister.syncOutPolarity).c_str()))
2310 {
2311 for (const auto& synchronizationControlSyncOutPolarity : synchronizationControlSyncOutPolarities)
2312 {
2313 const bool isSelected = (_synchronizationControlRegister.syncOutPolarity == synchronizationControlSyncOutPolarity.first);
2314 if (ImGui::Selectable(vn::protocol::uart::str(synchronizationControlSyncOutPolarity.first).c_str(), isSelected))
2315 {
2316 _synchronizationControlRegister.syncOutPolarity = synchronizationControlSyncOutPolarity.first;
2317 LOG_DEBUG("{}: synchronizationControlRegister.syncOutPolarity changed to {}", nameId(), vn::protocol::uart::str(_synchronizationControlRegister.syncOutPolarity));
2318 flow::ApplyChanges();
2319 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2320 {
2321 try
2322 {
2323 _vs.writeSynchronizationControl(_synchronizationControlRegister);
2324 }
2325 catch (const std::exception& e)
2326 {
2327 LOG_ERROR("{}: Could not configure the synchronizationControlRegister: {}", nameId(), e.what());
2328 doDeinitialize();
2329 }
2330 }
2331 else
2332 {
2333 doDeinitialize();
2334 }
2335 }
2336 if (ImGui::IsItemHovered())
2337 {
2338 ImGui::BeginTooltip();
2339 ImGui::TextUnformatted(synchronizationControlSyncOutPolarity.second);
2340 ImGui::EndTooltip();
2341 }
2342
2343 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2344 {
2345 ImGui::SetItemDefaultFocus();
2346 }
2347 }
2348 ImGui::EndCombo();
2349 }
2350 ImGui::SameLine();
2351 gui::widgets::HelpMarker("The SyncOutPolarity register controls the polarity of the output pulse on the SyncOut pin.\n"
2352 "Changes to this register take effect immediately.");
2353
2354 int syncOutSkipFactor = _synchronizationControlRegister.syncOutSkipFactor;
2355 if (ImGui::InputInt(fmt::format("SyncOut Skip Factor##{}", size_t(id)).c_str(), &syncOutSkipFactor))
2356 {
2357 if (syncOutSkipFactor < 0)
2358 {
2359 syncOutSkipFactor = 0;
2360 }
2361 else if (syncOutSkipFactor > std::numeric_limits<uint16_t>::max())
2362 {
2363 syncOutSkipFactor = std::numeric_limits<uint16_t>::max();
2364 }
2365 _synchronizationControlRegister.syncOutSkipFactor = static_cast<uint16_t>(syncOutSkipFactor);
2366 LOG_DEBUG("{}: synchronizationControlRegister.syncOutSkipFactor changed to {}", nameId(), _synchronizationControlRegister.syncOutSkipFactor);
2367 flow::ApplyChanges();
2368 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2369 {
2370 try
2371 {
2372 _vs.writeSynchronizationControl(_synchronizationControlRegister);
2373 }
2374 catch (const std::exception& e)
2375 {
2376 LOG_ERROR("{}: Could not configure the synchronizationControlRegister: {}", nameId(), e.what());
2377 doDeinitialize();
2378 }
2379 }
2380 else
2381 {
2382 doDeinitialize();
2383 }
2384 }
2385 ImGui::SameLine();
2386 gui::widgets::HelpMarker("The SyncOutSkipFactor defines how many times the sync out event should be skipped before actually triggering the SyncOut pin.");
2387
2388 int syncOutPulseWidth = static_cast<int>(_synchronizationControlRegister.syncOutPulseWidth);
2389 if (ImGui::InputInt(fmt::format("SyncOut Pulse Width##{}", size_t(id)).c_str(), &syncOutPulseWidth))
2390 {
2391 syncOutPulseWidth = std::max(syncOutPulseWidth, 0);
2392 _synchronizationControlRegister.syncOutPulseWidth = static_cast<uint32_t>(syncOutPulseWidth);
2393 LOG_DEBUG("{}: synchronizationControlRegister.syncOutPulseWidth changed to {}", nameId(), _synchronizationControlRegister.syncOutPulseWidth);
2394 flow::ApplyChanges();
2395 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2396 {
2397 try
2398 {
2399 _vs.writeSynchronizationControl(_synchronizationControlRegister);
2400 }
2401 catch (const std::exception& e)
2402 {
2403 LOG_ERROR("{}: Could not configure the synchronizationControlRegister: {}", nameId(), e.what());
2404 doDeinitialize();
2405 }
2406 }
2407 else
2408 {
2409 doDeinitialize();
2410 }
2411 }
2412 ImGui::SameLine();
2413 gui::widgets::HelpMarker("The SyncOutPulseWidth field controls the desired width of the SyncOut pulse.\n"
2414 "The default value is 100,000,000 (100 ms).");
2415
2416 ImGui::TreePop();
2417 }
2418
2419 if (ImGui::TreeNode(fmt::format("Communication Protocol Control##{}", size_t(id)).c_str()))
2420 {
2421 ImGui::TextUnformatted("Contains parameters that controls the communication protocol used by the sensor.");
2422
2423 static constexpr std::array<std::pair<vn::protocol::uart::CountMode, const char*>, 5> communicationProtocolControlSerialCounts = {
2424 { { vn::protocol::uart::CountMode::COUNTMODE_NONE, "OFF" },
2425 { vn::protocol::uart::CountMode::COUNTMODE_SYNCINCOUNT, "SyncIn Counter" },
2426 { vn::protocol::uart::CountMode::COUNTMODE_SYNCINTIME, "SyncIn Time" },
2427 { vn::protocol::uart::CountMode::COUNTMODE_SYNCOUTCOUNTER, "SyncOut Counter" },
2428 { vn::protocol::uart::CountMode::COUNTMODE_GPSPPS, "Gps Pps Time" } }
2429 };
2430 if (ImGui::BeginCombo(fmt::format("Serial Count Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_communicationProtocolControlRegister.serialCount).c_str()))
2431 {
2432 for (const auto& communicationProtocolControlSerialCount : communicationProtocolControlSerialCounts)
2433 {
2434 const bool isSelected = (_communicationProtocolControlRegister.serialCount == communicationProtocolControlSerialCount.first);
2435 if (ImGui::Selectable(vn::protocol::uart::str(communicationProtocolControlSerialCount.first).c_str(), isSelected))
2436 {
2437 _communicationProtocolControlRegister.serialCount = communicationProtocolControlSerialCount.first;
2438 LOG_DEBUG("{}: communicationProtocolControlRegister.serialCount changed to {}", nameId(), vn::protocol::uart::str(_communicationProtocolControlRegister.serialCount));
2439 flow::ApplyChanges();
2440 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2441 {
2442 try
2443 {
2444 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
2445 }
2446 catch (const std::exception& e)
2447 {
2448 LOG_ERROR("{}: Could not configure the communicationProtocolControlRegister: {}", nameId(), e.what());
2449 doDeinitialize();
2450 }
2451 }
2452 else
2453 {
2454 doDeinitialize();
2455 }
2456 }
2457 if (ImGui::IsItemHovered())
2458 {
2459 ImGui::BeginTooltip();
2460 ImGui::TextUnformatted(communicationProtocolControlSerialCount.second);
2461 ImGui::EndTooltip();
2462 }
2463
2464 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2465 {
2466 ImGui::SetItemDefaultFocus();
2467 }
2468 }
2469 ImGui::EndCombo();
2470 }
2471 ImGui::SameLine();
2472 gui::widgets::HelpMarker("The SerialCount field provides a means of appending a time or counter to the end of all asynchronous "
2473 "communication messages transmitted on the serial interface. The values for each of these counters come "
2474 "directly from the Synchronization Status Register in the System subsystem.\n\n"
2475 "With the SerialCount field set to OFF a typical serial asynchronous message would appear as the following:\n"
2476 "$VNYPR,+010.071,+000.278,-002.026*60\n\n"
2477 "With the SerialCount field set to one of the non-zero values the same asynchronous message would appear "
2478 "instead as:\n"
2479 "$VNYPR,+010.071,+000.278,-002.026,T1162704*2F\n\n"
2480 "When the SerialCount field is enabled the counter will always be appended to the end of the message just "
2481 "prior to the checksum. The counter will be preceded by the T character to distinguish it from the status field.");
2482
2483 static constexpr std::array<std::pair<vn::protocol::uart::StatusMode, const char*>, 3> communicationProtocolControlSerialStatuses = {
2484 { { vn::protocol::uart::StatusMode::STATUSMODE_OFF, "OFF" },
2485 { vn::protocol::uart::StatusMode::STATUSMODE_VPESTATUS, "VPE Status" },
2486 { vn::protocol::uart::StatusMode::STATUSMODE_INSSTATUS, "INS Status" } }
2487 };
2488 if (ImGui::BeginCombo(fmt::format("Serial Status##{}", size_t(id)).c_str(), vn::protocol::uart::str(_communicationProtocolControlRegister.serialStatus).c_str()))
2489 {
2490 for (const auto& communicationProtocolControlSerialStatus : communicationProtocolControlSerialStatuses)
2491 {
2492 const bool isSelected = (_communicationProtocolControlRegister.serialStatus == communicationProtocolControlSerialStatus.first);
2493 if (ImGui::Selectable(vn::protocol::uart::str(communicationProtocolControlSerialStatus.first).c_str(), isSelected))
2494 {
2495 _communicationProtocolControlRegister.serialStatus = communicationProtocolControlSerialStatus.first;
2496 LOG_DEBUG("{}: communicationProtocolControlRegister.serialStatus changed to {}", nameId(), vn::protocol::uart::str(_communicationProtocolControlRegister.serialStatus));
2497 flow::ApplyChanges();
2498 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2499 {
2500 try
2501 {
2502 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
2503 }
2504 catch (const std::exception& e)
2505 {
2506 LOG_ERROR("{}: Could not configure the communicationProtocolControlRegister: {}", nameId(), e.what());
2507 doDeinitialize();
2508 }
2509 }
2510 else
2511 {
2512 doDeinitialize();
2513 }
2514 }
2515 if (ImGui::IsItemHovered())
2516 {
2517 ImGui::BeginTooltip();
2518 ImGui::TextUnformatted(communicationProtocolControlSerialStatus.second);
2519 ImGui::EndTooltip();
2520 }
2521
2522 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2523 {
2524 ImGui::SetItemDefaultFocus();
2525 }
2526 }
2527 ImGui::EndCombo();
2528 }
2529 ImGui::SameLine();
2530 gui::widgets::HelpMarker("The SerialStatus field provides a means of tracking real-time status information pertain to the overall state "
2531 "of the sensor measurements and onboard filtering algorithm. As with the SerialCount, a typical serial "
2532 "asynchronous message would appear as the following:\n"
2533 "$VNYPR,+010.071,+000.278,-002.026*60\n\n"
2534 "With the SerialStatus field set to one of the non-zero values, the same asynchronous message would appear "
2535 "instead as:\n"
2536 "$VNYPR,+010.071,+000.278,-002.026,S0000*1F\n\n"
2537 "When the SerialStatus field is enabled the status will always be appended to the end of the message just "
2538 "prior to the checksum. If both the SerialCount and SerialStatus are enabled then the SerialStatus will be "
2539 "displayed first. The counter will be preceded by the S character to distinguish it from the counter field. The "
2540 "status consists of 4 hexadecimal characters.");
2541
2542 if (_sensorModel == VectorNavModel::VN100_VN110)
2543 {
2544 if (ImGui::BeginCombo(fmt::format("SPI Count##{}", size_t(id)).c_str(), vn::protocol::uart::str(_communicationProtocolControlRegister.spiCount).c_str()))
2545 {
2546 for (const auto& communicationProtocolControlSpiCount : communicationProtocolControlSerialCounts)
2547 {
2548 const bool isSelected = (_communicationProtocolControlRegister.spiCount == communicationProtocolControlSpiCount.first);
2549 if (ImGui::Selectable(vn::protocol::uart::str(communicationProtocolControlSpiCount.first).c_str(), isSelected))
2550 {
2551 _communicationProtocolControlRegister.spiCount = communicationProtocolControlSpiCount.first;
2552 LOG_DEBUG("{}: communicationProtocolControlRegister.spiCount changed to {}", nameId(), vn::protocol::uart::str(_communicationProtocolControlRegister.spiCount));
2553 flow::ApplyChanges();
2554 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2555 {
2556 try
2557 {
2558 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
2559 }
2560 catch (const std::exception& e)
2561 {
2562 LOG_ERROR("{}: Could not configure the communicationProtocolControlRegister: {}", nameId(), e.what());
2563 doDeinitialize();
2564 }
2565 }
2566 else
2567 {
2568 doDeinitialize();
2569 }
2570 }
2571 if (ImGui::IsItemHovered())
2572 {
2573 ImGui::BeginTooltip();
2574 ImGui::TextUnformatted(communicationProtocolControlSpiCount.second);
2575 ImGui::EndTooltip();
2576 }
2577
2578 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2579 {
2580 ImGui::SetItemDefaultFocus();
2581 }
2582 }
2583 ImGui::EndCombo();
2584 }
2585 ImGui::SameLine();
2586 gui::widgets::HelpMarker("The SPICount field provides a means of appending a time or counter to the end of all SPI packets. The "
2587 "values for each of these counters come directly from the Synchronization Status Register.");
2588 }
2589
2590 if (_sensorModel == VectorNavModel::VN100_VN110)
2591 {
2592 if (ImGui::BeginCombo(fmt::format("SPI Status##{}", size_t(id)).c_str(), vn::protocol::uart::str(_communicationProtocolControlRegister.spiStatus).c_str()))
2593 {
2594 for (const auto& communicationProtocolControlSpiStatus : communicationProtocolControlSerialStatuses)
2595 {
2596 const bool isSelected = (_communicationProtocolControlRegister.spiStatus == communicationProtocolControlSpiStatus.first);
2597 if (ImGui::Selectable(vn::protocol::uart::str(communicationProtocolControlSpiStatus.first).c_str(), isSelected))
2598 {
2599 _communicationProtocolControlRegister.spiStatus = communicationProtocolControlSpiStatus.first;
2600 LOG_DEBUG("{}: communicationProtocolControlRegister.spiStatus changed to {}", nameId(), vn::protocol::uart::str(_communicationProtocolControlRegister.spiStatus));
2601 flow::ApplyChanges();
2602 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2603 {
2604 try
2605 {
2606 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
2607 }
2608 catch (const std::exception& e)
2609 {
2610 LOG_ERROR("{}: Could not configure the communicationProtocolControlRegister: {}", nameId(), e.what());
2611 doDeinitialize();
2612 }
2613 }
2614 else
2615 {
2616 doDeinitialize();
2617 }
2618 }
2619 if (ImGui::IsItemHovered())
2620 {
2621 ImGui::BeginTooltip();
2622 ImGui::TextUnformatted(communicationProtocolControlSpiStatus.second);
2623 ImGui::EndTooltip();
2624 }
2625
2626 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2627 {
2628 ImGui::SetItemDefaultFocus();
2629 }
2630 }
2631 ImGui::EndCombo();
2632 }
2633 ImGui::SameLine();
2634 gui::widgets::HelpMarker("The AsyncStatus field provides a means of tracking real-time status information pertaining to the overall "
2635 "state of the sensor measurements and onboard filtering algorithm. This information is very useful in "
2636 "situations where action must be taken when certain crucial events happen such as the detection of gyro "
2637 "saturation or magnetic interference.");
2638 }
2639
2640 static constexpr std::array<std::pair<vn::protocol::uart::ChecksumMode, const char*>, 2> communicationProtocolControlSerialChecksums = {
2641 { { vn::protocol::uart::ChecksumMode::CHECKSUMMODE_CHECKSUM, "8-Bit Checksum" },
2642 { vn::protocol::uart::ChecksumMode::CHECKSUMMODE_CRC, "16-Bit CRC" } }
2643 };
2644 if (ImGui::BeginCombo(fmt::format("Serial Checksum##{}", size_t(id)).c_str(), vn::protocol::uart::str(_communicationProtocolControlRegister.serialChecksum).c_str()))
2645 {
2646 for (const auto& communicationProtocolControlSerialChecksum : communicationProtocolControlSerialChecksums)
2647 {
2648 const bool isSelected = (_communicationProtocolControlRegister.serialChecksum == communicationProtocolControlSerialChecksum.first);
2649 if (ImGui::Selectable(vn::protocol::uart::str(communicationProtocolControlSerialChecksum.first).c_str(), isSelected))
2650 {
2651 _communicationProtocolControlRegister.serialChecksum = communicationProtocolControlSerialChecksum.first;
2652 LOG_DEBUG("{}: communicationProtocolControlRegister.serialChecksum changed to {}", nameId(), vn::protocol::uart::str(_communicationProtocolControlRegister.serialChecksum));
2653 flow::ApplyChanges();
2654 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2655 {
2656 try
2657 {
2658 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
2659 }
2660 catch (const std::exception& e)
2661 {
2662 LOG_ERROR("{}: Could not configure the communicationProtocolControlRegister: {}", nameId(), e.what());
2663 doDeinitialize();
2664 }
2665 }
2666 else
2667 {
2668 doDeinitialize();
2669 }
2670 }
2671 if (ImGui::IsItemHovered())
2672 {
2673 ImGui::BeginTooltip();
2674 ImGui::TextUnformatted(communicationProtocolControlSerialChecksum.second);
2675 ImGui::EndTooltip();
2676 }
2677
2678 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2679 {
2680 ImGui::SetItemDefaultFocus();
2681 }
2682 }
2683 ImGui::EndCombo();
2684 }
2685 ImGui::SameLine();
2686 gui::widgets::HelpMarker("This field controls the type of checksum used for the serial communications. Normally the VN-310E uses an "
2687 "8-bit checksum identical to the type used for normal GPS NMEA packets. This form of checksum however "
2688 "offers only a limited means of error checking. As an alternative a full 16-bit CRC (CRC16-CCITT with "
2689 "polynomial = 0x07) is also offered. The 2-byte CRC value is printed using 4 hexadecimal digits.");
2690
2691 if (_sensorModel == VectorNavModel::VN100_VN110)
2692 {
2693 static constexpr std::array<std::pair<vn::protocol::uart::ChecksumMode, const char*>, 3> communicationProtocolControlSpiChecksums = {
2694 { { vn::protocol::uart::ChecksumMode::CHECKSUMMODE_OFF, "OFF" },
2695 { vn::protocol::uart::ChecksumMode::CHECKSUMMODE_CHECKSUM, "8-Bit Checksum" },
2696 { vn::protocol::uart::ChecksumMode::CHECKSUMMODE_CRC, "16-Bit CRC" } }
2697 };
2698 if (ImGui::BeginCombo(fmt::format("SPI Checksum##{}", size_t(id)).c_str(), vn::protocol::uart::str(_communicationProtocolControlRegister.spiChecksum).c_str()))
2699 {
2700 for (const auto& communicationProtocolControlSpiChecksum : communicationProtocolControlSpiChecksums)
2701 {
2702 const bool isSelected = (_communicationProtocolControlRegister.spiChecksum == communicationProtocolControlSpiChecksum.first);
2703 if (ImGui::Selectable(vn::protocol::uart::str(communicationProtocolControlSpiChecksum.first).c_str(), isSelected))
2704 {
2705 _communicationProtocolControlRegister.spiChecksum = communicationProtocolControlSpiChecksum.first;
2706 LOG_DEBUG("{}: communicationProtocolControlRegister.spiChecksum changed to {}", nameId(), vn::protocol::uart::str(_communicationProtocolControlRegister.spiChecksum));
2707 flow::ApplyChanges();
2708 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2709 {
2710 try
2711 {
2712 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
2713 }
2714 catch (const std::exception& e)
2715 {
2716 LOG_ERROR("{}: Could not configure the communicationProtocolControlRegister: {}", nameId(), e.what());
2717 doDeinitialize();
2718 }
2719 }
2720 else
2721 {
2722 doDeinitialize();
2723 }
2724 }
2725 if (ImGui::IsItemHovered())
2726 {
2727 ImGui::BeginTooltip();
2728 ImGui::TextUnformatted(communicationProtocolControlSpiChecksum.second);
2729 ImGui::EndTooltip();
2730 }
2731
2732 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2733 {
2734 ImGui::SetItemDefaultFocus();
2735 }
2736 }
2737 ImGui::EndCombo();
2738 }
2739 ImGui::SameLine();
2740 gui::widgets::HelpMarker("This field controls the type of checksum used for the SPI communications. The checksum is appended to "
2741 "the end of the binary data packet. The 16-bit CRC is identical to the one described above for the "
2742 "SerialChecksum.");
2743 }
2744
2745 static constexpr std::array<std::pair<vn::protocol::uart::ErrorMode, const char*>, 3> communicationProtocolControlErrorModes = {
2746 { { vn::protocol::uart::ErrorMode::ERRORMODE_IGNORE, "Ignore Error" },
2747 { vn::protocol::uart::ErrorMode::ERRORMODE_SEND, "Send Error" },
2748 { vn::protocol::uart::ErrorMode::ERRORMODE_SENDANDOFF, "Send Error and set ADOR register to OFF" } }
2749 };
2750 if (ImGui::BeginCombo(fmt::format("Error Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_communicationProtocolControlRegister.errorMode).c_str()))
2751 {
2752 for (const auto& communicationProtocolControlErrorMode : communicationProtocolControlErrorModes)
2753 {
2754 const bool isSelected = (_communicationProtocolControlRegister.errorMode == communicationProtocolControlErrorMode.first);
2755 if (ImGui::Selectable(vn::protocol::uart::str(communicationProtocolControlErrorMode.first).c_str(), isSelected))
2756 {
2757 _communicationProtocolControlRegister.errorMode = communicationProtocolControlErrorMode.first;
2758 LOG_DEBUG("{}: communicationProtocolControlRegister.errorMode changed to {}", nameId(), vn::protocol::uart::str(_communicationProtocolControlRegister.errorMode));
2759 flow::ApplyChanges();
2760 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2761 {
2762 try
2763 {
2764 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
2765 }
2766 catch (const std::exception& e)
2767 {
2768 LOG_ERROR("{}: Could not configure the communicationProtocolControlRegister: {}", nameId(), e.what());
2769 doDeinitialize();
2770 }
2771 }
2772 else
2773 {
2774 doDeinitialize();
2775 }
2776 }
2777 if (ImGui::IsItemHovered())
2778 {
2779 ImGui::BeginTooltip();
2780 ImGui::TextUnformatted(communicationProtocolControlErrorMode.second);
2781 ImGui::EndTooltip();
2782 }
2783
2784 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2785 {
2786 ImGui::SetItemDefaultFocus();
2787 }
2788 }
2789 ImGui::EndCombo();
2790 }
2791 ImGui::SameLine();
2792 gui::widgets::HelpMarker("This field controls the type of action taken by the VectorNav when an error event occurs. If the send error "
2793 "mode is enabled then a message similar to the one shown below will be sent on the serial bus when an error "
2794 "event occurs.\n\n"
2795 "$VNERR,03*72\n\n"
2796 "Regardless of the state of the ErrorMode, the number of error events is always recorded and is made available "
2797 "in the SysErrors field of the Communication Protocol Status Register in the System subsystem.");
2798
2799 if (ImGui::TreeNode(fmt::format("Example Async Messages##{}", size_t(id)).c_str()))
2800 {
2801 ImGui::TextUnformatted("The following table shows example asynchronous messages with the\nAsyncCount and the AsyncStatus values appended to the end.");
2802
2803 if (ImGui::BeginTable("Example Async Messages Table", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
2804 {
2805 ImGui::TableSetupColumn("Example Type");
2806 ImGui::TableSetupColumn("Message");
2807 ImGui::TableHeadersRow();
2808
2809 ImGui::TableNextColumn();
2810 ImGui::TextUnformatted("Async Message with\nAsyncCount Enabled");
2811 ImGui::TableNextColumn();
2812 ImGui::TextUnformatted("$VNYPR,+010.071,+000.278,-002.026,T1162704*2F");
2813
2814 ImGui::TableNextColumn();
2815 ImGui::TextUnformatted("Async Message with\nAsyncStatus Enabled");
2816 ImGui::TableNextColumn();
2817 ImGui::TextUnformatted("$VNYPR,+010.071,+000.278,-002.026,S0000*1F");
2818
2819 ImGui::TableNextColumn();
2820 ImGui::TextUnformatted("Async Message with\nAsyncCount and\nAsyncStatus Enabled");
2821 ImGui::TableNextColumn();
2822 ImGui::TextUnformatted("$VNYPR,+010.071,+000.278,-002.026,T1162704,S0000*50");
2823
2824 ImGui::EndTable();
2825 }
2826
2827 ImGui::TreePop();
2828 }
2829
2830 ImGui::TreePop();
2831 }
2832
2833 for (size_t b = 0; b < _binaryOutputRegister.size(); b++)
2834 {
2835 if (ImGui::TreeNode(fmt::format("Binary Output {}##{}", b + 1, size_t(id)).c_str()))
2836 {
2837 static constexpr std::array<std::pair<vn::protocol::uart::AsyncMode, const char*>, 4> asyncModes = {
2838 { { vn::protocol::uart::AsyncMode::ASYNCMODE_NONE, " User message is not automatically sent out either serial port" },
2839 { vn::protocol::uart::AsyncMode::ASYNCMODE_PORT1, "Message is sent out serial port 1 at a fixed rate" },
2840 { vn::protocol::uart::AsyncMode::ASYNCMODE_PORT2, "Message is sent out serial port 2 at a fixed rate" },
2841 { vn::protocol::uart::AsyncMode::ASYNCMODE_BOTH, "Message is sent out both serial ports at a fixed rate" } }
2842 };
2843 if ((_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output2 && b == 1)
2844 || (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output3 && b == 2)
2845 || (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output2_Output3 && b == 2))
2846 {
2847 ImGui::BeginDisabled();
2848 }
2849
2850 if (ImGui::BeginCombo(fmt::format("Async Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_binaryOutputRegister.at(b).asyncMode).c_str()))
2851 {
2852 for (const auto& asyncMode : asyncModes)
2853 {
2854 const bool isSelected = (_binaryOutputRegister.at(b).asyncMode == asyncMode.first);
2855 if (ImGui::Selectable(vn::protocol::uart::str(asyncMode.first).c_str(), isSelected))
2856 {
2857 _binaryOutputRegister.at(b).asyncMode = asyncMode.first;
2858 LOG_DEBUG("{}: binaryOutputRegister.at({}).asyncMode changed to {}", nameId(), b, vn::protocol::uart::str(_binaryOutputRegister.at(b).asyncMode));
2859
2860 std::vector<size_t> binaryOutputRegistersToUpdate;
2861 binaryOutputRegistersToUpdate.push_back(b);
2862 if (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output2 && b == 0)
2863 {
2864 _binaryOutputRegister.at(1).asyncMode = asyncMode.first;
2865 LOG_DEBUG("{}: binaryOutputRegister.at({}).asyncMode changed to {}", nameId(), 1, vn::protocol::uart::str(_binaryOutputRegister.at(1).asyncMode));
2866 binaryOutputRegistersToUpdate.push_back(1);
2867 }
2868 else if ((_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output3 && b == 0)
2869 || (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output2_Output3 && b == 1))
2870 {
2871 _binaryOutputRegister.at(2).asyncMode = asyncMode.first;
2872 LOG_DEBUG("{}: binaryOutputRegister.at({}).asyncMode changed to {}", nameId(), 2, vn::protocol::uart::str(_binaryOutputRegister.at(2).asyncMode));
2873 binaryOutputRegistersToUpdate.push_back(2);
2874 }
2875 flow::ApplyChanges();
2876
2877 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2878 {
2879 for (const auto& binUpdate : binaryOutputRegistersToUpdate)
2880 {
2881 try
2882 {
2883 switch (binUpdate)
2884 {
2885 case 0:
2886 _vs.writeBinaryOutput1(_binaryOutputRegister.at(binUpdate));
2887 break;
2888 case 1:
2889 _vs.writeBinaryOutput2(_binaryOutputRegister.at(binUpdate));
2890 break;
2891 case 2:
2892 _vs.writeBinaryOutput3(_binaryOutputRegister.at(binUpdate));
2893 break;
2894 default:
2895 break;
2896 }
2897 }
2898 catch (const std::exception& e)
2899 {
2900 LOG_ERROR("{}: Could not configure the binaryOutputRegister {}: {}", nameId(), binUpdate + 1, e.what());
2901 doDeinitialize();
2902 }
2903 }
2904 }
2905 else
2906 {
2907 doDeinitialize();
2908 }
2909 }
2910 if (ImGui::IsItemHovered())
2911 {
2912 ImGui::BeginTooltip();
2913 ImGui::TextUnformatted(asyncMode.second);
2914 ImGui::EndTooltip();
2915 }
2916
2917 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2918 {
2919 ImGui::SetItemDefaultFocus();
2920 }
2921 }
2922 ImGui::EndCombo();
2923 }
2924 ImGui::SameLine();
2925 gui::widgets::HelpMarker("Selects whether the output message should be sent "
2926 "out on the serial port(s) at a fixed rate.");
2927
2928 const char* frequencyText = _binaryOutputSelectedFrequency.at(b) < _dividerFrequency.first.size()
2929 ? _dividerFrequency.second.at(_binaryOutputSelectedFrequency.at(b)).c_str()
2930 : "Unknown";
2931 if (auto freq = static_cast<int>(_binaryOutputSelectedFrequency.at(b));
2932 ImGui::SliderInt(fmt::format("Frequency##{} {}", size_t(id), b).c_str(),
2933 &freq,
2934 0, static_cast<int>(_dividerFrequency.second.size()) - 1,
2935 frequencyText))
2936 {
2937 _binaryOutputSelectedFrequency.at(b) = static_cast<size_t>(freq);
2938 _binaryOutputRegister.at(b).rateDivisor = _dividerFrequency.first.at(_binaryOutputSelectedFrequency.at(b));
2939 LOG_DEBUG("{}: Frequency of Binary Group {} changed to {}", nameId(), b + 1, _dividerFrequency.second.at(_binaryOutputSelectedFrequency.at(b)));
2940 if (_coupleImuRateOutput)
2941 {
2942 if (_binaryOutputRegister.at(b).imuField & (vn::protocol::uart::IMUGROUP_ACCEL | vn::protocol::uart::IMUGROUP_UNCOMPACCEL))
2943 {
2944 _imuFilteringConfigurationRegister.accelWindowSize = _binaryOutputRegister.at(b).rateDivisor;
2945 }
2946 if (_binaryOutputRegister.at(b).imuField & (vn::protocol::uart::IMUGROUP_ANGULARRATE | vn::protocol::uart::IMUGROUP_UNCOMPGYRO))
2947 {
2948 _imuFilteringConfigurationRegister.gyroWindowSize = _binaryOutputRegister.at(b).rateDivisor;
2949 }
2950 if (_binaryOutputRegister.at(b).imuField & (vn::protocol::uart::IMUGROUP_MAG | vn::protocol::uart::IMUGROUP_UNCOMPMAG))
2951 {
2952 _imuFilteringConfigurationRegister.magWindowSize = _binaryOutputRegister.at(b).rateDivisor;
2953 }
2954 if (_binaryOutputRegister.at(b).imuField & (vn::protocol::uart::IMUGROUP_MAG | vn::protocol::uart::IMUGROUP_UNCOMPMAG))
2955 {
2956 _imuFilteringConfigurationRegister.magWindowSize = _binaryOutputRegister.at(b).rateDivisor;
2957 }
2958 if (_binaryOutputRegister.at(b).imuField & vn::protocol::uart::IMUGROUP_TEMP)
2959 {
2960 _imuFilteringConfigurationRegister.tempWindowSize = _binaryOutputRegister.at(b).rateDivisor;
2961 }
2962 if (_binaryOutputRegister.at(b).imuField & vn::protocol::uart::IMUGROUP_PRES)
2963 {
2964 _imuFilteringConfigurationRegister.presWindowSize = _binaryOutputRegister.at(b).rateDivisor;
2965 }
2966 LOG_DATA("{}: Frequency of ImuFilter matches output frequency (coupled 'rateDivisor' and 'windowSize' of moving average filter.)", nameId());
2967
2968 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2969 {
2970 try
2971 {
2972 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
2973 }
2974 catch (const std::exception& e)
2975 {
2976 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
2977 doDeinitialize();
2978 }
2979 }
2980 else
2981 {
2982 doDeinitialize();
2983 }
2984 }
2985
2986 std::vector<size_t> binaryOutputRegistersToUpdate;
2987 binaryOutputRegistersToUpdate.push_back(b);
2988 if (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output2 && b == 0)
2989 {
2990 _binaryOutputSelectedFrequency.at(1) = _binaryOutputSelectedFrequency.at(b);
2991 _binaryOutputRegister.at(1).rateDivisor = _dividerFrequency.first.at(_binaryOutputSelectedFrequency.at(1));
2992 LOG_DEBUG("{}: Frequency of Binary Group {} changed to {}", nameId(), 1 + 1, _dividerFrequency.second.at(_binaryOutputSelectedFrequency.at(1)));
2993 binaryOutputRegistersToUpdate.push_back(1);
2994 }
2995 else if ((_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output3 && b == 0)
2996 || (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output2_Output3 && b == 1))
2997 {
2998 _binaryOutputSelectedFrequency.at(2) = _binaryOutputSelectedFrequency.at(b);
2999 _binaryOutputRegister.at(2).rateDivisor = _dividerFrequency.first.at(_binaryOutputSelectedFrequency.at(2));
3000 LOG_DEBUG("{}: Frequency of Binary Group {} changed to {}", nameId(), 2 + 1, _dividerFrequency.second.at(_binaryOutputSelectedFrequency.at(2)));
3001 binaryOutputRegistersToUpdate.push_back(2);
3002 }
3003
3004 flow::ApplyChanges();
3005 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3006 {
3007 for (const auto& binUpdate : binaryOutputRegistersToUpdate)
3008 {
3009 try
3010 {
3011 switch (binUpdate)
3012 {
3013 case 0:
3014 _vs.writeBinaryOutput1(_binaryOutputRegister.at(binUpdate));
3015 break;
3016 case 1:
3017 _vs.writeBinaryOutput2(_binaryOutputRegister.at(binUpdate));
3018 break;
3019 case 2:
3020 _vs.writeBinaryOutput3(_binaryOutputRegister.at(binUpdate));
3021 break;
3022 default:
3023 break;
3024 }
3025 }
3026 catch (const std::exception& e)
3027 {
3028 LOG_ERROR("{}: Could not configure the binaryOutputRegister {}: {}", nameId(), binUpdate + 1, e.what());
3029 doDeinitialize();
3030 }
3031 }
3032 }
3033 else
3034 {
3035 doDeinitialize();
3036 }
3037 }
3038 if ((_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output2 && b == 1)
3039 || (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output3 && b == 2)
3040 || (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output2_Output3 && b == 2))
3041 {
3042 ImGui::EndDisabled();
3043 }
3044
3045 if (ImGui::BeginTable(fmt::format("##VectorNavSensorConfig ({})", size_t(id)).c_str(), 6,
3046 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
3047 {
3048 // ImGui::TableSetupColumn("Common", ImGuiTableColumnFlags_WidthFixed);
3049 ImGui::TableSetupColumn("Time", ImGuiTableColumnFlags_WidthFixed);
3050 ImGui::TableSetupColumn("IMU", ImGuiTableColumnFlags_WidthFixed);
3051 ImGui::TableSetupColumn("GNSS1", ImGuiTableColumnFlags_WidthFixed);
3052 ImGui::TableSetupColumn("Attitude", ImGuiTableColumnFlags_WidthFixed);
3053 ImGui::TableSetupColumn("INS", ImGuiTableColumnFlags_WidthFixed);
3054 ImGui::TableSetupColumn("GNSS2", ImGuiTableColumnFlags_WidthFixed);
3055 ImGui::TableHeadersRow();
3056
3057 auto CheckboxFlags = [&, this](int index, const char* label, int* flags, int flags_value, bool enabled, void (*toggleFields)(VectorNavSensor*, vn::sensors::BinaryOutputRegister&, uint32_t&)) {
3058 ImGui::TableSetColumnIndex(index);
3059
3060 if (!enabled)
3061 {
3062 ImGui::BeginDisabled();
3063 }
3064
3065 if (ImGui::CheckboxFlags(label, flags, flags_value))
3066 {
3067 LOG_DEBUG("{}: Field '{}' of Binary Group {} is now {}", nameId(), std::string(label).substr(0, std::string(label).find('#')), b + 1, (*flags & flags_value) ? "checked" : "unchecked");
3068 if (toggleFields)
3069 {
3070 toggleFields(this, _binaryOutputRegister.at(b), *reinterpret_cast<uint32_t*>(flags));
3071 }
3072 flow::ApplyChanges();
3073 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3074 {
3075 try
3076 {
3077 if (b == 0)
3078 {
3079 _vs.writeBinaryOutput1(_binaryOutputRegister.at(b));
3080 }
3081 else if (b == 1)
3082 {
3083 _vs.writeBinaryOutput2(_binaryOutputRegister.at(b));
3084 }
3085 else if (b == 2)
3086 {
3087 _vs.writeBinaryOutput3(_binaryOutputRegister.at(b));
3088 }
3089
3090 for (auto& link : outputPins.at(b + 2).links)
3091 {
3092 if (auto* connectedPin = link.getConnectedPin())
3093 {
3094 outputPins.at(b + 2).recreateLink(*connectedPin);
3095 }
3096 }
3097 }
3098 catch (const std::exception& e)
3099 {
3100 LOG_ERROR("{}: Could not configure the binaryOutputRegister {}: {}", nameId(), b + 1, e.what());
3101 doDeinitialize();
3102 }
3103 }
3104 else
3105 {
3106 doDeinitialize();
3107 }
3108 }
3109
3110 if (!enabled)
3111 {
3112 ImGui::EndDisabled();
3113 }
3114 };
3115
3116 for (size_t i = 0; i < 16; i++)
3117 {
3118 if (i < std::max({ /* _binaryGroupCommon.size(), */ _binaryGroupTime.size(), _binaryGroupIMU.size(),
3119 _binaryGroupGNSS.size(), _binaryGroupAttitude.size(), _binaryGroupINS.size() }))
3120 {
3121 ImGui::TableNextRow();
3122 }
3123 // if (i < _binaryGroupCommon.size())
3124 // {
3125 // const auto& binaryGroupItem = _binaryGroupCommon.at(i);
3126 // CheckboxFlags(0, fmt::format("{}##Common {} {}", binaryGroupItem.name, size_t(id), b).c_str(),
3127 // reinterpret_cast<int*>(&_binaryOutputRegister.at(b).commonField),
3128 // binaryGroupItem.flagsValue,
3129 // binaryGroupItem.isEnabled(_sensorModel, _binaryOutputRegister.at(b), static_cast<uint32_t>(_binaryOutputRegister.at(b).commonField)),
3130 // binaryGroupItem.toggleFields);
3131 // if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && binaryGroupItem.tooltip != nullptr)
3132 // {
3133 // ImGui::BeginTooltip();
3134 // binaryGroupItem.tooltip();
3135 // ImGui::EndTooltip();
3136 // }
3137 // }
3138 if (i < _binaryGroupTime.size())
3139 {
3140 const auto& binaryGroupItem = _binaryGroupTime.at(i);
3141 CheckboxFlags(0, fmt::format("{}##Time {} {}", binaryGroupItem.name, size_t(id), b).c_str(),
3142 reinterpret_cast<int*>(&_binaryOutputRegister.at(b).timeField),
3143 binaryGroupItem.flagsValue,
3144 binaryGroupItem.isEnabled(_sensorModel, _binaryOutputRegister.at(b), static_cast<uint32_t>(_binaryOutputRegister.at(b).timeField)),
3145 binaryGroupItem.toggleFields);
3146 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && binaryGroupItem.tooltip != nullptr)
3147 {
3148 ImGui::BeginTooltip();
3149 binaryGroupItem.tooltip();
3150 ImGui::EndTooltip();
3151 }
3152 }
3153 if (i < _binaryGroupIMU.size())
3154 {
3155 const auto& binaryGroupItem = _binaryGroupIMU.at(i);
3156 CheckboxFlags(1, fmt::format("{}##IMU {} {}", binaryGroupItem.name, size_t(id), b).c_str(),
3157 reinterpret_cast<int*>(&_binaryOutputRegister.at(b).imuField),
3158 binaryGroupItem.flagsValue,
3159 binaryGroupItem.isEnabled(_sensorModel, _binaryOutputRegister.at(b), static_cast<uint32_t>(_binaryOutputRegister.at(b).imuField)),
3160 binaryGroupItem.toggleFields);
3161 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && binaryGroupItem.tooltip != nullptr)
3162 {
3163 ImGui::BeginTooltip();
3164 binaryGroupItem.tooltip();
3165 ImGui::EndTooltip();
3166 }
3167 }
3168 if (i < _binaryGroupGNSS.size())
3169 {
3170 const auto& binaryGroupItem = _binaryGroupGNSS.at(i);
3171 CheckboxFlags(2, fmt::format("{}##GNSS1 {} {}", binaryGroupItem.name, size_t(id), b).c_str(),
3172 reinterpret_cast<int*>(&_binaryOutputRegister.at(b).gpsField),
3173 binaryGroupItem.flagsValue,
3174 binaryGroupItem.isEnabled(_sensorModel, _binaryOutputRegister.at(b), static_cast<uint32_t>(_binaryOutputRegister.at(b).gpsField)),
3175 binaryGroupItem.toggleFields);
3176 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && binaryGroupItem.tooltip != nullptr)
3177 {
3178 ImGui::BeginTooltip();
3179 binaryGroupItem.tooltip();
3180 ImGui::EndTooltip();
3181 }
3182 }
3183 if (i < _binaryGroupAttitude.size())
3184 {
3185 const auto& binaryGroupItem = _binaryGroupAttitude.at(i);
3186 CheckboxFlags(3, fmt::format("{}##Attitude {} {}", binaryGroupItem.name, size_t(id), b).c_str(),
3187 reinterpret_cast<int*>(&_binaryOutputRegister.at(b).attitudeField),
3188 binaryGroupItem.flagsValue,
3189 binaryGroupItem.isEnabled(_sensorModel, _binaryOutputRegister.at(b), static_cast<uint32_t>(_binaryOutputRegister.at(b).attitudeField)),
3190 binaryGroupItem.toggleFields);
3191 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && binaryGroupItem.tooltip != nullptr)
3192 {
3193 ImGui::BeginTooltip();
3194 binaryGroupItem.tooltip();
3195 ImGui::EndTooltip();
3196 }
3197 }
3198 if (i < _binaryGroupINS.size())
3199 {
3200 const auto& binaryGroupItem = _binaryGroupINS.at(i);
3201 CheckboxFlags(4, fmt::format("{}##INS {} {}", binaryGroupItem.name, size_t(id), b).c_str(),
3202 reinterpret_cast<int*>(&_binaryOutputRegister.at(b).insField),
3203 binaryGroupItem.flagsValue,
3204 binaryGroupItem.isEnabled(_sensorModel, _binaryOutputRegister.at(b), static_cast<uint32_t>(_binaryOutputRegister.at(b).insField)),
3205 binaryGroupItem.toggleFields);
3206 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && binaryGroupItem.tooltip != nullptr)
3207 {
3208 ImGui::BeginTooltip();
3209 binaryGroupItem.tooltip();
3210 ImGui::EndTooltip();
3211 }
3212 }
3213 if (i < _binaryGroupGNSS.size())
3214 {
3215 const auto& binaryGroupItem = _binaryGroupGNSS.at(i);
3216 CheckboxFlags(5, fmt::format("{}##GNSS2 {} {}", binaryGroupItem.name, size_t(id), b).c_str(),
3217 reinterpret_cast<int*>(&_binaryOutputRegister.at(b).gps2Field),
3218 binaryGroupItem.flagsValue,
3219 binaryGroupItem.isEnabled(_sensorModel, _binaryOutputRegister.at(b), static_cast<uint32_t>(_binaryOutputRegister.at(b).gps2Field)),
3220 binaryGroupItem.toggleFields);
3221 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && binaryGroupItem.tooltip != nullptr)
3222 {
3223 ImGui::BeginTooltip();
3224 binaryGroupItem.tooltip();
3225 ImGui::EndTooltip();
3226 }
3227 }
3228 }
3229
3230 ImGui::EndTable();
3231 }
3232
3233 ImGui::TreePop();
3234 }
3235 }
3236
3237 if (auto binaryOutputRegisterMerge = static_cast<int>(_binaryOutputRegisterMerge);
3238 ImGui::Combo(fmt::format("Merge Binary outputs").c_str(), &binaryOutputRegisterMerge, "None\0"
3239 "1 <-> 2\0"
3240 "1 <-> 3\0"
3241 "2 <-> 3\0\0"))
3242 {
3243 _binaryOutputRegisterMerge = static_cast<decltype(_binaryOutputRegisterMerge)>(binaryOutputRegisterMerge);
3244 flow::ApplyChanges();
3245 switch (_binaryOutputRegisterMerge)
3246 {
3247 case BinaryRegisterMerge::Output1_Output2:
3248 _binaryOutputRegister.at(1).rateDivisor = _binaryOutputRegister.at(0).rateDivisor;
3249 _binaryOutputSelectedFrequency.at(1) = _binaryOutputSelectedFrequency.at(0);
3250 _binaryOutputRegister.at(1).asyncMode = _binaryOutputRegister.at(0).asyncMode;
3251 outputPins.at(2).deleteLinks();
3252 outputPins.at(1).type = Pin::Type::Flow;
3253 outputPins.at(2).type = Pin::Type::None;
3254 outputPins.at(3).type = Pin::Type::Flow;
3255 break;
3256 case BinaryRegisterMerge::Output1_Output3:
3257 _binaryOutputRegister.at(2).rateDivisor = _binaryOutputRegister.at(0).rateDivisor;
3258 _binaryOutputSelectedFrequency.at(2) = _binaryOutputSelectedFrequency.at(0);
3259 _binaryOutputRegister.at(2).asyncMode = _binaryOutputRegister.at(0).asyncMode;
3260 outputPins.at(3).deleteLinks();
3261 outputPins.at(1).type = Pin::Type::Flow;
3262 outputPins.at(2).type = Pin::Type::Flow;
3263 outputPins.at(3).type = Pin::Type::None;
3264 break;
3265 case BinaryRegisterMerge::Output2_Output3:
3266 _binaryOutputRegister.at(2).rateDivisor = _binaryOutputRegister.at(1).rateDivisor;
3267 _binaryOutputSelectedFrequency.at(2) = _binaryOutputSelectedFrequency.at(1);
3268 _binaryOutputRegister.at(2).asyncMode = _binaryOutputRegister.at(1).asyncMode;
3269 outputPins.at(3).deleteLinks();
3270 outputPins.at(1).type = Pin::Type::Flow;
3271 outputPins.at(2).type = Pin::Type::Flow;
3272 outputPins.at(3).type = Pin::Type::None;
3273 break;
3274 default:
3275 break;
3276 }
3277 }
3278 ImGui::SameLine();
3279 NAV::gui::widgets::HelpMarker("VectorNav sensors have a buffer overflow if packages get too big (SatInfo/RawMeas selected and a lot of satellites). "
3280 "A workaround is, to split the data into different Binary Outputs. "
3281 "This option merges the outputs into a single observation.");
3282
3283 // TODO: Add Gui Config for NMEA output - User manual VN-310 - 8.2.14 (p 103)
3284 }
3285
3286 // ###########################################################################################################
3287 // IMU SUBSYSTEM
3288 // ###########################################################################################################
3289
3290 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
3291 if (ImGui::CollapsingHeader(fmt::format("IMU Subsystem##{}", size_t(id)).c_str()))
3292 {
3293 if (ImGui::TreeNode(fmt::format("Reference Frame Rotation##{}", size_t(id)).c_str()))
3294 {
3295 ImGui::TextUnformatted("Allows the measurements of the VN-310E to be rotated into a different reference frame.");
3296 ImGui::SameLine();
3297 gui::widgets::HelpMarker("This register contains a transformation matrix that allows for the transformation of measured acceleration, "
3298 "magnetic, and angular rates from the body frame of the VN-310E to any other arbitrary frame of reference. "
3299 "The use of this register allows for the sensor to be placed in any arbitrary orientation with respect to the "
3300 "user's desired body coordinate frame. This register can also be used to correct for any orientation errors due "
3301 "to mounting the VN-310E on the user's vehicle or platform.\n\n"
3302 "(X Y Z)_U = C * (X Y Z)_B\n\n"
3303 "The variables (X Y Z)_B are a measured parameter such as acceleration in the body reference frame with "
3304 "respect to the VectorNav. The variables (X Y Z)_U are a measured parameter such as acceleration in the user's "
3305 "frame of reference. The reference frame rotation register Thus needs to be loaded with the transformation "
3306 "matrix that will transform measurements from the body reference frame of the VectorNav to the desired user "
3307 "frame of reference.");
3308 if (_sensorModel == VectorNavModel::VN310)
3309 {
3310 ImGui::SameLine();
3311 gui::widgets::HelpMarker("The reference frame rotation is performed on all vector measurements prior to entering the INS "
3312 "filter. As such, changing this register while the attitude filter is running will lead to unexpected "
3313 "behavior in the INS output. To prevent this, the register is cached on startup and changes will "
3314 "not take effect during runtime. After setting the reference frame rotation register to its new value, "
3315 "send a write settings command and then reset the VN-310E. This will allow the INS filter to "
3316 "startup with the newly set reference frame rotation.",
3317 "(!)");
3318 }
3319 ImGui::SameLine();
3320 gui::widgets::HelpMarker("The matrix C in the Reference Frame Rotation Register must be an orthonormal, right-handed"
3321 " matrix. The sensor will output an error if the tolerance is not within 1e-5. The sensor will also"
3322 " report an error if any of the parameters are greater than 1 or less than -1.",
3323 "(!)");
3324
3325 ImGui::TextUnformatted("Rotation Angles [deg]");
3326 ImGui::SameLine();
3327 gui::widgets::HelpMarker("The rotation happens in ZYX Order");
3328 ImGui::Indent();
3329
3330 Eigen::Matrix3d dcm;
3331 dcm << _referenceFrameRotationMatrix.e00, _referenceFrameRotationMatrix.e01, _referenceFrameRotationMatrix.e02,
3332 _referenceFrameRotationMatrix.e10, _referenceFrameRotationMatrix.e11, _referenceFrameRotationMatrix.e12,
3333 _referenceFrameRotationMatrix.e20, _referenceFrameRotationMatrix.e21, _referenceFrameRotationMatrix.e22;
3334 auto b_Quat_p = Eigen::Quaterniond(dcm);
3335
3336 Eigen::Vector3d eulerRot = rad2deg(trafo::quat2eulerZYX(b_Quat_p.conjugate()));
3337 std::array<float, 3> imuRot = { static_cast<float>(eulerRot.x()), static_cast<float>(eulerRot.y()), static_cast<float>(eulerRot.z()) };
3338 if (ImGui::InputFloat3(fmt::format("##rotationAngles{}", size_t(id)).c_str(), imuRot.data()))
3339 {
3340 // (-180:180] x (-90:90] x (-180:180]
3341 imuRot.at(0) = std::max(imuRot.at(0), -179.9999F);
3342 imuRot.at(0) = std::min(imuRot.at(0), 180.0F);
3343 imuRot.at(1) = std::max(imuRot.at(1), -89.9999F);
3344 imuRot.at(1) = std::min(imuRot.at(1), 90.0F);
3345 imuRot.at(2) = std::max(imuRot.at(2), -179.9999F);
3346 imuRot.at(2) = std::min(imuRot.at(2), 180.0F);
3347
3348 Eigen::Matrix3f dcmf = trafo::b_Quat_p(deg2rad(imuRot.at(0)), deg2rad(imuRot.at(1)), deg2rad(imuRot.at(2))).toRotationMatrix().cast<float>();
3349 _referenceFrameRotationMatrix = vn::math::mat3f(dcmf(0, 0), dcmf(0, 1), dcmf(0, 2),
3350 dcmf(1, 0), dcmf(1, 1), dcmf(1, 2),
3351 dcmf(2, 0), dcmf(2, 1), dcmf(2, 2));
3352 LOG_DEBUG("{}: referenceFrameRotationMatrix changed to {}", nameId(), _referenceFrameRotationMatrix);
3353 flow::ApplyChanges();
3354 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3355 {
3356 try
3357 {
3358 _vs.writeReferenceFrameRotation(_referenceFrameRotationMatrix);
3359 }
3360 catch (const std::exception& e)
3361 {
3362 LOG_ERROR("{}: Could not configure the referenceFrameRotationMatrix: {}", nameId(), e.what());
3363 doDeinitialize();
3364 }
3365 }
3366 else
3367 {
3368 doDeinitialize();
3369 }
3370 }
3371
3372 ImGui::Unindent();
3373 ImGui::TextUnformatted("Rotation Matrix C");
3374 ImGui::Indent();
3375
3376 std::array<float, 3> row = { _referenceFrameRotationMatrix.e00, _referenceFrameRotationMatrix.e01, _referenceFrameRotationMatrix.e02 };
3377 if (ImGui::InputFloat3(fmt::format("##referenceFrameRotationMatrix row 0 {}", size_t(id)).c_str(), row.data(), "%.2f"))
3378 {
3379 _referenceFrameRotationMatrix.e00 = row.at(0);
3380 _referenceFrameRotationMatrix.e01 = row.at(1);
3381 _referenceFrameRotationMatrix.e02 = row.at(2);
3382 LOG_DEBUG("{}: referenceFrameRotationMatrix changed to {}", nameId(), _referenceFrameRotationMatrix);
3383 flow::ApplyChanges();
3384 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3385 {
3386 try
3387 {
3388 _vs.writeReferenceFrameRotation(_referenceFrameRotationMatrix);
3389 }
3390 catch (const std::exception& e)
3391 {
3392 LOG_ERROR("{}: Could not configure the referenceFrameRotationMatrix: {}", nameId(), e.what());
3393 doDeinitialize();
3394 }
3395 }
3396 else
3397 {
3398 doDeinitialize();
3399 }
3400 }
3401 row = { _referenceFrameRotationMatrix.e10, _referenceFrameRotationMatrix.e11, _referenceFrameRotationMatrix.e12 };
3402 if (ImGui::InputFloat3(fmt::format("##referenceFrameRotationMatrix row 1 {}", size_t(id)).c_str(), row.data(), "%.2f"))
3403 {
3404 _referenceFrameRotationMatrix.e10 = row.at(0);
3405 _referenceFrameRotationMatrix.e11 = row.at(1);
3406 _referenceFrameRotationMatrix.e12 = row.at(2);
3407 LOG_DEBUG("{}: referenceFrameRotationMatrix changed to {}", nameId(), _referenceFrameRotationMatrix);
3408 flow::ApplyChanges();
3409 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3410 {
3411 try
3412 {
3413 _vs.writeReferenceFrameRotation(_referenceFrameRotationMatrix);
3414 }
3415 catch (const std::exception& e)
3416 {
3417 LOG_ERROR("{}: Could not configure the referenceFrameRotationMatrix: {}", nameId(), e.what());
3418 doDeinitialize();
3419 }
3420 }
3421 else
3422 {
3423 doDeinitialize();
3424 }
3425 }
3426 row = { _referenceFrameRotationMatrix.e20, _referenceFrameRotationMatrix.e21, _referenceFrameRotationMatrix.e22 };
3427 if (ImGui::InputFloat3(fmt::format("##referenceFrameRotationMatrix row 2 {}", size_t(id)).c_str(), row.data(), "%.2f"))
3428 {
3429 _referenceFrameRotationMatrix.e20 = row.at(0);
3430 _referenceFrameRotationMatrix.e21 = row.at(1);
3431 _referenceFrameRotationMatrix.e22 = row.at(2);
3432 LOG_DEBUG("{}: referenceFrameRotationMatrix changed to {}", nameId(), _referenceFrameRotationMatrix);
3433 flow::ApplyChanges();
3434 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3435 {
3436 try
3437 {
3438 _vs.writeReferenceFrameRotation(_referenceFrameRotationMatrix);
3439 }
3440 catch (const std::exception& e)
3441 {
3442 LOG_ERROR("{}: Could not configure the referenceFrameRotationMatrix: {}", nameId(), e.what());
3443 doDeinitialize();
3444 }
3445 }
3446 else
3447 {
3448 doDeinitialize();
3449 }
3450 }
3451
3452 ImGui::Unindent();
3453 ImGui::TreePop();
3454 }
3455
3456 if (ImGui::TreeNode(fmt::format("IMU Filtering Configuration##{}", size_t(id)).c_str()))
3457 {
3458 ImGui::TextUnformatted("This register allows the user to configure the FIR filtering which is applied to the IMU measurements.\n"
3459 "The filter is a uniformly-weighted moving window (boxcar) filter, also known as a 'moving-average' filter.\n"
3460 "Its window size can be adjusted with respect to the internal IMU frequency (800 Hz).");
3461
3462 if (ImGui::Checkbox(fmt::format("Couple the Imu-Filter's rate to the output rate##{}", size_t(id)).c_str(), &_coupleImuRateOutput))
3463 {
3464 LOG_DEBUG("{}: coupleImuRateOutput changed to {}", nameId(), _coupleImuRateOutput);
3465 flow::ApplyChanges();
3466 }
3467 ImGui::SameLine();
3468 gui::widgets::HelpMarker("If the window-size of the IMU's moving-average filter is smaller than the output rate "
3469 "divisor, some samples are lost. This results in a low output rate that still contains "
3470 "noise due to high frequencies. Therefore, it is recommended to couple the ImuFilter's "
3471 "rate to the output rate.");
3472
3473 int magWindowSize = _imuFilteringConfigurationRegister.magWindowSize;
3474 if (_coupleImuRateOutput)
3475 {
3476 ImGui::BeginDisabled();
3477 }
3478 if (ImGui::InputInt(fmt::format("Mag Window Size##{}", size_t(id)).c_str(), &magWindowSize))
3479 {
3480 if (magWindowSize < 0)
3481 {
3482 magWindowSize = 0;
3483 }
3484 else if (magWindowSize > std::numeric_limits<uint16_t>::max())
3485 {
3486 magWindowSize = std::numeric_limits<uint16_t>::max();
3487 }
3488 _imuFilteringConfigurationRegister.magWindowSize = static_cast<uint16_t>(magWindowSize);
3489 LOG_DEBUG("{}: imuFilteringConfigurationRegister.magWindowSize changed to {}", nameId(), _imuFilteringConfigurationRegister.magWindowSize);
3490 flow::ApplyChanges();
3491 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3492 {
3493 try
3494 {
3495 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3496 }
3497 catch (const std::exception& e)
3498 {
3499 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3500 doDeinitialize();
3501 }
3502 }
3503 else
3504 {
3505 doDeinitialize();
3506 }
3507 }
3508 ImGui::SameLine();
3509 gui::widgets::HelpMarker("The WindowSize parameters for each sensor define the number of samples at the IMU rate (default 800Hz) "
3510 "which will be averaged for each output measurement.");
3511
3512 int accelWindowSize = _imuFilteringConfigurationRegister.accelWindowSize;
3513 if (ImGui::InputInt(fmt::format("Accel Window Size##{}", size_t(id)).c_str(), &accelWindowSize))
3514 {
3515 if (accelWindowSize < 0)
3516 {
3517 accelWindowSize = 0;
3518 }
3519 else if (accelWindowSize > std::numeric_limits<uint16_t>::max())
3520 {
3521 accelWindowSize = std::numeric_limits<uint16_t>::max();
3522 }
3523 _imuFilteringConfigurationRegister.accelWindowSize = static_cast<uint16_t>(accelWindowSize);
3524 LOG_DEBUG("{}: imuFilteringConfigurationRegister.accelWindowSize changed to {}", nameId(), _imuFilteringConfigurationRegister.accelWindowSize);
3525 flow::ApplyChanges();
3526 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3527 {
3528 try
3529 {
3530 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3531 }
3532 catch (const std::exception& e)
3533 {
3534 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3535 doDeinitialize();
3536 }
3537 }
3538 else
3539 {
3540 doDeinitialize();
3541 }
3542 }
3543 ImGui::SameLine();
3544 gui::widgets::HelpMarker("The WindowSize parameters for each sensor define the number of samples at the IMU rate (default 800Hz) "
3545 "which will be averaged for each output measurement.");
3546
3547 int gyroWindowSize = _imuFilteringConfigurationRegister.gyroWindowSize;
3548 if (ImGui::InputInt(fmt::format("Gyro Window Size##{}", size_t(id)).c_str(), &gyroWindowSize))
3549 {
3550 if (gyroWindowSize < 0)
3551 {
3552 gyroWindowSize = 0;
3553 }
3554 else if (gyroWindowSize > std::numeric_limits<uint16_t>::max())
3555 {
3556 gyroWindowSize = std::numeric_limits<uint16_t>::max();
3557 }
3558 _imuFilteringConfigurationRegister.gyroWindowSize = static_cast<uint16_t>(gyroWindowSize);
3559 LOG_DEBUG("{}: imuFilteringConfigurationRegister.gyroWindowSize changed to {}", nameId(), _imuFilteringConfigurationRegister.gyroWindowSize);
3560 flow::ApplyChanges();
3561 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3562 {
3563 try
3564 {
3565 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3566 }
3567 catch (const std::exception& e)
3568 {
3569 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3570 doDeinitialize();
3571 }
3572 }
3573 else
3574 {
3575 doDeinitialize();
3576 }
3577 }
3578 ImGui::SameLine();
3579 gui::widgets::HelpMarker("The WindowSize parameters for each sensor define the number of samples at the IMU rate (default 800Hz) "
3580 "which will be averaged for each output measurement.");
3581
3582 int tempWindowSize = _imuFilteringConfigurationRegister.tempWindowSize;
3583 if (ImGui::InputInt(fmt::format("Temp Window Size##{}", size_t(id)).c_str(), &tempWindowSize))
3584 {
3585 if (tempWindowSize < 0)
3586 {
3587 tempWindowSize = 0;
3588 }
3589 else if (tempWindowSize > std::numeric_limits<uint16_t>::max())
3590 {
3591 tempWindowSize = std::numeric_limits<uint16_t>::max();
3592 }
3593 _imuFilteringConfigurationRegister.tempWindowSize = static_cast<uint16_t>(tempWindowSize);
3594 LOG_DEBUG("{}: imuFilteringConfigurationRegister.tempWindowSize changed to {}", nameId(), _imuFilteringConfigurationRegister.tempWindowSize);
3595 flow::ApplyChanges();
3596 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3597 {
3598 try
3599 {
3600 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3601 }
3602 catch (const std::exception& e)
3603 {
3604 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3605 doDeinitialize();
3606 }
3607 }
3608 else
3609 {
3610 doDeinitialize();
3611 }
3612 }
3613 ImGui::SameLine();
3614 gui::widgets::HelpMarker("The WindowSize parameters for each sensor define the number of samples at the IMU rate (default 800Hz) "
3615 "which will be averaged for each output measurement.");
3616
3617 int presWindowSize = _imuFilteringConfigurationRegister.presWindowSize;
3618 if (ImGui::InputInt(fmt::format("Pres Window Size##{}", size_t(id)).c_str(), &presWindowSize))
3619 {
3620 if (presWindowSize < 0)
3621 {
3622 presWindowSize = 0;
3623 }
3624 else if (presWindowSize > std::numeric_limits<uint16_t>::max())
3625 {
3626 presWindowSize = std::numeric_limits<uint16_t>::max();
3627 }
3628 _imuFilteringConfigurationRegister.presWindowSize = static_cast<uint16_t>(presWindowSize);
3629 LOG_DEBUG("{}: imuFilteringConfigurationRegister.presWindowSize changed to {}", nameId(), _imuFilteringConfigurationRegister.presWindowSize);
3630 flow::ApplyChanges();
3631 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3632 {
3633 try
3634 {
3635 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3636 }
3637 catch (const std::exception& e)
3638 {
3639 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3640 doDeinitialize();
3641 }
3642 }
3643 else
3644 {
3645 doDeinitialize();
3646 }
3647 }
3648 ImGui::SameLine();
3649 gui::widgets::HelpMarker("The WindowSize parameters for each sensor define the number of samples at the IMU rate (default 800Hz) "
3650 "which will be averaged for each output measurement.");
3651 if (_coupleImuRateOutput)
3652 {
3653 ImGui::EndDisabled();
3654 }
3655
3656 static constexpr std::array<std::pair<vn::protocol::uart::FilterMode, const char*>, 4> imuFilteringConfigurationFilterModes = {
3657 { { vn::protocol::uart::FilterMode::FILTERMODE_NOFILTERING, "No Filtering" },
3658 { vn::protocol::uart::FilterMode::FILTERMODE_ONLYRAW, "Filtering performed only on raw uncompensated IMU measurements." },
3659 { vn::protocol::uart::FilterMode::FILTERMODE_ONLYCOMPENSATED, "Filtering performed only on compensated IMU measurements." },
3660 { vn::protocol::uart::FilterMode::FILTERMODE_BOTH, "Filtering performed on both uncompensated and compensated IMU measurements." } }
3661 };
3662 if (ImGui::BeginCombo(fmt::format("Mag Filter Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.magFilterMode).c_str()))
3663 {
3664 for (const auto& imuFilteringConfigurationFilterMode : imuFilteringConfigurationFilterModes)
3665 {
3666 const bool isSelected = (_imuFilteringConfigurationRegister.magFilterMode == imuFilteringConfigurationFilterMode.first);
3667 if (ImGui::Selectable(vn::protocol::uart::str(imuFilteringConfigurationFilterMode.first).c_str(), isSelected))
3668 {
3669 _imuFilteringConfigurationRegister.magFilterMode = imuFilteringConfigurationFilterMode.first;
3670 LOG_DEBUG("{}: imuFilteringConfigurationRegister.magFilterMode changed to {}", nameId(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.magFilterMode));
3671 flow::ApplyChanges();
3672 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3673 {
3674 try
3675 {
3676 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3677 }
3678 catch (const std::exception& e)
3679 {
3680 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3681 doDeinitialize();
3682 }
3683 }
3684 else
3685 {
3686 doDeinitialize();
3687 }
3688 }
3689 if (ImGui::IsItemHovered())
3690 {
3691 ImGui::BeginTooltip();
3692 ImGui::TextUnformatted(imuFilteringConfigurationFilterMode.second);
3693 ImGui::EndTooltip();
3694 }
3695
3696 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
3697 {
3698 ImGui::SetItemDefaultFocus();
3699 }
3700 }
3701 ImGui::EndCombo();
3702 }
3703 ImGui::SameLine();
3704 gui::widgets::HelpMarker("The FilterMode parameters for each sensor select which output quantities the filtering should be applied to. "
3705 "Filtering can be applied to either the uncompensated IMU measurements, compensated (HSI and biases "
3706 "compensated by onboard filters, if applicable), or both.");
3707
3708 if (ImGui::BeginCombo(fmt::format("Accel Filter Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.accelFilterMode).c_str()))
3709 {
3710 for (const auto& imuFilteringConfigurationFilterMode : imuFilteringConfigurationFilterModes)
3711 {
3712 const bool isSelected = (_imuFilteringConfigurationRegister.accelFilterMode == imuFilteringConfigurationFilterMode.first);
3713 if (ImGui::Selectable(vn::protocol::uart::str(imuFilteringConfigurationFilterMode.first).c_str(), isSelected))
3714 {
3715 _imuFilteringConfigurationRegister.accelFilterMode = imuFilteringConfigurationFilterMode.first;
3716 LOG_DEBUG("{}: imuFilteringConfigurationRegister.accelFilterMode changed to {}", nameId(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.accelFilterMode));
3717 flow::ApplyChanges();
3718 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3719 {
3720 try
3721 {
3722 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3723 }
3724 catch (const std::exception& e)
3725 {
3726 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3727 doDeinitialize();
3728 }
3729 }
3730 else
3731 {
3732 doDeinitialize();
3733 }
3734 }
3735 if (ImGui::IsItemHovered())
3736 {
3737 ImGui::BeginTooltip();
3738 ImGui::TextUnformatted(imuFilteringConfigurationFilterMode.second);
3739 ImGui::EndTooltip();
3740 }
3741
3742 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
3743 {
3744 ImGui::SetItemDefaultFocus();
3745 }
3746 }
3747 ImGui::EndCombo();
3748 }
3749 ImGui::SameLine();
3750 gui::widgets::HelpMarker("The FilterMode parameters for each sensor select which output quantities the filtering should be applied to. "
3751 "Filtering can be applied to either the uncompensated IMU measurements, compensated (HSI and biases "
3752 "compensated by onboard filters, if applicable), or both.");
3753
3754 if (ImGui::BeginCombo(fmt::format("Gyro Filter Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.gyroFilterMode).c_str()))
3755 {
3756 for (const auto& imuFilteringConfigurationFilterMode : imuFilteringConfigurationFilterModes)
3757 {
3758 const bool isSelected = (_imuFilteringConfigurationRegister.gyroFilterMode == imuFilteringConfigurationFilterMode.first);
3759 if (ImGui::Selectable(vn::protocol::uart::str(imuFilteringConfigurationFilterMode.first).c_str(), isSelected))
3760 {
3761 _imuFilteringConfigurationRegister.gyroFilterMode = imuFilteringConfigurationFilterMode.first;
3762 LOG_DEBUG("{}: imuFilteringConfigurationRegister.gyroFilterMode changed to {}", nameId(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.gyroFilterMode));
3763 flow::ApplyChanges();
3764 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3765 {
3766 try
3767 {
3768 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3769 }
3770 catch (const std::exception& e)
3771 {
3772 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3773 doDeinitialize();
3774 }
3775 }
3776 else
3777 {
3778 doDeinitialize();
3779 }
3780 }
3781 if (ImGui::IsItemHovered())
3782 {
3783 ImGui::BeginTooltip();
3784 ImGui::TextUnformatted(imuFilteringConfigurationFilterMode.second);
3785 ImGui::EndTooltip();
3786 }
3787
3788 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
3789 {
3790 ImGui::SetItemDefaultFocus();
3791 }
3792 }
3793 ImGui::EndCombo();
3794 }
3795 ImGui::SameLine();
3796 gui::widgets::HelpMarker("The FilterMode parameters for each sensor select which output quantities the filtering should be applied to. "
3797 "Filtering can be applied to either the uncompensated IMU measurements, compensated (HSI and biases "
3798 "compensated by onboard filters, if applicable), or both.");
3799
3800 if (ImGui::BeginCombo(fmt::format("Temp Filter Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.tempFilterMode).c_str()))
3801 {
3802 for (const auto& imuFilteringConfigurationFilterMode : imuFilteringConfigurationFilterModes)
3803 {
3804 const bool isSelected = (_imuFilteringConfigurationRegister.tempFilterMode == imuFilteringConfigurationFilterMode.first);
3805 if (ImGui::Selectable(vn::protocol::uart::str(imuFilteringConfigurationFilterMode.first).c_str(), isSelected))
3806 {
3807 _imuFilteringConfigurationRegister.tempFilterMode = imuFilteringConfigurationFilterMode.first;
3808 LOG_DEBUG("{}: imuFilteringConfigurationRegister.tempFilterMode changed to {}", nameId(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.tempFilterMode));
3809 flow::ApplyChanges();
3810 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3811 {
3812 try
3813 {
3814 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3815 }
3816 catch (const std::exception& e)
3817 {
3818 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3819 doDeinitialize();
3820 }
3821 }
3822 else
3823 {
3824 doDeinitialize();
3825 }
3826 }
3827 if (ImGui::IsItemHovered())
3828 {
3829 ImGui::BeginTooltip();
3830 ImGui::TextUnformatted(imuFilteringConfigurationFilterMode.second);
3831 ImGui::EndTooltip();
3832 }
3833
3834 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
3835 {
3836 ImGui::SetItemDefaultFocus();
3837 }
3838 }
3839 ImGui::EndCombo();
3840 }
3841 ImGui::SameLine();
3842 gui::widgets::HelpMarker("The FilterMode parameters for each sensor select which output quantities the filtering should be applied to. "
3843 "Filtering can be applied to either the uncompensated IMU measurements, compensated (HSI and biases "
3844 "compensated by onboard filters, if applicable), or both.");
3845
3846 if (ImGui::BeginCombo(fmt::format("Pres Filter Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.presFilterMode).c_str()))
3847 {
3848 for (const auto& imuFilteringConfigurationFilterMode : imuFilteringConfigurationFilterModes)
3849 {
3850 const bool isSelected = (_imuFilteringConfigurationRegister.presFilterMode == imuFilteringConfigurationFilterMode.first);
3851 if (ImGui::Selectable(vn::protocol::uart::str(imuFilteringConfigurationFilterMode.first).c_str(), isSelected))
3852 {
3853 _imuFilteringConfigurationRegister.presFilterMode = imuFilteringConfigurationFilterMode.first;
3854 LOG_DEBUG("{}: imuFilteringConfigurationRegister.presFilterMode changed to {}", nameId(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.presFilterMode));
3855 flow::ApplyChanges();
3856 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3857 {
3858 try
3859 {
3860 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3861 }
3862 catch (const std::exception& e)
3863 {
3864 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3865 doDeinitialize();
3866 }
3867 }
3868 else
3869 {
3870 doDeinitialize();
3871 }
3872 }
3873 if (ImGui::IsItemHovered())
3874 {
3875 ImGui::BeginTooltip();
3876 ImGui::TextUnformatted(imuFilteringConfigurationFilterMode.second);
3877 ImGui::EndTooltip();
3878 }
3879
3880 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
3881 {
3882 ImGui::SetItemDefaultFocus();
3883 }
3884 }
3885 ImGui::EndCombo();
3886 }
3887 ImGui::SameLine();
3888 gui::widgets::HelpMarker("The FilterMode parameters for each sensor select which output quantities the filtering should be applied to. "
3889 "Filtering can be applied to either the uncompensated IMU measurements, compensated (HSI and biases "
3890 "compensated by onboard filters, if applicable), or both.");
3891
3892 ImGui::TreePop();
3893 }
3894
3895 if (ImGui::TreeNode(fmt::format("Delta Theta and Delta Velocity Configuration##{}", size_t(id)).c_str()))
3896 {
3897 ImGui::TextUnformatted("The Delta Theta and Delta Velocity Configuration register allows configuration of the onboard coning and\n"
3898 "sculling used to generate integrated motion values from the angular rate and acceleration IMU quantities.\n"
3899 "The fully-coupled coning and sculling integrals are computed at the IMU sample rate (nominal 400 Hz).");
3900
3901 static constexpr std::array<std::pair<vn::protocol::uart::IntegrationFrame, const char*>, 2> deltaThetaAndDeltaVelocityConfigurationIntegrationFrames = {
3902 { { vn::protocol::uart::IntegrationFrame::INTEGRATIONFRAME_BODY, "Body frame" },
3903 { vn::protocol::uart::IntegrationFrame::INTEGRATIONFRAME_NED, "NED frame" } }
3904 };
3905 if (ImGui::BeginCombo(fmt::format("Integration Frame##{}", size_t(id)).c_str(), vn::protocol::uart::str(_deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame).c_str()))
3906 {
3907 for (const auto& deltaThetaAndDeltaVelocityConfigurationIntegrationFrame : deltaThetaAndDeltaVelocityConfigurationIntegrationFrames)
3908 {
3909 const bool isSelected = (_deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame == deltaThetaAndDeltaVelocityConfigurationIntegrationFrame.first);
3910 if (ImGui::Selectable(vn::protocol::uart::str(deltaThetaAndDeltaVelocityConfigurationIntegrationFrame.first).c_str(), isSelected))
3911 {
3912 _deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame = deltaThetaAndDeltaVelocityConfigurationIntegrationFrame.first;
3913 LOG_DEBUG("{}: deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame changed to {}", nameId(), vn::protocol::uart::str(_deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame));
3914 flow::ApplyChanges();
3915 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3916 {
3917 try
3918 {
3919 _vs.writeDeltaThetaAndDeltaVelocityConfiguration(_deltaThetaAndDeltaVelocityConfigurationRegister);
3920 }
3921 catch (const std::exception& e)
3922 {
3923 LOG_ERROR("{}: Could not configure the deltaThetaAndDeltaVelocityConfigurationRegister: {}", nameId(), e.what());
3924 doDeinitialize();
3925 }
3926 }
3927 else
3928 {
3929 doDeinitialize();
3930 }
3931 }
3932 if (ImGui::IsItemHovered())
3933 {
3934 ImGui::BeginTooltip();
3935 ImGui::TextUnformatted(deltaThetaAndDeltaVelocityConfigurationIntegrationFrame.second);
3936 ImGui::EndTooltip();
3937 }
3938
3939 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
3940 {
3941 ImGui::SetItemDefaultFocus();
3942 }
3943 }
3944 ImGui::EndCombo();
3945 }
3946 ImGui::SameLine();
3947 gui::widgets::HelpMarker("The IntegrationFrame register setting selects the reference frame used for coning and sculling. Note that "
3948 "using any frame other than the body frame will rely on the onboard Kalman filter's attitude estimate. The "
3949 "factory default state is to integrate in the sensor body frame.");
3950
3951 static constexpr std::array<std::pair<vn::protocol::uart::CompensationMode, const char*>, 2> deltaThetaAndDeltaVelocityConfigurationGyroCompensationModes = {
3952 { { vn::protocol::uart::CompensationMode::COMPENSATIONMODE_NONE, "None" },
3953 { vn::protocol::uart::CompensationMode::COMPENSATIONMODE_BIAS, "Bias" } }
3954 };
3955 if (ImGui::BeginCombo(fmt::format("Gyro Compensation##{}", size_t(id)).c_str(), vn::protocol::uart::str(_deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation).c_str()))
3956 {
3957 for (const auto& deltaThetaAndDeltaVelocityConfigurationGyroCompensationMode : deltaThetaAndDeltaVelocityConfigurationGyroCompensationModes)
3958 {
3959 const bool isSelected = (_deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation == deltaThetaAndDeltaVelocityConfigurationGyroCompensationMode.first);
3960 if (ImGui::Selectable(vn::protocol::uart::str(deltaThetaAndDeltaVelocityConfigurationGyroCompensationMode.first).c_str(), isSelected))
3961 {
3962 _deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation = deltaThetaAndDeltaVelocityConfigurationGyroCompensationMode.first;
3963 LOG_DEBUG("{}: deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation changed to {}", nameId(), vn::protocol::uart::str(_deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation));
3964 flow::ApplyChanges();
3965 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3966 {
3967 try
3968 {
3969 _vs.writeDeltaThetaAndDeltaVelocityConfiguration(_deltaThetaAndDeltaVelocityConfigurationRegister);
3970 }
3971 catch (const std::exception& e)
3972 {
3973 LOG_ERROR("{}: Could not configure the deltaThetaAndDeltaVelocityConfigurationRegister: {}", nameId(), e.what());
3974 doDeinitialize();
3975 }
3976 }
3977 else
3978 {
3979 doDeinitialize();
3980 }
3981 }
3982 if (ImGui::IsItemHovered())
3983 {
3984 ImGui::BeginTooltip();
3985 ImGui::TextUnformatted(deltaThetaAndDeltaVelocityConfigurationGyroCompensationMode.second);
3986 ImGui::EndTooltip();
3987 }
3988
3989 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
3990 {
3991 ImGui::SetItemDefaultFocus();
3992 }
3993 }
3994 ImGui::EndCombo();
3995 }
3996 ImGui::SameLine();
3997 gui::widgets::HelpMarker("The GyroCompensation register setting selects the compensation to be applied to the angular rate "
3998 "measurements before integration. If bias compensation is selected, the onboard Kalman filter’s real-time "
3999 "estimate of the gyro biases will be used to compensate the IMU measurements before integration. The "
4000 "factory default state is to integrate the uncompensated angular rates from the IMU.");
4001
4002 static constexpr std::array<std::pair<vn::protocol::uart::AccCompensationMode, const char*>, 2> deltaThetaAndDeltaVelocityConfigurationAccelCompensationModes = {
4003 { { vn::protocol::uart::AccCompensationMode::ACCCOMPENSATIONMODE_NONE, "None" },
4004 { vn::protocol::uart::AccCompensationMode::ACCCOMPENSATIONMODE_BIAS, "Bias" } }
4005 };
4006 if (ImGui::BeginCombo(fmt::format("Accel Compensation##{}", size_t(id)).c_str(), vn::protocol::uart::str(_deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation).c_str()))
4007 {
4008 for (const auto& deltaThetaAndDeltaVelocityConfigurationAccelCompensationMode : deltaThetaAndDeltaVelocityConfigurationAccelCompensationModes)
4009 {
4010 const bool isSelected = (_deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation == deltaThetaAndDeltaVelocityConfigurationAccelCompensationMode.first);
4011 if (ImGui::Selectable(vn::protocol::uart::str(deltaThetaAndDeltaVelocityConfigurationAccelCompensationMode.first).c_str(), isSelected))
4012 {
4013 _deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation = deltaThetaAndDeltaVelocityConfigurationAccelCompensationMode.first;
4014 LOG_DEBUG("{}: deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation changed to {}", nameId(), vn::protocol::uart::str(_deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation));
4015 flow::ApplyChanges();
4016 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4017 {
4018 try
4019 {
4020 _vs.writeDeltaThetaAndDeltaVelocityConfiguration(_deltaThetaAndDeltaVelocityConfigurationRegister);
4021 }
4022 catch (const std::exception& e)
4023 {
4024 LOG_ERROR("{}: Could not configure the deltaThetaAndDeltaVelocityConfigurationRegister: {}", nameId(), e.what());
4025 doDeinitialize();
4026 }
4027 }
4028 else
4029 {
4030 doDeinitialize();
4031 }
4032 }
4033 if (ImGui::IsItemHovered())
4034 {
4035 ImGui::BeginTooltip();
4036 ImGui::TextUnformatted(deltaThetaAndDeltaVelocityConfigurationAccelCompensationMode.second);
4037 ImGui::EndTooltip();
4038 }
4039
4040 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4041 {
4042 ImGui::SetItemDefaultFocus();
4043 }
4044 }
4045 ImGui::EndCombo();
4046 }
4047 ImGui::SameLine();
4048 gui::widgets::HelpMarker("The AccelCompensation register setting selects the compensation to be applied to the acceleration "
4049 "measurements before integration. If bias compensation is selected, the onboard Kalman filter’s real-time "
4050 "estimate of the accel biases will be used to compensate the IMU measurements before integration. The "
4051 "factory default state is to integrate the uncompensated acceleration from the IMU.");
4052
4053 ImGui::TreePop();
4054 }
4055 }
4056
4057 // ###########################################################################################################
4058 // GNSS SUBSYSTEM
4059 // ###########################################################################################################
4060
4061 if (_sensorModel == VectorNavModel::VN310)
4062 {
4063 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
4064 if (ImGui::CollapsingHeader(fmt::format("GNSS Subsystem##{}", size_t(id)).c_str()))
4065 {
4066 if (ImGui::TreeNode(fmt::format("GNSS Configuration##{}", size_t(id)).c_str()))
4067 {
4068 static constexpr std::array<std::pair<vn::protocol::uart::GpsMode, const char*>, 3> gpsConfigurationModes = {
4069 { { vn::protocol::uart::GpsMode::GPSMODE_ONBOARDGPS, "Use onboard GNSS" },
4070 { vn::protocol::uart::GpsMode::GPSMODE_EXTERNALGPS, "Use external GNSS" },
4071 { vn::protocol::uart::GpsMode::GPSMODE_EXTERNALVN200GPS, "Use external VectorNav sensor as the GNSS" } }
4072 };
4073 if (ImGui::BeginCombo(fmt::format("Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_gpsConfigurationRegister.mode).c_str()))
4074 {
4075 for (const auto& gpsConfigurationMode : gpsConfigurationModes)
4076 {
4077 const bool isSelected = (_gpsConfigurationRegister.mode == gpsConfigurationMode.first);
4078 if (ImGui::Selectable(vn::protocol::uart::str(gpsConfigurationMode.first).c_str(), isSelected))
4079 {
4080 _gpsConfigurationRegister.mode = gpsConfigurationMode.first;
4081 LOG_DEBUG("{}: gpsConfigurationRegister.mode changed to {}", nameId(), vn::protocol::uart::str(_gpsConfigurationRegister.mode));
4082 flow::ApplyChanges();
4083 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4084 {
4085 try
4086 {
4087 _vs.writeGpsConfiguration(_gpsConfigurationRegister);
4088 }
4089 catch (const std::exception& e)
4090 {
4091 LOG_ERROR("{}: Could not configure the gpsConfigurationRegister: {}", nameId(), e.what());
4092 doDeinitialize();
4093 }
4094 }
4095 else
4096 {
4097 doDeinitialize();
4098 }
4099 }
4100 if (ImGui::IsItemHovered())
4101 {
4102 ImGui::BeginTooltip();
4103 ImGui::TextUnformatted(gpsConfigurationMode.second);
4104 ImGui::EndTooltip();
4105 }
4106
4107 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4108 {
4109 ImGui::SetItemDefaultFocus();
4110 }
4111 }
4112 ImGui::EndCombo();
4113 }
4114
4115 static constexpr std::array<std::pair<vn::protocol::uart::PpsSource, const char*>, 4> gpsConfigurationPpsSources = {
4116 { { vn::protocol::uart::PpsSource::PPSSOURCE_GPSPPSRISING, "GNSS PPS signal is present on the GNSS_PPS pin (pin 24) and should trigger on a rising edge." },
4117 { vn::protocol::uart::PpsSource::PPSSOURCE_GPSPPSFALLING, "GNSS PPS signal is present on the GNSS_PPS pin (pin 24) and should trigger on a falling edge" },
4118 { vn::protocol::uart::PpsSource::PPSSOURCE_SYNCINRISING, "GNSS PPS signal is present on the SyncIn pin (pin 22) and should trigger on a rising edge" },
4119 { vn::protocol::uart::PpsSource::PPSSOURCE_SYNCINFALLING, "GNSS PPS signal is present on the SyncIn pin (pin 22) and should trigger on a falling edge" } }
4120 };
4121 if (ImGui::BeginCombo(fmt::format("PPS Source##{}", size_t(id)).c_str(), vn::protocol::uart::str(_gpsConfigurationRegister.ppsSource).c_str()))
4122 {
4123 for (const auto& gpsConfigurationPpsSource : gpsConfigurationPpsSources)
4124 {
4125 const bool isSelected = (_gpsConfigurationRegister.ppsSource == gpsConfigurationPpsSource.first);
4126 if (ImGui::Selectable(vn::protocol::uart::str(gpsConfigurationPpsSource.first).c_str(), isSelected))
4127 {
4128 _gpsConfigurationRegister.ppsSource = gpsConfigurationPpsSource.first;
4129 LOG_DEBUG("{}: gpsConfigurationRegister.ppsSource changed to {}", nameId(), vn::protocol::uart::str(_gpsConfigurationRegister.ppsSource));
4130 flow::ApplyChanges();
4131 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4132 {
4133 try
4134 {
4135 _vs.writeGpsConfiguration(_gpsConfigurationRegister);
4136 }
4137 catch (const std::exception& e)
4138 {
4139 LOG_ERROR("{}: Could not configure the gpsConfigurationRegister: {}", nameId(), e.what());
4140 doDeinitialize();
4141 }
4142 }
4143 else
4144 {
4145 doDeinitialize();
4146 }
4147 }
4148 if (ImGui::IsItemHovered())
4149 {
4150 ImGui::BeginTooltip();
4151 ImGui::TextUnformatted(gpsConfigurationPpsSource.second);
4152 ImGui::EndTooltip();
4153 }
4154
4155 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4156 {
4157 ImGui::SetItemDefaultFocus();
4158 }
4159 }
4160 ImGui::EndCombo();
4161 }
4162
4163 static constexpr std::array<vn::protocol::uart::GpsRate, 1> gpsConfigurationRates = {
4164 { /* vn::protocol::uart::GpsRate::GPSRATE_1HZ, */
4165 vn::protocol::uart::GpsRate::GPSRATE_5HZ }
4166 };
4167 if (ImGui::BeginCombo(fmt::format("Rate##{}", size_t(id)).c_str(), vn::protocol::uart::str(_gpsConfigurationRegister.rate).c_str()))
4168 {
4169 for (const auto& gpsConfigurationRate : gpsConfigurationRates)
4170 {
4171 const bool isSelected = (_gpsConfigurationRegister.rate == gpsConfigurationRate);
4172 if (ImGui::Selectable(vn::protocol::uart::str(gpsConfigurationRate).c_str(), isSelected))
4173 {
4174 _gpsConfigurationRegister.rate = gpsConfigurationRate;
4175 LOG_DEBUG("{}: gpsConfigurationRegister.rate changed to {}", nameId(), vn::protocol::uart::str(_gpsConfigurationRegister.rate));
4176 flow::ApplyChanges();
4177 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4178 {
4179 try
4180 {
4181 _vs.writeGpsConfiguration(_gpsConfigurationRegister);
4182 }
4183 catch (const std::exception& e)
4184 {
4185 LOG_ERROR("{}: Could not configure the gpsConfigurationRegister: {}", nameId(), e.what());
4186 doDeinitialize();
4187 }
4188 }
4189 else
4190 {
4191 doDeinitialize();
4192 }
4193 }
4194
4195 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4196 {
4197 ImGui::SetItemDefaultFocus();
4198 }
4199 }
4200 ImGui::EndCombo();
4201 }
4202 ImGui::SameLine();
4203 gui::widgets::HelpMarker("GNSS navigation rate. Value must be set to 5.");
4204
4205 static constexpr std::array<std::pair<vn::protocol::uart::AntPower, const char*>, 3> gpsConfigurationAntPowers = {
4206 { { vn::protocol::uart::AntPower::ANTPOWER_OFFRESV, "Disable antenna power supply." },
4207 { vn::protocol::uart::AntPower::ANTPOWER_INTERNAL, "Use internal antenna power supply (3V, 50mA combined)." },
4208 { vn::protocol::uart::AntPower::ANTPOWER_EXTERNAL, "Use external antenna power supply (VANT pin, up to 5V and 100mA combined)" } }
4209 };
4210 if (ImGui::BeginCombo(fmt::format("Ant Power##{}", size_t(id)).c_str(), vn::protocol::uart::str(_gpsConfigurationRegister.antPow).c_str()))
4211 {
4212 for (const auto& gpsConfigurationAntPower : gpsConfigurationAntPowers)
4213 {
4214 const bool isSelected = (_gpsConfigurationRegister.antPow == gpsConfigurationAntPower.first);
4215 if (ImGui::Selectable(vn::protocol::uart::str(gpsConfigurationAntPower.first).c_str(), isSelected))
4216 {
4217 _gpsConfigurationRegister.antPow = gpsConfigurationAntPower.first;
4218 LOG_DEBUG("{}: gpsConfigurationRegister.antPow changed to {}", nameId(), vn::protocol::uart::str(_gpsConfigurationRegister.antPow));
4219 flow::ApplyChanges();
4220 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4221 {
4222 try
4223 {
4224 _vs.writeGpsConfiguration(_gpsConfigurationRegister);
4225 }
4226 catch (const std::exception& e)
4227 {
4228 LOG_ERROR("{}: Could not configure the gpsConfigurationRegister: {}", nameId(), e.what());
4229 doDeinitialize();
4230 }
4231 }
4232 else
4233 {
4234 doDeinitialize();
4235 }
4236 }
4237 if (ImGui::IsItemHovered())
4238 {
4239 ImGui::BeginTooltip();
4240 ImGui::TextUnformatted(gpsConfigurationAntPower.second);
4241 ImGui::EndTooltip();
4242 }
4243
4244 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4245 {
4246 ImGui::SetItemDefaultFocus();
4247 }
4248 }
4249 ImGui::EndCombo();
4250 }
4251 ImGui::SameLine();
4252 gui::widgets::HelpMarker("GNSS navigation rate. Value must be set to 5.");
4253
4254 ImGui::TreePop();
4255 }
4256
4257 if (ImGui::TreeNode(fmt::format("GNSS Antenna A Offset##{}", size_t(id)).c_str()))
4258 {
4259 ImGui::TextUnformatted("The position of the GNSS antenna A relative to the sensor in the vehicle coordinate frame\n"
4260 "also referred to as the GNSS antenna lever arm.");
4261
4262 if (ImGui::InputFloat3(fmt::format("##gpsAntennaOffset {}", size_t(id)).c_str(), _gpsAntennaOffset.c, "%.6f"))
4263 {
4264 LOG_DEBUG("{}: gpsAntennaOffset changed to {}", nameId(), _gpsAntennaOffset);
4265 flow::ApplyChanges();
4266 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4267 {
4268 try
4269 {
4270 _vs.writeGpsAntennaOffset(_gpsAntennaOffset);
4271 }
4272 catch (const std::exception& e)
4273 {
4274 LOG_ERROR("{}: Could not configure the gpsAntennaOffset: {}", nameId(), e.what());
4275 doDeinitialize();
4276 }
4277 }
4278 else
4279 {
4280 doDeinitialize();
4281 }
4282 }
4283
4284 ImGui::TreePop();
4285 }
4286
4287 if (ImGui::TreeNode(fmt::format("GNSS Compass Baseline##{}", size_t(id)).c_str()))
4288 {
4289 ImGui::TextUnformatted("Configures the position offset and measurement uncertainty of the second GNSS\n"
4290 "antenna relative to the first GNSS antenna in the vehicle reference frame.");
4291
4292 if (ImGui::InputFloat3(fmt::format("Position [m]##{}", size_t(id)).c_str(), _gpsCompassBaselineRegister.position.c, "%.6f"))
4293 {
4294 LOG_DEBUG("{}: gpsCompassBaselineRegister.position changed to {}", nameId(), _gpsCompassBaselineRegister.position);
4295 flow::ApplyChanges();
4296 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4297 {
4298 try
4299 {
4300 _vs.writeGpsCompassBaseline(_gpsCompassBaselineRegister);
4301 }
4302 catch (const std::exception& e)
4303 {
4304 LOG_ERROR("{}: Could not configure the gpsCompassBaselineRegister: {}", nameId(), e.what());
4305 doDeinitialize();
4306 }
4307 }
4308 else
4309 {
4310 doDeinitialize();
4311 }
4312 }
4313 ImGui::SameLine();
4314 gui::widgets::HelpMarker("HEADING ACCURACY\n\n"
4315 "The accuracy of the estimated heading is dependent upon the accuracy of the measured baseline "
4316 "between the two GNSS antennas. The factory default baseline is {1.0m, 0.0m, 0.0m}. If any other "
4317 "baseline is used, it is extremely important that the user acurately measures this baseline to ensure "
4318 "accurate heading estimates.\n"
4319 "The heading accuracy is linearly proportional to the measurement accuracy of the position of "
4320 "GNSS antenna B with respect to GNSS antenna A, and inversely proportional to the baseline "
4321 "length.\n\n"
4322 "Heading Error [deg] ~= 0.57 * (Baseline Error [cm]) / (Baseline Length [m])\n\n"
4323 "On a 1 meter baseline, a 1 cm measurement error equates to heading error of 0.6 degrees.",
4324 "(!)");
4325
4326 if (ImGui::InputFloat3(fmt::format("Uncertainty [m]##{}", size_t(id)).c_str(), _gpsCompassBaselineRegister.uncertainty.c, "%.3f"))
4327 {
4328 LOG_DEBUG("{}: gpsCompassBaselineRegister.uncertainty changed to {}", nameId(), _gpsCompassBaselineRegister.uncertainty);
4329 flow::ApplyChanges();
4330 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4331 {
4332 try
4333 {
4334 _vs.writeGpsCompassBaseline(_gpsCompassBaselineRegister);
4335 }
4336 catch (const std::exception& e)
4337 {
4338 LOG_ERROR("{}: Could not configure the gpsCompassBaselineRegister: {}", nameId(), e.what());
4339 doDeinitialize();
4340 }
4341 }
4342 else
4343 {
4344 doDeinitialize();
4345 }
4346 }
4347 ImGui::SameLine();
4348 gui::widgets::HelpMarker("MEASUREMENT UNCERTAINTY\n\n"
4349 "For the VN-310E to function properly it is very important that the user supplies a reasonable "
4350 "measurement uncertainty that is greater than the actual uncertainty in the baseline measurement. "
4351 "The VN-310E uses the uncertainty supplied by the user to validate measurements that it receives "
4352 "from the GNSS receivers. If the user inputs an uncertainty that is lower than the actual error in "
4353 "the baseline measurement between the two antennas, the VN-310E will no longer be able to derive "
4354 "heading estimates from the GNSS.\n\n"
4355 "It is recommended that you set the uncertainty equal to twice what you expect the worst case "
4356 "error to be in your baseline measurements. In many applications it is easier to measure more "
4357 "accurately in one direction than another. It is recommended that you set each of the X, Y, & Z "
4358 "uncertainties seperately to reflect this, as opposed to using a single large value.",
4359 "(!)");
4360
4361 ImGui::TreePop();
4362 }
4363 }
4364 }
4365
4366 // ###########################################################################################################
4367 // ATTITUDE SUBSYSTEM
4368 // ###########################################################################################################
4369
4370 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
4371 if (ImGui::CollapsingHeader(fmt::format("Attitude Subsystem##{}", size_t(id)).c_str()))
4372 {
4373 if (ImGui::TreeNode(fmt::format("VPE Basic Control##{}", size_t(id)).c_str()))
4374 {
4375 ImGui::TextUnformatted("Provides control over various features relating to the onboard attitude filtering algorithm.");
4376
4377 static constexpr std::array<vn::protocol::uart::VpeEnable, 2> vpeBasicControlEnables = {
4378 { vn::protocol::uart::VpeEnable::VPEENABLE_DISABLE,
4379 vn::protocol::uart::VpeEnable::VPEENABLE_ENABLE }
4380 };
4381 if (ImGui::BeginCombo(fmt::format("Enable##{}", size_t(id)).c_str(), vn::protocol::uart::str(_vpeBasicControlRegister.enable).c_str()))
4382 {
4383 for (const auto& vpeBasicControlEnable : vpeBasicControlEnables)
4384 {
4385 const bool isSelected = (_vpeBasicControlRegister.enable == vpeBasicControlEnable);
4386 if (ImGui::Selectable(vn::protocol::uart::str(vpeBasicControlEnable).c_str(), isSelected))
4387 {
4388 _vpeBasicControlRegister.enable = vpeBasicControlEnable;
4389 LOG_DEBUG("{}: vpeBasicControlRegister.enable changed to {}", nameId(), vn::protocol::uart::str(_vpeBasicControlRegister.enable));
4390 flow::ApplyChanges();
4391 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4392 {
4393 try
4394 {
4395 _vs.writeVpeBasicControl(_vpeBasicControlRegister);
4396 }
4397 catch (const std::exception& e)
4398 {
4399 LOG_ERROR("{}: Could not configure the vpeBasicControlRegister: {}", nameId(), e.what());
4400 doDeinitialize();
4401 }
4402 }
4403 else
4404 {
4405 doDeinitialize();
4406 }
4407 }
4408
4409 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4410 {
4411 ImGui::SetItemDefaultFocus();
4412 }
4413 }
4414 ImGui::EndCombo();
4415 }
4416
4417 static constexpr std::array<vn::protocol::uart::HeadingMode, 3> vpeBasicControlHeadingModes = {
4418 { vn::protocol::uart::HeadingMode::HEADINGMODE_ABSOLUTE,
4419 vn::protocol::uart::HeadingMode::HEADINGMODE_RELATIVE,
4420 vn::protocol::uart::HeadingMode::HEADINGMODE_INDOOR }
4421 };
4422 if (ImGui::BeginCombo(fmt::format("Heading Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_vpeBasicControlRegister.headingMode).c_str()))
4423 {
4424 for (const auto& vpeBasicControlHeadingMode : vpeBasicControlHeadingModes)
4425 {
4426 const bool isSelected = (_vpeBasicControlRegister.headingMode == vpeBasicControlHeadingMode);
4427 if (ImGui::Selectable(vn::protocol::uart::str(vpeBasicControlHeadingMode).c_str(), isSelected))
4428 {
4429 _vpeBasicControlRegister.headingMode = vpeBasicControlHeadingMode;
4430 LOG_DEBUG("{}: vpeBasicControlRegister.headingMode changed to {}", nameId(), vn::protocol::uart::str(_vpeBasicControlRegister.headingMode));
4431 flow::ApplyChanges();
4432 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4433 {
4434 try
4435 {
4436 _vs.writeVpeBasicControl(_vpeBasicControlRegister);
4437 }
4438 catch (const std::exception& e)
4439 {
4440 LOG_ERROR("{}: Could not configure the vpeBasicControlRegister: {}", nameId(), e.what());
4441 doDeinitialize();
4442 }
4443 }
4444 else
4445 {
4446 doDeinitialize();
4447 }
4448 }
4449
4450 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4451 {
4452 ImGui::SetItemDefaultFocus();
4453 }
4454 }
4455 ImGui::EndCombo();
4456 }
4457
4458 static constexpr std::array<vn::protocol::uart::VpeMode, 2> vpeBasicControlModes = {
4459 { vn::protocol::uart::VpeMode::VPEMODE_OFF,
4460 vn::protocol::uart::VpeMode::VPEMODE_MODE1 }
4461 };
4462 if (ImGui::BeginCombo(fmt::format("Filtering Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_vpeBasicControlRegister.filteringMode).c_str()))
4463 {
4464 for (const auto& vpeBasicControlMode : vpeBasicControlModes)
4465 {
4466 const bool isSelected = (_vpeBasicControlRegister.filteringMode == vpeBasicControlMode);
4467 if (ImGui::Selectable(vn::protocol::uart::str(vpeBasicControlMode).c_str(), isSelected))
4468 {
4469 _vpeBasicControlRegister.filteringMode = vpeBasicControlMode;
4470 LOG_DEBUG("{}: vpeBasicControlRegister.filteringMode changed to {}", nameId(), vn::protocol::uart::str(_vpeBasicControlRegister.filteringMode));
4471 flow::ApplyChanges();
4472 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4473 {
4474 try
4475 {
4476 _vs.writeVpeBasicControl(_vpeBasicControlRegister);
4477 }
4478 catch (const std::exception& e)
4479 {
4480 LOG_ERROR("{}: Could not configure the vpeBasicControlRegister: {}", nameId(), e.what());
4481 doDeinitialize();
4482 }
4483 }
4484 else
4485 {
4486 doDeinitialize();
4487 }
4488 }
4489
4490 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4491 {
4492 ImGui::SetItemDefaultFocus();
4493 }
4494 }
4495 ImGui::EndCombo();
4496 }
4497
4498 if (ImGui::BeginCombo(fmt::format("Tuning Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_vpeBasicControlRegister.tuningMode).c_str()))
4499 {
4500 for (const auto& vpeBasicControlMode : vpeBasicControlModes)
4501 {
4502 const bool isSelected = (_vpeBasicControlRegister.tuningMode == vpeBasicControlMode);
4503 if (ImGui::Selectable(vn::protocol::uart::str(vpeBasicControlMode).c_str(), isSelected))
4504 {
4505 _vpeBasicControlRegister.tuningMode = vpeBasicControlMode;
4506 LOG_DEBUG("{}: vpeBasicControlRegister.tuningMode changed to {}", nameId(), vn::protocol::uart::str(_vpeBasicControlRegister.tuningMode));
4507 flow::ApplyChanges();
4508 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4509 {
4510 try
4511 {
4512 _vs.writeVpeBasicControl(_vpeBasicControlRegister);
4513 }
4514 catch (const std::exception& e)
4515 {
4516 LOG_ERROR("{}: Could not configure the vpeBasicControlRegister: {}", nameId(), e.what());
4517 doDeinitialize();
4518 }
4519 }
4520 else
4521 {
4522 doDeinitialize();
4523 }
4524 }
4525
4526 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4527 {
4528 ImGui::SetItemDefaultFocus();
4529 }
4530 }
4531 ImGui::EndCombo();
4532 }
4533
4534 ImGui::TreePop();
4535 }
4536
4537 if (_sensorModel == VectorNavModel::VN100_VN110)
4538 {
4539 if (ImGui::TreeNode(fmt::format("VPE Magnetometer Basic Tuning##{}", size_t(id)).c_str()))
4540 {
4541 ImGui::TextUnformatted("Provides basic control of the adaptive filtering and tuning for the magnetometer.");
4542
4543 if (ImGui::DragFloat3(fmt::format("Base Tuning Level##{}", size_t(id)).c_str(), _vpeMagnetometerBasicTuningRegister.baseTuning.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4544 {
4545 LOG_DEBUG("{}: vpeMagnetometerBasicTuningRegister.baseTuning changed to {}", nameId(), _vpeMagnetometerBasicTuningRegister.baseTuning);
4546 flow::ApplyChanges();
4547 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4548 {
4549 try
4550 {
4551 _vs.writeVpeMagnetometerBasicTuning(_vpeMagnetometerBasicTuningRegister);
4552 }
4553 catch (const std::exception& e)
4554 {
4555 LOG_ERROR("{}: Could not configure the vpeMagnetometerBasicTuningRegister: {}", nameId(), e.what());
4556 doDeinitialize();
4557 }
4558 }
4559 else
4560 {
4561 doDeinitialize();
4562 }
4563 }
4564 ImGui::SameLine();
4565 gui::widgets::HelpMarker("This sets the level of confidence placed in the magnetometer when no disturbances are present. "
4566 "A larger number provides better heading accuracy, but with more sensitivity to magnetic interference.");
4567
4568 if (ImGui::DragFloat3(fmt::format("Adaptive Tuning Level##{}", size_t(id)).c_str(), _vpeMagnetometerBasicTuningRegister.adaptiveTuning.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4569 {
4570 LOG_DEBUG("{}: vpeMagnetometerBasicTuningRegister.adaptiveTuning changed to {}", nameId(), _vpeMagnetometerBasicTuningRegister.adaptiveTuning);
4571 flow::ApplyChanges();
4572 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4573 {
4574 try
4575 {
4576 _vs.writeVpeMagnetometerBasicTuning(_vpeMagnetometerBasicTuningRegister);
4577 }
4578 catch (const std::exception& e)
4579 {
4580 LOG_ERROR("{}: Could not configure the vpeMagnetometerBasicTuningRegister: {}", nameId(), e.what());
4581 doDeinitialize();
4582 }
4583 }
4584 else
4585 {
4586 doDeinitialize();
4587 }
4588 }
4589
4590 if (ImGui::DragFloat3(fmt::format("Adaptive Filtering Level##{}", size_t(id)).c_str(), _vpeMagnetometerBasicTuningRegister.adaptiveFiltering.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4591 {
4592 LOG_DEBUG("{}: vpeMagnetometerBasicTuningRegister.adaptiveFiltering changed to {}", nameId(), _vpeMagnetometerBasicTuningRegister.adaptiveFiltering);
4593 flow::ApplyChanges();
4594 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4595 {
4596 try
4597 {
4598 _vs.writeVpeMagnetometerBasicTuning(_vpeMagnetometerBasicTuningRegister);
4599 }
4600 catch (const std::exception& e)
4601 {
4602 LOG_ERROR("{}: Could not configure the vpeMagnetometerBasicTuningRegister: {}", nameId(), e.what());
4603 doDeinitialize();
4604 }
4605 }
4606 else
4607 {
4608 doDeinitialize();
4609 }
4610 }
4611
4612 ImGui::TreePop();
4613 }
4614
4615 if (ImGui::TreeNode(fmt::format("VPE Accelerometer Basic Tuning##{}", size_t(id)).c_str()))
4616 {
4617 ImGui::TextUnformatted("Provides basic control of the adaptive filtering and tuning for the accelerometer.");
4618
4619 if (ImGui::DragFloat3(fmt::format("Base Tuning Level##{}", size_t(id)).c_str(), _vpeAccelerometerBasicTuningRegister.baseTuning.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4620 {
4621 LOG_DEBUG("{}: vpeAccelerometerBasicTuningRegister.baseTuning changed to {}", nameId(), _vpeAccelerometerBasicTuningRegister.baseTuning);
4622 flow::ApplyChanges();
4623 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4624 {
4625 try
4626 {
4627 _vs.writeVpeAccelerometerBasicTuning(_vpeAccelerometerBasicTuningRegister);
4628 }
4629 catch (const std::exception& e)
4630 {
4631 LOG_ERROR("{}: Could not configure the vpeAccelerometerBasicTuningRegister: {}", nameId(), e.what());
4632 doDeinitialize();
4633 }
4634 }
4635 else
4636 {
4637 doDeinitialize();
4638 }
4639 }
4640 ImGui::SameLine();
4641 gui::widgets::HelpMarker("This sets the level of confidence placed in the accelerometer when no disturbances are present. "
4642 "A larger number provides better pitch/roll heading accuracy, but with more sensitivity to acceleration interference.");
4643
4644 if (ImGui::DragFloat3(fmt::format("Adaptive Tuning Level##{}", size_t(id)).c_str(), _vpeAccelerometerBasicTuningRegister.adaptiveTuning.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4645 {
4646 LOG_DEBUG("{}: vpeAccelerometerBasicTuningRegister.adaptiveTuning changed to {}", nameId(), _vpeAccelerometerBasicTuningRegister.adaptiveTuning);
4647 flow::ApplyChanges();
4648 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4649 {
4650 try
4651 {
4652 _vs.writeVpeAccelerometerBasicTuning(_vpeAccelerometerBasicTuningRegister);
4653 }
4654 catch (const std::exception& e)
4655 {
4656 LOG_ERROR("{}: Could not configure the vpeAccelerometerBasicTuningRegister: {}", nameId(), e.what());
4657 doDeinitialize();
4658 }
4659 }
4660 else
4661 {
4662 doDeinitialize();
4663 }
4664 }
4665
4666 if (ImGui::DragFloat3(fmt::format("Adaptive Filtering Level##{}", size_t(id)).c_str(), _vpeAccelerometerBasicTuningRegister.adaptiveFiltering.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4667 {
4668 LOG_DEBUG("{}: vpeAccelerometerBasicTuningRegister.adaptiveFiltering changed to {}", nameId(), _vpeAccelerometerBasicTuningRegister.adaptiveFiltering);
4669 flow::ApplyChanges();
4670 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4671 {
4672 try
4673 {
4674 _vs.writeVpeAccelerometerBasicTuning(_vpeAccelerometerBasicTuningRegister);
4675 }
4676 catch (const std::exception& e)
4677 {
4678 LOG_ERROR("{}: Could not configure the vpeAccelerometerBasicTuningRegister: {}", nameId(), e.what());
4679 doDeinitialize();
4680 }
4681 }
4682 else
4683 {
4684 doDeinitialize();
4685 }
4686 }
4687
4688 ImGui::TreePop();
4689 }
4690
4691 if (ImGui::TreeNode(fmt::format("VPE Gyro Basic Tuning##{}", size_t(id)).c_str()))
4692 {
4693 ImGui::TextUnformatted("Provides basic control of the adaptive filtering and tuning for the gyroscope.");
4694
4695 if (ImGui::DragFloat3(fmt::format("Base Tuning Level##{}", size_t(id)).c_str(), _vpeGyroBasicTuningRegister.baseTuning.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4696 {
4697 LOG_DEBUG("{}: vpeGyroBasicTuningRegister.baseTuning changed to {}", nameId(), _vpeGyroBasicTuningRegister.baseTuning);
4698 flow::ApplyChanges();
4699 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4700 {
4701 try
4702 {
4703 _vs.writeVpeGyroBasicTuning(_vpeGyroBasicTuningRegister);
4704 }
4705 catch (const std::exception& e)
4706 {
4707 LOG_ERROR("{}: Could not configure the vpeGyroBasicTuningRegister: {}", nameId(), e.what());
4708 doDeinitialize();
4709 }
4710 }
4711 else
4712 {
4713 doDeinitialize();
4714 }
4715 }
4716 ImGui::SameLine();
4717 gui::widgets::HelpMarker("This sets the level of confidence placed in the gyro axes.");
4718
4719 if (ImGui::DragFloat3(fmt::format("Adaptive Tuning Level##{}", size_t(id)).c_str(), _vpeGyroBasicTuningRegister.adaptiveTuning.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4720 {
4721 LOG_DEBUG("{}: vpeGyroBasicTuningRegister.adaptiveTuning changed to {}", nameId(), _vpeGyroBasicTuningRegister.adaptiveTuning);
4722 flow::ApplyChanges();
4723 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4724 {
4725 try
4726 {
4727 _vs.writeVpeGyroBasicTuning(_vpeGyroBasicTuningRegister);
4728 }
4729 catch (const std::exception& e)
4730 {
4731 LOG_ERROR("{}: Could not configure the vpeGyroBasicTuningRegister: {}", nameId(), e.what());
4732 doDeinitialize();
4733 }
4734 }
4735 else
4736 {
4737 doDeinitialize();
4738 }
4739 }
4740
4741 if (ImGui::DragFloat3(fmt::format("Variance - Angular Walk##{}", size_t(id)).c_str(), _vpeGyroBasicTuningRegister.angularWalkVariance.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4742 {
4743 LOG_DEBUG("{}: vpeGyroBasicTuningRegister.angularWalkVariance changed to {}", nameId(), _vpeGyroBasicTuningRegister.angularWalkVariance);
4744 flow::ApplyChanges();
4745 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4746 {
4747 try
4748 {
4749 _vs.writeVpeGyroBasicTuning(_vpeGyroBasicTuningRegister);
4750 }
4751 catch (const std::exception& e)
4752 {
4753 LOG_ERROR("{}: Could not configure the vpeGyroBasicTuningRegister: {}", nameId(), e.what());
4754 doDeinitialize();
4755 }
4756 }
4757 else
4758 {
4759 doDeinitialize();
4760 }
4761 }
4762
4763 ImGui::TreePop();
4764 }
4765
4766 if (ImGui::TreeNode(fmt::format("Filter Startup Gyro Bias##{}", size_t(id)).c_str()))
4767 {
4768 ImGui::TextUnformatted("The filter gyro bias estimate used at startup [rad/s].");
4769
4770 if (ImGui::InputFloat3(fmt::format("## FilterStartupGyroBias {}", size_t(id)).c_str(), _filterStartupGyroBias.c, "%.1f"))
4771 {
4772 LOG_DEBUG("{}: filterStartupGyroBias changed to {}", nameId(), _filterStartupGyroBias);
4773 flow::ApplyChanges();
4774 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4775 {
4776 try
4777 {
4778 _vs.writeFilterStartupGyroBias(_filterStartupGyroBias);
4779 }
4780 catch (const std::exception& e)
4781 {
4782 LOG_ERROR("{}: Could not configure the filterStartupGyroBias: {}", nameId(), e.what());
4783 doDeinitialize();
4784 }
4785 }
4786 else
4787 {
4788 doDeinitialize();
4789 }
4790 }
4791
4792 ImGui::TreePop();
4793 }
4794 }
4795 }
4796
4797 // ###########################################################################################################
4798 // INS SUBSYSTEM
4799 // ###########################################################################################################
4800
4801 if (_sensorModel == VectorNavModel::VN310)
4802 {
4803 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
4804 if (ImGui::CollapsingHeader(fmt::format("INS Subsystem##{}", size_t(id)).c_str()))
4805 {
4806 if (ImGui::TreeNode(fmt::format("INS Basic Configuration##{}", size_t(id)).c_str()))
4807 {
4808 static constexpr std::array<std::pair<vn::protocol::uart::Scenario, const char*>, 3> insBasicConfigurationRegisterVn300Scenarios = {
4809 { { vn::protocol::uart::Scenario::SCENARIO_INSWITHPRESSURE, "General purpose INS with barometric pressure sensor" },
4810 { vn::protocol::uart::Scenario::SCENARIO_INSWITHOUTPRESSURE, "General purpose INS without barometric pressure sensor" },
4811 { vn::protocol::uart::Scenario::SCENARIO_GPSMOVINGBASELINEDYNAMIC, "GNSS moving baseline for dynamic applications" } }
4812 };
4813 if (ImGui::BeginCombo(fmt::format("Scenario##{}", size_t(id)).c_str(), vn::protocol::uart::str(_insBasicConfigurationRegisterVn300.scenario).c_str()))
4814 {
4815 for (const auto& insBasicConfigurationRegisterVn300Scenario : insBasicConfigurationRegisterVn300Scenarios)
4816 {
4817 const bool isSelected = (_insBasicConfigurationRegisterVn300.scenario == insBasicConfigurationRegisterVn300Scenario.first);
4818 if (ImGui::Selectable(vn::protocol::uart::str(insBasicConfigurationRegisterVn300Scenario.first).c_str(), isSelected))
4819 {
4820 _insBasicConfigurationRegisterVn300.scenario = insBasicConfigurationRegisterVn300Scenario.first;
4821 LOG_DEBUG("{}: insBasicConfigurationRegisterVn300.scenario changed to {}", nameId(), vn::protocol::uart::str(_insBasicConfigurationRegisterVn300.scenario));
4822 flow::ApplyChanges();
4823 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4824 {
4825 try
4826 {
4827 _vs.writeInsBasicConfigurationVn300(_insBasicConfigurationRegisterVn300);
4828 }
4829 catch (const std::exception& e)
4830 {
4831 LOG_ERROR("{}: Could not configure the insBasicConfigurationRegisterVn300: {}", nameId(), e.what());
4832 doDeinitialize();
4833 }
4834 }
4835 else
4836 {
4837 doDeinitialize();
4838 }
4839 }
4840 if (ImGui::IsItemHovered())
4841 {
4842 ImGui::BeginTooltip();
4843 ImGui::TextUnformatted(insBasicConfigurationRegisterVn300Scenario.second);
4844 ImGui::EndTooltip();
4845 }
4846
4847 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4848 {
4849 ImGui::SetItemDefaultFocus();
4850 }
4851 }
4852 ImGui::EndCombo();
4853 }
4854
4855 if (ImGui::Checkbox(fmt::format("Ahrs Aiding##{}", size_t(id)).c_str(), &_insBasicConfigurationRegisterVn300.ahrsAiding))
4856 {
4857 LOG_DEBUG("{}: insBasicConfigurationRegisterVn300.ahrsAiding changed to {}", nameId(), _insBasicConfigurationRegisterVn300.ahrsAiding);
4858 flow::ApplyChanges();
4859 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4860 {
4861 try
4862 {
4863 _vs.writeInsBasicConfigurationVn300(_insBasicConfigurationRegisterVn300);
4864 }
4865 catch (const std::exception& e)
4866 {
4867 LOG_ERROR("{}: Could not configure the insBasicConfigurationRegisterVn300: {}", nameId(), e.what());
4868 doDeinitialize();
4869 }
4870 }
4871 else
4872 {
4873 doDeinitialize();
4874 }
4875 }
4876 ImGui::SameLine();
4877 gui::widgets::HelpMarker("Enables AHRS attitude aiding. AHRS aiding provides "
4878 "the ability to switch to using the magnetometer to "
4879 "stabilize heading during times when the device is "
4880 "stationary and the GNSS compass is not available. "
4881 "AHRS aiding also helps to eliminate large updates in "
4882 "the attitude solution during times when heading is "
4883 "weakly observable, such as at startup.");
4884
4885 if (ImGui::Checkbox(fmt::format("Estimate Baseline##{}", size_t(id)).c_str(), &_insBasicConfigurationRegisterVn300.estBaseline))
4886 {
4887 LOG_DEBUG("{}: insBasicConfigurationRegisterVn300.estBaseline changed to {}", nameId(), _insBasicConfigurationRegisterVn300.estBaseline);
4888 flow::ApplyChanges();
4889 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4890 {
4891 try
4892 {
4893 _vs.writeInsBasicConfigurationVn300(_insBasicConfigurationRegisterVn300);
4894 }
4895 catch (const std::exception& e)
4896 {
4897 LOG_ERROR("{}: Could not configure the insBasicConfigurationRegisterVn300: {}", nameId(), e.what());
4898 doDeinitialize();
4899 }
4900 }
4901 else
4902 {
4903 doDeinitialize();
4904 }
4905 }
4906 ImGui::SameLine();
4907 gui::widgets::HelpMarker("Enables GNSS compass baseline estimation by INS");
4908
4909 ImGui::TreePop();
4910 }
4911
4912 if (ImGui::TreeNode(fmt::format("Startup Filter Bias Estimate##{}", size_t(id)).c_str()))
4913 {
4914 ImGui::TextUnformatted("Sets the initial estimate for the filter bias states");
4915
4916 if (ImGui::InputFloat3(fmt::format("Gyro Bias [rad/s]##{}", size_t(id)).c_str(), _startupFilterBiasEstimateRegister.gyroBias.c, "%.1f"))
4917 {
4918 LOG_DEBUG("{}: startupFilterBiasEstimateRegister.gyroBias changed to {}", nameId(), _startupFilterBiasEstimateRegister.gyroBias);
4919 flow::ApplyChanges();
4920 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4921 {
4922 try
4923 {
4924 _vs.writeStartupFilterBiasEstimate(_startupFilterBiasEstimateRegister);
4925 }
4926 catch (const std::exception& e)
4927 {
4928 LOG_ERROR("{}: Could not configure the startupFilterBiasEstimateRegister: {}", nameId(), e.what());
4929 doDeinitialize();
4930 }
4931 }
4932 else
4933 {
4934 doDeinitialize();
4935 }
4936 }
4937
4938 if (ImGui::InputFloat3(fmt::format("Accel Bias [m/s^2]##{}", size_t(id)).c_str(), _startupFilterBiasEstimateRegister.accelBias.c, "%.1f"))
4939 {
4940 LOG_DEBUG("{}: startupFilterBiasEstimateRegister.accelBias changed to {}", nameId(), _startupFilterBiasEstimateRegister.accelBias);
4941 flow::ApplyChanges();
4942 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4943 {
4944 try
4945 {
4946 _vs.writeStartupFilterBiasEstimate(_startupFilterBiasEstimateRegister);
4947 }
4948 catch (const std::exception& e)
4949 {
4950 LOG_ERROR("{}: Could not configure the startupFilterBiasEstimateRegister: {}", nameId(), e.what());
4951 doDeinitialize();
4952 }
4953 }
4954 else
4955 {
4956 doDeinitialize();
4957 }
4958 }
4959
4960 if (ImGui::InputFloat(fmt::format("Pressure Bias [m]##{}", size_t(id)).c_str(), &_startupFilterBiasEstimateRegister.pressureBias))
4961 {
4962 LOG_DEBUG("{}: startupFilterBiasEstimateRegister.pressureBias changed to {}", nameId(), _startupFilterBiasEstimateRegister.pressureBias);
4963 flow::ApplyChanges();
4964 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4965 {
4966 try
4967 {
4968 _vs.writeStartupFilterBiasEstimate(_startupFilterBiasEstimateRegister);
4969 }
4970 catch (const std::exception& e)
4971 {
4972 LOG_ERROR("{}: Could not configure the startupFilterBiasEstimateRegister: {}", nameId(), e.what());
4973 doDeinitialize();
4974 }
4975 }
4976 else
4977 {
4978 doDeinitialize();
4979 }
4980 }
4981
4982 ImGui::TreePop();
4983 }
4984 }
4985 }
4986
4987 // ###########################################################################################################
4988 // HARD/SOFT IRON ESTIMATOR SUBSYSTEM
4989 // ###########################################################################################################
4990
4991 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
4992 if (ImGui::CollapsingHeader(fmt::format("Hard/Soft Iron Estimator Subsystem##{}", size_t(id)).c_str()))
4993 {
4994 if (ImGui::TreeNode(fmt::format("Magnetometer Calibration Control##{}", size_t(id)).c_str()))
4995 {
4996 ImGui::TextUnformatted("Controls the magnetometer real-time calibration algorithm.");
4997 if (_sensorModel == VectorNavModel::VN310)
4998 {
4999 ImGui::SameLine();
5000 gui::widgets::HelpMarker("On the PRODUCT the magnetometer is only used to provide a coarse heading estimate at startup "
5001 "and is completely tuned out of the INS filter during normal operation. A Hard/Soft Iron calibration "
5002 "may be performed to try and improve the startup magnetic heading, but is not required for nominal "
5003 "operaiton.",
5004 "(!)");
5005 }
5006
5007 static constexpr std::array<std::pair<vn::protocol::uart::HsiMode, const char*>, 3> magnetometerCalibrationControlHsiModes = {
5008 { { vn::protocol::uart::HsiMode::HSIMODE_OFF, "Real-time hard/soft iron calibration algorithm is turned off" },
5009 { vn::protocol::uart::HsiMode::HSIMODE_RUN, "Runs the real-time hard/soft iron calibration. The algorithm will continue using its existing solution.\n"
5010 "The algorithm can be started and stopped at any time by switching between the HSI_OFF and HSI_RUN state." },
5011 { vn::protocol::uart::HsiMode::HSIMODE_RESET, "Resets the real-time hard/soft iron solution" } }
5012 };
5013 if (ImGui::BeginCombo(fmt::format("HSI Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_magnetometerCalibrationControlRegister.hsiMode).c_str()))
5014 {
5015 for (const auto& magnetometerCalibrationControlHsiMode : magnetometerCalibrationControlHsiModes)
5016 {
5017 const bool isSelected = (_magnetometerCalibrationControlRegister.hsiMode == magnetometerCalibrationControlHsiMode.first);
5018 if (ImGui::Selectable(vn::protocol::uart::str(magnetometerCalibrationControlHsiMode.first).c_str(), isSelected))
5019 {
5020 _magnetometerCalibrationControlRegister.hsiMode = magnetometerCalibrationControlHsiMode.first;
5021 LOG_DEBUG("{}: magnetometerCalibrationControlRegister.hsiMode changed to {}", nameId(), vn::protocol::uart::str(_magnetometerCalibrationControlRegister.hsiMode));
5022 flow::ApplyChanges();
5023 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5024 {
5025 try
5026 {
5027 _vs.writeMagnetometerCalibrationControl(_magnetometerCalibrationControlRegister);
5028 }
5029 catch (const std::exception& e)
5030 {
5031 LOG_ERROR("{}: Could not configure the magnetometerCalibrationControlRegister: {}", nameId(), e.what());
5032 doDeinitialize();
5033 }
5034 }
5035 else
5036 {
5037 doDeinitialize();
5038 }
5039 }
5040 if (ImGui::IsItemHovered())
5041 {
5042 ImGui::BeginTooltip();
5043 ImGui::TextUnformatted(magnetometerCalibrationControlHsiMode.second);
5044 ImGui::EndTooltip();
5045 }
5046
5047 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
5048 {
5049 ImGui::SetItemDefaultFocus();
5050 }
5051 }
5052 ImGui::EndCombo();
5053 }
5054 ImGui::SameLine();
5055 gui::widgets::HelpMarker("Controls the mode of operation for the onboard real-time magnetometer hard/soft iron compensation algorithm.");
5056
5057 static constexpr std::array<std::pair<vn::protocol::uart::HsiOutput, const char*>, 2> magnetometerCalibrationControlHsiOutputs = {
5058 { { vn::protocol::uart::HsiOutput::HSIOUTPUT_NOONBOARD, "Onboard HSI is not applied to the magnetic measurements" },
5059 { vn::protocol::uart::HsiOutput::HSIOUTPUT_USEONBOARD, "Onboard HSI is applied to the magnetic measurements" } }
5060 };
5061 if (ImGui::BeginCombo(fmt::format("HSI Output##{}", size_t(id)).c_str(), vn::protocol::uart::str(_magnetometerCalibrationControlRegister.hsiOutput).c_str()))
5062 {
5063 for (const auto& [magnetometerCalibrationControlHsiOutputMode, magnetometerCalibrationControlHsiOutputDescription] : magnetometerCalibrationControlHsiOutputs)
5064 {
5065 const bool isSelected = (_magnetometerCalibrationControlRegister.hsiOutput == magnetometerCalibrationControlHsiOutputMode);
5066 if (ImGui::Selectable(vn::protocol::uart::str(magnetometerCalibrationControlHsiOutputMode).c_str(), isSelected))
5067 {
5068 _magnetometerCalibrationControlRegister.hsiOutput = magnetometerCalibrationControlHsiOutputMode;
5069 LOG_DEBUG("{}: magnetometerCalibrationControlRegister.hsiOutput changed to {}", nameId(), vn::protocol::uart::str(_magnetometerCalibrationControlRegister.hsiOutput));
5070 flow::ApplyChanges();
5071 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5072 {
5073 try
5074 {
5075 _vs.writeMagnetometerCalibrationControl(_magnetometerCalibrationControlRegister);
5076 }
5077 catch (const std::exception& e)
5078 {
5079 LOG_ERROR("{}: Could not configure the magnetometerCalibrationControlRegister: {}", nameId(), e.what());
5080 doDeinitialize();
5081 }
5082 }
5083 else
5084 {
5085 doDeinitialize();
5086 }
5087 }
5088 if (ImGui::IsItemHovered())
5089 {
5090 ImGui::BeginTooltip();
5091 ImGui::TextUnformatted(magnetometerCalibrationControlHsiOutputDescription);
5092 ImGui::EndTooltip();
5093 }
5094
5095 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
5096 {
5097 ImGui::SetItemDefaultFocus();
5098 }
5099 }
5100 ImGui::EndCombo();
5101 }
5102 ImGui::SameLine();
5103 gui::widgets::HelpMarker("Controls the type of measurements that are provided as "
5104 "outputs from the magnetometer sensor and also "
5105 "subsequently used in the attitude filter.");
5106
5107 int convergeRate = _magnetometerCalibrationControlRegister.convergeRate;
5108 if (ImGui::SliderInt(fmt::format("Converge Rate##{}", size_t(id)).c_str(), &convergeRate, 0, 5))
5109 {
5110 _magnetometerCalibrationControlRegister.convergeRate = static_cast<uint8_t>(convergeRate);
5111 LOG_DEBUG("{}: magnetometerCalibrationControlRegister.convergeRate changed to {}", nameId(), _magnetometerCalibrationControlRegister.convergeRate);
5112 flow::ApplyChanges();
5113 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5114 {
5115 try
5116 {
5117 _vs.writeMagnetometerCalibrationControl(_magnetometerCalibrationControlRegister);
5118 }
5119 catch (const std::exception& e)
5120 {
5121 LOG_ERROR("{}: Could not configure the magnetometerCalibrationControlRegister: {}", nameId(), e.what());
5122 doDeinitialize();
5123 }
5124 }
5125 else
5126 {
5127 doDeinitialize();
5128 }
5129 }
5130 ImGui::SameLine();
5131 gui::widgets::HelpMarker("Controls how quickly the hard/soft iron solution is allowed "
5132 "to converge onto a new solution. The slower the "
5133 "convergence the more accurate the estimate of the "
5134 "hard/soft iron solution. A quicker convergence will provide "
5135 "a less accurate estimate of the hard/soft iron parameters, "
5136 "but for applications where the hard/soft iron changes "
5137 "rapidly may provide a more accurate attitude estimate.\n\n"
5138 "Range: 1 to 5\n"
5139 "1 = Solution converges slowly over approximately 60-90 seconds.\n"
5140 "5 = Solution converges rapidly over approximately 15-20 seconds.");
5141
5142 ImGui::TreePop();
5143 }
5144 }
5145
5146 // ###########################################################################################################
5147 // WORLD MAGNETIC & GRAVITY MODULE
5148 // ###########################################################################################################
5149
5150 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
5151 if (ImGui::CollapsingHeader(fmt::format("World Magnetic & Gravity Module##{}", size_t(id)).c_str()))
5152 {
5153 if (ImGui::TreeNode(fmt::format("Magnetic and Gravity Reference Vectors##{}", size_t(id)).c_str()))
5154 {
5155 ImGui::TextUnformatted("Magnetic and gravity reference vectors");
5156 ImGui::SameLine();
5157 gui::widgets::HelpMarker("This register contains the reference vectors for the magnetic and gravitational fields as used by the "
5158 "onboard filter. The values map to either the user-set values or the results of calculations of the onboard "
5159 "reference models (see the Reference Vector Configuration Register in the IMU subsystem). When the "
5160 "reference values come from the onboard model(s), those values are read-only. When the reference models "
5161 "are disabled, the values reflect the user reference vectors and will be writable. For example, if the onboard "
5162 "World Magnetic Model is enabled and the onboard Gravitational Model is disabled, only the gravity "
5163 "reference values will be modified on a register write. Note that the user reference vectors will not be "
5164 "overwritten by the onboard models, but will retain their previous values for when the onboard models are "
5165 "disabled.");
5166
5167 if (ImGui::InputFloat3(fmt::format("Magnetic Reference [Gauss]##{}", size_t(id)).c_str(), _magneticAndGravityReferenceVectorsRegister.magRef.c, "%.3f"))
5168 {
5169 LOG_DEBUG("{}: magneticAndGravityReferenceVectorsRegister.magRef changed to {}", nameId(), _magneticAndGravityReferenceVectorsRegister.magRef);
5170 flow::ApplyChanges();
5171 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5172 {
5173 try
5174 {
5175 _vs.writeMagneticAndGravityReferenceVectors(_magneticAndGravityReferenceVectorsRegister);
5176 }
5177 catch (const std::exception& e)
5178 {
5179 LOG_ERROR("{}: Could not configure the magneticAndGravityReferenceVectorsRegister: {}", nameId(), e.what());
5180 doDeinitialize();
5181 }
5182 }
5183 else
5184 {
5185 doDeinitialize();
5186 }
5187 }
5188
5189 if (ImGui::InputFloat3(fmt::format("Gravity Reference [m/s^2]##{}", size_t(id)).c_str(), _magneticAndGravityReferenceVectorsRegister.accRef.c, "%.6f"))
5190 {
5191 LOG_DEBUG("{}: magneticAndGravityReferenceVectorsRegister.accRef changed to {}", nameId(), _magneticAndGravityReferenceVectorsRegister.accRef);
5192 flow::ApplyChanges();
5193 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5194 {
5195 try
5196 {
5197 _vs.writeMagneticAndGravityReferenceVectors(_magneticAndGravityReferenceVectorsRegister);
5198 }
5199 catch (const std::exception& e)
5200 {
5201 LOG_ERROR("{}: Could not configure the magneticAndGravityReferenceVectorsRegister: {}", nameId(), e.what());
5202 doDeinitialize();
5203 }
5204 }
5205 else
5206 {
5207 doDeinitialize();
5208 }
5209 }
5210
5211 ImGui::TreePop();
5212 }
5213
5214 if (ImGui::TreeNode(fmt::format("Reference Vector Configuration##{}", size_t(id)).c_str()))
5215 {
5216 ImGui::TextUnformatted("Control register for both the onboard world magnetic and gravity model corrections");
5217 ImGui::SameLine();
5218 gui::widgets::HelpMarker("This register allows configuration of the onboard spherical harmonic models used to calculate the local "
5219 "magnetic and gravitational reference values. Having accurate magnetic reference values improves the "
5220 "accuracy of heading when using the magnetometer and accounts for magnetic declination. Having accurate "
5221 "gravitational reference values improves accuracy by allowing the INS filter to more accurately estimate the "
5222 "accelerometer biases. The VectorNav currently includes the EGM96 gravitational model and the WMM2010 "
5223 "magnetic model. The models are upgradable to allow updating to future models when available.\n\n"
5224 "The magnetic and gravity models can be individually enabled or disabled using the UseMagModel and "
5225 "UseGravityModel parameters, respectively. When disabled, the corresponding values set by the user in the "
5226 "Reference Vector Register in the IMU subsystem will be used instead of values calculated by the onboard "
5227 "model.\n\n"
5228 "The VectorNav starts up with the user configured reference vector values. Shortly after startup (and if the "
5229 "models are enabled), the location and time set in this register will be used to update the reference vectors. "
5230 "When a 3D GNSS fix is available, the location and time reported by the GNSS will be used to update the model. "
5231 "If GNSS is lost, the reference vectors will hold their last valid values. The model values will be recalculated "
5232 "whenever the current position has changed by the RecaclThreshold or the date has changed by more than "
5233 "approximately 8 hours, whichever comes first.");
5234
5235 if (ImGui::Checkbox(fmt::format("Use Mag Model##{}", size_t(id)).c_str(), &_referenceVectorConfigurationRegister.useMagModel))
5236 {
5237 LOG_DEBUG("{}: referenceVectorConfigurationRegister.useMagModel changed to {}", nameId(), _referenceVectorConfigurationRegister.useMagModel);
5238 flow::ApplyChanges();
5239 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5240 {
5241 try
5242 {
5243 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
5244 }
5245 catch (const std::exception& e)
5246 {
5247 LOG_ERROR("{}: Could not configure the referenceVectorConfigurationRegister: {}", nameId(), e.what());
5248 doDeinitialize();
5249 }
5250 }
5251 else
5252 {
5253 doDeinitialize();
5254 }
5255 }
5256
5257 if (ImGui::Checkbox(fmt::format("Use Gravity Model##{}", size_t(id)).c_str(), &_referenceVectorConfigurationRegister.useGravityModel))
5258 {
5259 LOG_DEBUG("{}: referenceVectorConfigurationRegister.useGravityModel changed to {}", nameId(), _referenceVectorConfigurationRegister.useGravityModel);
5260 flow::ApplyChanges();
5261 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5262 {
5263 try
5264 {
5265 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
5266 }
5267 catch (const std::exception& e)
5268 {
5269 LOG_ERROR("{}: Could not configure the referenceVectorConfigurationRegister: {}", nameId(), e.what());
5270 doDeinitialize();
5271 }
5272 }
5273 else
5274 {
5275 doDeinitialize();
5276 }
5277 }
5278
5279 auto recalcThreshold = static_cast<int>(_referenceVectorConfigurationRegister.recalcThreshold);
5280 if (ImGui::InputInt(fmt::format("Recalc Threshold [m]##{}", size_t(id)).c_str(), &recalcThreshold))
5281 {
5282 _referenceVectorConfigurationRegister.recalcThreshold = static_cast<uint32_t>(recalcThreshold);
5283 LOG_DEBUG("{}: referenceVectorConfigurationRegister.recalcThreshold changed to {}", nameId(), _referenceVectorConfigurationRegister.recalcThreshold);
5284 flow::ApplyChanges();
5285 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5286 {
5287 try
5288 {
5289 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
5290 }
5291 catch (const std::exception& e)
5292 {
5293 LOG_ERROR("{}: Could not configure the referenceVectorConfigurationRegister: {}", nameId(), e.what());
5294 doDeinitialize();
5295 }
5296 }
5297 else
5298 {
5299 doDeinitialize();
5300 }
5301 }
5302 ImGui::SameLine();
5303 gui::widgets::HelpMarker("Maximum distance traveled before magnetic and gravity models are recalculated for the new position.");
5304
5305 if (ImGui::InputFloat(fmt::format("Year##{}", size_t(id)).c_str(), &_referenceVectorConfigurationRegister.year))
5306 {
5307 LOG_DEBUG("{}: referenceVectorConfigurationRegister.year changed to {}", nameId(), _referenceVectorConfigurationRegister.year);
5308 flow::ApplyChanges();
5309 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5310 {
5311 try
5312 {
5313 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
5314 }
5315 catch (const std::exception& e)
5316 {
5317 LOG_ERROR("{}: Could not configure the referenceVectorConfigurationRegister: {}", nameId(), e.what());
5318 doDeinitialize();
5319 }
5320 }
5321 else
5322 {
5323 doDeinitialize();
5324 }
5325 }
5326 ImGui::SameLine();
5327 gui::widgets::HelpMarker("The reference date expressed as a decimal year. Used for both the magnetic and gravity models.");
5328
5329 if (ImGui::InputDouble(fmt::format("Latitude [deg]##{}", size_t(id)).c_str(), &_referenceVectorConfigurationRegister.position[0]))
5330 {
5331 LOG_DEBUG("{}: referenceVectorConfigurationRegister.position.latitude changed to {}", nameId(), _referenceVectorConfigurationRegister.position[0]);
5332 flow::ApplyChanges();
5333 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5334 {
5335 try
5336 {
5337 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
5338 }
5339 catch (const std::exception& e)
5340 {
5341 LOG_ERROR("{}: Could not configure the referenceVectorConfigurationRegister: {}", nameId(), e.what());
5342 doDeinitialize();
5343 }
5344 }
5345 else
5346 {
5347 doDeinitialize();
5348 }
5349 }
5350 ImGui::SameLine();
5351 gui::widgets::HelpMarker("The reference latitude position in degrees.");
5352
5353 if (ImGui::InputDouble(fmt::format("Longitude [deg]##{}", size_t(id)).c_str(), &_referenceVectorConfigurationRegister.position[1]))
5354 {
5355 LOG_DEBUG("{}: referenceVectorConfigurationRegister.position.longitude changed to {}", nameId(), _referenceVectorConfigurationRegister.position[1]);
5356 flow::ApplyChanges();
5357 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5358 {
5359 try
5360 {
5361 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
5362 }
5363 catch (const std::exception& e)
5364 {
5365 LOG_ERROR("{}: Could not configure the referenceVectorConfigurationRegister: {}", nameId(), e.what());
5366 doDeinitialize();
5367 }
5368 }
5369 else
5370 {
5371 doDeinitialize();
5372 }
5373 }
5374 ImGui::SameLine();
5375 gui::widgets::HelpMarker("The reference longitude position in degrees.");
5376
5377 if (ImGui::InputDouble(fmt::format("Altitude [m]##{}", size_t(id)).c_str(), &_referenceVectorConfigurationRegister.position[2]))
5378 {
5379 LOG_DEBUG("{}: referenceVectorConfigurationRegister.position.altitude changed to {}", nameId(), _referenceVectorConfigurationRegister.position[2]);
5380 flow::ApplyChanges();
5381 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5382 {
5383 try
5384 {
5385 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
5386 }
5387 catch (const std::exception& e)
5388 {
5389 LOG_ERROR("{}: Could not configure the referenceVectorConfigurationRegister: {}", nameId(), e.what());
5390 doDeinitialize();
5391 }
5392 }
5393 else
5394 {
5395 doDeinitialize();
5396 }
5397 }
5398 ImGui::SameLine();
5399 gui::widgets::HelpMarker("The reference altitude above the reference ellipsoid in meters.");
5400
5401 ImGui::TreePop();
5402 }
5403 }
5404
5405 // ###########################################################################################################
5406 // VELOCITY AIDING
5407 // ###########################################################################################################
5408
5409 if (_sensorModel == VectorNavModel::VN100_VN110)
5410 {
5411 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
5412 if (ImGui::CollapsingHeader(fmt::format("Velocity Aiding##{}", size_t(id)).c_str()))
5413 {
5414 if (ImGui::TreeNode(fmt::format("Velocity Compensation Control##{}", size_t(id)).c_str()))
5415 {
5416 ImGui::TextUnformatted("Provides control over the velocity compensation feature for the attitude filter.");
5417
5418 static constexpr std::array<vn::protocol::uart::VelocityCompensationMode, 2> velocityCompensationControlModes = {
5419 { vn::protocol::uart::VelocityCompensationMode::VELOCITYCOMPENSATIONMODE_DISABLED,
5420 vn::protocol::uart::VelocityCompensationMode::VELOCITYCOMPENSATIONMODE_BODYMEASUREMENT }
5421 };
5422 if (ImGui::BeginCombo(fmt::format("Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_velocityCompensationControlRegister.mode).c_str()))
5423 {
5424 for (const auto& velocityCompensationControlMode : velocityCompensationControlModes)
5425 {
5426 const bool isSelected = (_velocityCompensationControlRegister.mode == velocityCompensationControlMode);
5427 if (ImGui::Selectable(vn::protocol::uart::str(velocityCompensationControlMode).c_str(), isSelected))
5428 {
5429 _velocityCompensationControlRegister.mode = velocityCompensationControlMode;
5430 LOG_DEBUG("{}: velocityCompensationControlRegister.mode changed to {}", nameId(), vn::protocol::uart::str(_velocityCompensationControlRegister.mode));
5431 flow::ApplyChanges();
5432 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5433 {
5434 try
5435 {
5436 _vs.writeVelocityCompensationControl(_velocityCompensationControlRegister);
5437 }
5438 catch (const std::exception& e)
5439 {
5440 LOG_ERROR("{}: Could not configure the velocityCompensationControlRegister: {}", nameId(), e.what());
5441 doDeinitialize();
5442 }
5443 }
5444 else
5445 {
5446 doDeinitialize();
5447 }
5448 }
5449
5450 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
5451 {
5452 ImGui::SetItemDefaultFocus();
5453 }
5454 }
5455 ImGui::EndCombo();
5456 }
5457 ImGui::SameLine();
5458 gui::widgets::HelpMarker("Selects the type of velocity compensation performed by the VPE");
5459
5460 if (ImGui::InputFloat(fmt::format("Velocity Tuning##{}", size_t(id)).c_str(), &_velocityCompensationControlRegister.velocityTuning))
5461 {
5462 LOG_DEBUG("{}: velocityCompensationControlRegister.velocityTuning changed to {}", nameId(), _velocityCompensationControlRegister.velocityTuning);
5463 flow::ApplyChanges();
5464 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5465 {
5466 try
5467 {
5468 _vs.writeVelocityCompensationControl(_velocityCompensationControlRegister);
5469 }
5470 catch (const std::exception& e)
5471 {
5472 LOG_ERROR("{}: Could not configure the velocityCompensationControlRegister: {}", nameId(), e.what());
5473 doDeinitialize();
5474 }
5475 }
5476 else
5477 {
5478 doDeinitialize();
5479 }
5480 }
5481 ImGui::SameLine();
5482 gui::widgets::HelpMarker("Tuning parameter for the velocity measurement");
5483
5484 if (ImGui::InputFloat(fmt::format("Rate Tuning##{}", size_t(id)).c_str(), &_velocityCompensationControlRegister.rateTuning))
5485 {
5486 LOG_DEBUG("{}: velocityCompensationControlRegister.rateTuning changed to {}", nameId(), _velocityCompensationControlRegister.rateTuning);
5487 flow::ApplyChanges();
5488 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5489 {
5490 try
5491 {
5492 _vs.writeVelocityCompensationControl(_velocityCompensationControlRegister);
5493 }
5494 catch (const std::exception& e)
5495 {
5496 LOG_ERROR("{}: Could not configure the velocityCompensationControlRegister: {}", nameId(), e.what());
5497 doDeinitialize();
5498 }
5499 }
5500 else
5501 {
5502 doDeinitialize();
5503 }
5504 }
5505 ImGui::SameLine();
5506 gui::widgets::HelpMarker("Tuning parameter for the angular rate measurement");
5507
5508 ImGui::TreePop();
5509 }
5510 }
5511 }
5512 }
5513
5514 [[nodiscard]] json NAV::VectorNavSensor::save() const
5515 {
5516 LOG_TRACE("{}: called", nameId());
5517
5518 json j;
5519
5520 j["UartSensor"] = UartSensor::save();
5521 j["sensorModel"] = _sensorModel;
5522
5523 // ###########################################################################################################
5524 // SYSTEM MODULE
5525 // ###########################################################################################################
5526
5527 j["asyncDataOutputType"] = _asyncDataOutputType;
5528 j["asyncDataOutputFrequency"] = _asyncDataOutputFrequency;
5529 j["asciiOutputBufferSize"] = _asciiOutputBufferSize;
5530 j["syncInPin"] = _syncInPin;
5531 j["synchronizationControlRegister"] = _synchronizationControlRegister;
5532 j["communicationProtocolControlRegister"] = _communicationProtocolControlRegister;
5533 j["binaryOutputRegisterMerge"] = _binaryOutputRegisterMerge;
5534 for (size_t b = 0; b < 3; b++)
5535 {
5536 j[fmt::format("binaryOutputRegister{}", b + 1)] = _binaryOutputRegister.at(b);
5537 j[fmt::format("binaryOutputRegister{}-Frequency", b + 1)] = _dividerFrequency.second.at(static_cast<size_t>(_binaryOutputSelectedFrequency.at(b)));
5538 }
5539
5540 // ###########################################################################################################
5541 // IMU SUBSYSTEM
5542 // ###########################################################################################################
5543
5544 j["referenceFrameRotationMatrix"] = _referenceFrameRotationMatrix;
5545 j["imuFilteringConfigurationRegister"] = _imuFilteringConfigurationRegister;
5546 j["deltaThetaAndDeltaVelocityConfigurationRegister"] = _deltaThetaAndDeltaVelocityConfigurationRegister;
5547 j["coupleImuRateOutput"] = _coupleImuRateOutput;
5548
5549 // ###########################################################################################################
5550 // GNSS SUBSYSTEM
5551 // ###########################################################################################################
5552
5553 j["gpsConfigurationRegister"] = _gpsConfigurationRegister;
5554 j["gpsAntennaOffset"] = _gpsAntennaOffset;
5555 j["gpsCompassBaselineRegister"] = _gpsCompassBaselineRegister;
5556
5557 // ###########################################################################################################
5558 // ATTITUDE SUBSYSTEM
5559 // ###########################################################################################################
5560
5561 j["vpeBasicControlRegister"] = _vpeBasicControlRegister;
5562 j["vpeMagnetometerBasicTuningRegister"] = _vpeMagnetometerBasicTuningRegister;
5563 j["vpeAccelerometerBasicTuningRegister"] = _vpeAccelerometerBasicTuningRegister;
5564 j["vpeGyroBasicTuningRegister"] = _vpeGyroBasicTuningRegister;
5565 j["filterStartupGyroBias"] = _filterStartupGyroBias;
5566
5567 // ###########################################################################################################
5568 // INS SUBSYSTEM
5569 // ###########################################################################################################
5570
5571 j["insBasicConfigurationRegisterVn300"] = _insBasicConfigurationRegisterVn300;
5572 j["startupFilterBiasEstimateRegister"] = _startupFilterBiasEstimateRegister;
5573
5574 // ###########################################################################################################
5575 // HARD/SOFT IRON ESTIMATOR SUBSYSTEM
5576 // ###########################################################################################################
5577
5578 j["magnetometerCalibrationControlRegister"] = _magnetometerCalibrationControlRegister;
5579
5580 // ###########################################################################################################
5581 // WORLD MAGNETIC & GRAVITY MODULE
5582 // ###########################################################################################################
5583
5584 j["magneticAndGravityReferenceVectorsRegister"] = _magneticAndGravityReferenceVectorsRegister;
5585 j["referenceVectorConfigurationRegister"] = _referenceVectorConfigurationRegister;
5586
5587 // ###########################################################################################################
5588 // VELOCITY AIDING
5589 // ###########################################################################################################
5590
5591 j["velocityCompensationControlRegister"] = _velocityCompensationControlRegister;
5592
5593 return j;
5594 }
5595
5596 void NAV::VectorNavSensor::restore(json const& j)
5597 {
5598 LOG_TRACE("{}: called", nameId());
5599
5600 if (j.contains("UartSensor"))
5601 {
5602 UartSensor::restore(j.at("UartSensor"));
5603 }
5604 if (j.contains("sensorModel"))
5605 {
5606 j.at("sensorModel").get_to(_sensorModel);
5607 }
5608
5609 // ###########################################################################################################
5610 // SYSTEM MODULE
5611 // ###########################################################################################################
5612
5613 if (j.contains("asyncDataOutputType"))
5614 {
5615 j.at("asyncDataOutputType").get_to(_asyncDataOutputType);
5616 }
5617 if (j.contains("asyncDataOutputFrequency"))
5618 {
5619 j.at("asyncDataOutputFrequency").get_to(_asyncDataOutputFrequency);
5620 _asyncDataOutputFrequencySelected = static_cast<int>(std::ranges::find(_possibleAsyncDataOutputFrequency, static_cast<int>(_asyncDataOutputFrequency))
5621 - _possibleAsyncDataOutputFrequency.begin());
5622 }
5623 if (j.contains("asciiOutputBufferSize"))
5624 {
5625 j.at("asciiOutputBufferSize").get_to(_asciiOutputBufferSize);
5626 _asciiOutputBuffer.resize(static_cast<size_t>(_asciiOutputBufferSize));
5627 }
5628 if (j.contains("syncInPin"))
5629 {
5630 j.at("syncInPin").get_to(_syncInPin);
5631 if (_syncInPin && inputPins.empty())
5632 {
5633 nm::CreateInputPin(this, "SyncIn", Pin::Type::Object, { "TimeSync" });
5634 }
5635 }
5636 if (j.contains("synchronizationControlRegister"))
5637 {
5638 j.at("synchronizationControlRegister").get_to(_synchronizationControlRegister);
5639 if (_synchronizationControlRegister.syncOutMode == vn::protocol::uart::SyncOutMode::SYNCOUTMODE_GPSPPS
5640 && outputPins.size() <= 4)
5641 {
5642 nm::CreateOutputPin(this, "SyncOut", Pin::Type::Object, { "TimeSync" }, &_timeSyncOut);
5643 }
5644 }
5645 if (j.contains("communicationProtocolControlRegister"))
5646 {
5647 j.at("communicationProtocolControlRegister").get_to(_communicationProtocolControlRegister);
5648 }
5649 if (j.contains("binaryOutputRegisterMerge"))
5650 {
5651 j.at("binaryOutputRegisterMerge").get_to(_binaryOutputRegisterMerge);
5652 if (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output2)
5653 {
5654 outputPins.at(1).type = Pin::Type::Flow;
5655 outputPins.at(2).type = Pin::Type::None;
5656 outputPins.at(3).type = Pin::Type::Flow;
5657 }
5658 else if (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output3
5659 || _binaryOutputRegisterMerge == BinaryRegisterMerge::Output2_Output3)
5660 {
5661 outputPins.at(1).type = Pin::Type::Flow;
5662 outputPins.at(2).type = Pin::Type::Flow;
5663 outputPins.at(3).type = Pin::Type::None;
5664 }
5665 }
5666 for (size_t b = 0; b < 3; b++)
5667 {
5668 if (j.contains(fmt::format("binaryOutputRegister{}", b + 1)))
5669 {
5670 j.at(fmt::format("binaryOutputRegister{}", b + 1)).get_to(_binaryOutputRegister.at(b));
5671 }
5672 if (j.contains(fmt::format("binaryOutputRegister{}-Frequency", b + 1)))
5673 {
5674 std::string frequency;
5675 j.at(fmt::format("binaryOutputRegister{}-Frequency", b + 1)).get_to(frequency);
5676 for (size_t i = 0; i < _dividerFrequency.second.size(); i++)
5677 {
5678 if (_dividerFrequency.second.at(i) == frequency)
5679 {
5680 _binaryOutputSelectedFrequency.at(b) = i;
5681 break;
5682 }
5683 }
5684 }
5685 }
5686
5687 // ###########################################################################################################
5688 // IMU SUBSYSTEM
5689 // ###########################################################################################################
5690
5691 if (j.contains("referenceFrameRotationMatrix"))
5692 {
5693 j.at("referenceFrameRotationMatrix").get_to(_referenceFrameRotationMatrix);
5694 }
5695 if (j.contains("imuFilteringConfigurationRegister"))
5696 {
5697 j.at("imuFilteringConfigurationRegister").get_to(_imuFilteringConfigurationRegister);
5698 }
5699 if (j.contains("deltaThetaAndDeltaVelocityConfigurationRegister"))
5700 {
5701 j.at("deltaThetaAndDeltaVelocityConfigurationRegister").get_to(_deltaThetaAndDeltaVelocityConfigurationRegister);
5702 }
5703 if (j.contains("coupleImuRateOutput"))
5704 {
5705 j.at("coupleImuRateOutput").get_to(_coupleImuRateOutput);
5706 }
5707
5708 // ###########################################################################################################
5709 // GNSS SUBSYSTEM
5710 // ###########################################################################################################
5711
5712 if (j.contains("gpsConfigurationRegister"))
5713 {
5714 j.at("gpsConfigurationRegister").get_to(_gpsConfigurationRegister);
5715 }
5716 if (j.contains("gpsAntennaOffset"))
5717 {
5718 j.at("gpsAntennaOffset").get_to(_gpsAntennaOffset);
5719 }
5720 if (j.contains("gpsCompassBaselineRegister"))
5721 {
5722 j.at("gpsCompassBaselineRegister").get_to(_gpsCompassBaselineRegister);
5723 }
5724
5725 // ###########################################################################################################
5726 // ATTITUDE SUBSYSTEM
5727 // ###########################################################################################################
5728
5729 if (j.contains("vpeBasicControlRegister"))
5730 {
5731 j.at("vpeBasicControlRegister").get_to(_vpeBasicControlRegister);
5732 }
5733 if (j.contains("vpeMagnetometerBasicTuningRegister"))
5734 {
5735 j.at("vpeMagnetometerBasicTuningRegister").get_to(_vpeMagnetometerBasicTuningRegister);
5736 }
5737 if (j.contains("vpeAccelerometerBasicTuningRegister"))
5738 {
5739 j.at("vpeAccelerometerBasicTuningRegister").get_to(_vpeAccelerometerBasicTuningRegister);
5740 }
5741 if (j.contains("vpeGyroBasicTuningRegister"))
5742 {
5743 j.at("vpeGyroBasicTuningRegister").get_to(_vpeGyroBasicTuningRegister);
5744 }
5745 if (j.contains("filterStartupGyroBias"))
5746 {
5747 j.at("filterStartupGyroBias").get_to(_filterStartupGyroBias);
5748 }
5749
5750 // ###########################################################################################################
5751 // INS SUBSYSTEM
5752 // ###########################################################################################################
5753
5754 if (j.contains("insBasicConfigurationRegisterVn300"))
5755 {
5756 j.at("insBasicConfigurationRegisterVn300").get_to(_insBasicConfigurationRegisterVn300);
5757 }
5758 if (j.contains("startupFilterBiasEstimateRegister"))
5759 {
5760 j.at("startupFilterBiasEstimateRegister").get_to(_startupFilterBiasEstimateRegister);
5761 }
5762
5763 // ###########################################################################################################
5764 // HARD/SOFT IRON ESTIMATOR SUBSYSTEM
5765 // ###########################################################################################################
5766
5767 if (j.contains("magnetometerCalibrationControlRegister"))
5768 {
5769 j.at("magnetometerCalibrationControlRegister").get_to(_magnetometerCalibrationControlRegister);
5770 }
5771
5772 // ###########################################################################################################
5773 // WORLD MAGNETIC & GRAVITY MODULE
5774 // ###########################################################################################################
5775
5776 if (j.contains("magneticAndGravityReferenceVectorsRegister"))
5777 {
5778 j.at("magneticAndGravityReferenceVectorsRegister").get_to(_magneticAndGravityReferenceVectorsRegister);
5779 }
5780 if (j.contains("referenceVectorConfigurationRegister"))
5781 {
5782 j.at("referenceVectorConfigurationRegister").get_to(_referenceVectorConfigurationRegister);
5783 }
5784
5785 // ###########################################################################################################
5786 // VELOCITY AIDING
5787 // ###########################################################################################################
5788
5789 if (j.contains("velocityCompensationControlRegister"))
5790 {
5791 j.at("velocityCompensationControlRegister").get_to(_velocityCompensationControlRegister);
5792 }
5793 }
5794
5795 bool NAV::VectorNavSensor::resetNode()
5796 {
5797 std::ranges::for_each(_lastMessageTime, [](InsTime& insTime) { insTime.reset(); });
5798 std::ranges::for_each(_lastMessageTimeSinceStartup, [](uint64_t& value) { value = 0; });
5799 _gnssTimeCounter = {};
5800
5801 _lastSyncInCnt = 0;
5802 _lastSyncOutCnt = 0;
5803 _syncCntOffset = 0;
5804
5805 _timeSyncOut.ppsTime.reset();
5806 _timeSyncOut.syncOutCnt = 0;
5807 _binaryOutputRegisterMergeObservation = nullptr;
5808
5809 return true;
5810 }
5811
5812 bool NAV::VectorNavSensor::initialize()
5813 {
5814 LOG_TRACE("{}: called", nameId());
5815
5816 // ###########################################################################################################
5817 // Connecting
5818 // ###########################################################################################################
5819
5820 // Choose baudrate
5821 Baudrate targetBaudrate = sensorBaudrate() == BAUDRATE_FASTEST
5822 ? static_cast<Baudrate>(vn::sensors::VnSensor::supportedBaudrates()[vn::sensors::VnSensor::supportedBaudrates().size() - 1])
5823 : sensorBaudrate();
5824
5825 Baudrate connectedBaudrate{};
5826 _connectedSensorPort.clear();
5827
5828 // Search for the VectorNav Sensor
5829 size_t maxTries = std::filesystem::exists(_sensorPort) ? 5 : 0;
5830 for (size_t i = 0; i < maxTries; ++i)
5831 {
5832 if (int32_t foundBaudrate = 0;
5833 vn::sensors::Searcher::search(_sensorPort, &foundBaudrate))
5834 {
5835 // Sensor was found at specified port with the baudrate 'foundBaudrate'
5836 connectedBaudrate = static_cast<Baudrate>(foundBaudrate);
5837 _connectedSensorPort = _sensorPort;
5838 LOG_DEBUG("{}: Found VectorNav sensor on specified port '{}' with baudrate {} (it took {} attempts to connect)",
5839 nameId(), _connectedSensorPort, foundBaudrate, i + 1);
5840 break;
5841 }
5842 }
5843 if (_connectedSensorPort.empty())
5844 {
5845 if (std::filesystem::exists(_sensorPort))
5846 {
5847 LOG_ERROR("{}: Could not connect to VectorNav sensor on specified port '{}', but port exists.", nameId(), _sensorPort);
5848 return false;
5849 }
5850
5851 for (size_t i = 0; i < 5; ++i)
5852 {
5853 if (!_connectedSensorPort.empty())
5854 {
5855 break;
5856 }
5857
5858 if (std::vector<std::pair<std::string, uint32_t>> foundSensors = vn::sensors::Searcher::search();
5859 !foundSensors.empty())
5860 {
5861 for (const auto& [port, baudrate] : foundSensors)
5862 {
5863 _vs.connect(port, baudrate);
5864 std::string modelNumber = _vs.readModelNumber();
5865 _vs.disconnect();
5866
5867 LOG_DEBUG("{}: Found VectorNav sensor '{}' on port '{}' with baudrate {} ({} VN sensors connected in total)",
5868 nameId(), modelNumber, port, baudrate, foundSensors.size());
5869
5870 // Identify the sensor by its model number
5871 const std::string searchString = _sensorModel == VectorNavModel::VN100_VN110 ? "VN-1" : "VN-3";
5872 if (modelNumber.find(searchString) != std::string::npos)
5873 {
5874 _connectedSensorPort = port;
5875 connectedBaudrate = static_cast<Baudrate>(baudrate);
5876
5877 LOG_DEBUG("{}: Selected VectorNav sensor '{}' on port '{}' (it took {} attempts to connect)",
5878 nameId(), modelNumber, port, i + 1);
5879 break;
5880 }
5881 }
5882 }
5883 }
5884 if (_connectedSensorPort.empty())
5885 {
5886 LOG_ERROR("{}: Could not connect. Is the sensor connected and do you have read permissions?", nameId());
5887 return false;
5888 }
5889 }
5890
5891 try
5892 {
5893 // Connect to the sensor (vs.verifySensorConnectivity does not have to be called as sensor is already tested)
5894 _vs.connect(_connectedSensorPort, connectedBaudrate);
5895 }
5896 catch (const std::exception& e)
5897 {
5898 LOG_ERROR("{}: Failed to connect to sensor on port '{}' with baudrate {} with error: {}", nameId(),
5899 _connectedSensorPort, connectedBaudrate, e.what());
5900 return false;
5901 }
5902
5903 try
5904 {
5905 if (!_vs.verifySensorConnectivity())
5906 {
5907 LOG_ERROR("{}: Connected to sensor on port '{}' with baudrate {} but sensor does not answer", nameId(),
5908 _connectedSensorPort, connectedBaudrate);
5909 return false;
5910 }
5911 }
5912 catch (const std::exception& e)
5913 {
5914 LOG_ERROR("{}: Connected to sensor on port '{}' with baudrate {} but sensor threw an exception: {}", nameId(),
5915 _connectedSensorPort, connectedBaudrate, e.what());
5916 return false;
5917 }
5918
5919 // Query the sensor's model number
5920 auto modelNumber = _vs.readModelNumber();
5921 LOG_DEBUG("{}: {} connected on port '{}' with baudrate {}", nameId(), _vs.readModelNumber(), _connectedSensorPort, connectedBaudrate);
5922
5923 if (_sensorModel == VectorNavModel::VN100_VN110 && (!modelNumber.starts_with("VN-100") && !modelNumber.starts_with("VN-110")))
5924 {
5925 LOG_ERROR("{}: VN100/VN110 configured, but sensor identifies as {}", nameId(), modelNumber);
5926 doDeinitialize();
5927 return false;
5928 }
5929 if (_sensorModel == VectorNavModel::VN310 && !modelNumber.starts_with("VN-310"))
5930 {
5931 LOG_ERROR("{}: VN310 configured, but sensor identifies as {}", nameId(), modelNumber);
5932 doDeinitialize();
5933 return false;
5934 }
5935
5936 // ###########################################################################################################
5937 // SYSTEM MODULE
5938 // ###########################################################################################################
5939
5940 // Change Connection Baudrate
5941 if (targetBaudrate != connectedBaudrate)
5942 {
5943 auto suppBaud = vn::sensors::VnSensor::supportedBaudrates();
5944 if (std::ranges::find(suppBaud, targetBaudrate) != suppBaud.end())
5945 {
5946 _vs.changeBaudRate(targetBaudrate);
5947 LOG_DEBUG("{}: Baudrate changed to {}", nameId(), static_cast<size_t>(targetBaudrate));
5948 }
5949 else
5950 {
5951 LOG_ERROR("{}: Does not support baudrate {}", nameId(), static_cast<size_t>(targetBaudrate));
5952 return false;
5953 }
5954 }
5955 if (_vs.readSerialBaudRate() != targetBaudrate)
5956 {
5957 LOG_ERROR("{}: Changing the baudrate from {} to {} was not successfull", nameId(), _vs.readSerialBaudRate(), targetBaudrate);
5958 doDeinitialize();
5959 return false;
5960 }
5961
5962 _vs.writeSynchronizationControl(_synchronizationControlRegister);
5963 if (auto vnSynchronizationControlRegister = _vs.readSynchronizationControl();
5964 vnSynchronizationControlRegister.syncInMode != _synchronizationControlRegister.syncInMode
5965 || vnSynchronizationControlRegister.syncInEdge != _synchronizationControlRegister.syncInEdge
5966 || vnSynchronizationControlRegister.syncInSkipFactor != _synchronizationControlRegister.syncInSkipFactor
5967 || vnSynchronizationControlRegister.syncOutMode != _synchronizationControlRegister.syncOutMode
5968 || vnSynchronizationControlRegister.syncOutPolarity != _synchronizationControlRegister.syncOutPolarity
5969 || vnSynchronizationControlRegister.syncOutSkipFactor != _synchronizationControlRegister.syncOutSkipFactor
5970 || vnSynchronizationControlRegister.syncOutPulseWidth != _synchronizationControlRegister.syncOutPulseWidth)
5971 {
5972 LOG_ERROR("{}: Writing the synchronizationControlRegister was not successfull.\n"
5973 "Target: syncInMode = {}, syncInEdge = {}, syncInSkipFactor = {}, syncOutMode = {}, syncOutPolarity = {}, syncOutSkipFactor = {}, syncOutPulseWidth = {}\n"
5974 "Sensor: syncInMode = {}, syncInEdge = {}, syncInSkipFactor = {}, syncOutMode = {}, syncOutPolarity = {}, syncOutSkipFactor = {}, syncOutPulseWidth = {}",
5975 nameId(),
5976 _synchronizationControlRegister.syncInMode, _synchronizationControlRegister.syncInEdge, _synchronizationControlRegister.syncInSkipFactor, _synchronizationControlRegister.syncOutMode, _synchronizationControlRegister.syncOutPolarity, _synchronizationControlRegister.syncOutSkipFactor, _synchronizationControlRegister.syncOutPulseWidth,
5977 vnSynchronizationControlRegister.syncInMode, vnSynchronizationControlRegister.syncInEdge, vnSynchronizationControlRegister.syncInSkipFactor, vnSynchronizationControlRegister.syncOutMode, vnSynchronizationControlRegister.syncOutPolarity, vnSynchronizationControlRegister.syncOutSkipFactor, vnSynchronizationControlRegister.syncOutPulseWidth);
5978 doDeinitialize();
5979 return false;
5980 }
5981
5982 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
5983 if (auto vnCommunicationProtocolControlRegister = _vs.readCommunicationProtocolControl();
5984 vnCommunicationProtocolControlRegister.serialCount != _communicationProtocolControlRegister.serialCount
5985 || vnCommunicationProtocolControlRegister.serialStatus != _communicationProtocolControlRegister.serialStatus
5986 || vnCommunicationProtocolControlRegister.spiCount != _communicationProtocolControlRegister.spiCount
5987 || vnCommunicationProtocolControlRegister.spiStatus != _communicationProtocolControlRegister.spiStatus
5988 || vnCommunicationProtocolControlRegister.serialChecksum != _communicationProtocolControlRegister.serialChecksum
5989 || vnCommunicationProtocolControlRegister.spiChecksum != _communicationProtocolControlRegister.spiChecksum
5990 || vnCommunicationProtocolControlRegister.errorMode != _communicationProtocolControlRegister.errorMode)
5991 {
5992 LOG_ERROR("{}: Writing the communicationProtocolControlRegister was not successfull.\n"
5993 "Target: serialCount = {}, serialStatus = {}, spiCount = {}, spiStatus = {}, serialChecksum = {}, spiChecksum = {}, errorMode = {}\n"
5994 "Sensor: serialCount = {}, serialStatus = {}, spiCount = {}, spiStatus = {}, serialChecksum = {}, spiChecksum = {}, errorMode = {}",
5995 nameId(),
5996 _communicationProtocolControlRegister.serialCount, _communicationProtocolControlRegister.serialStatus, _communicationProtocolControlRegister.spiCount, _communicationProtocolControlRegister.spiStatus, _communicationProtocolControlRegister.serialChecksum, _communicationProtocolControlRegister.spiChecksum, _communicationProtocolControlRegister.errorMode,
5997 vnCommunicationProtocolControlRegister.serialCount, vnCommunicationProtocolControlRegister.serialStatus, vnCommunicationProtocolControlRegister.spiCount, vnCommunicationProtocolControlRegister.spiStatus, vnCommunicationProtocolControlRegister.serialChecksum, vnCommunicationProtocolControlRegister.spiChecksum, vnCommunicationProtocolControlRegister.errorMode);
5998 doDeinitialize();
5999 return false;
6000 }
6001
6002 // _vs.writeSynchronizationStatus(vn::sensors::SynchronizationStatusRegister); // User manual VN-310 - 8.3.1 (p 105) / VN-100 - 5.3.1 (p 76)
6003
6004 // ###########################################################################################################
6005 // IMU SUBSYSTEM
6006 // ###########################################################################################################
6007
6008 // _vs.writeMagnetometerCompensation(vn::sensors::MagnetometerCompensationRegister()); // User manual VN-310 - 9.2.1 (p 111) / VN-100 - 6.2.1 (p 82)
6009 // _vs.writeAccelerationCompensation(vn::sensors::AccelerationCompensationRegister()); // User manual VN-310 - 9.2.2 (p 112) / VN-100 - 6.2.2 (p 83)
6010 // _vs.writeGyroCompensation(vn::sensors::GyroCompensationRegister()); // User manual VN-310 - 9.2.3 (p 113) / VN-100 - 6.2.3 (p 84)
6011
6012 _vs.writeReferenceFrameRotation(_referenceFrameRotationMatrix);
6013 if (auto vnReferenceFrameRotationMatrix = _vs.readReferenceFrameRotation();
6014 vnReferenceFrameRotationMatrix != _referenceFrameRotationMatrix)
6015 {
6016 LOG_ERROR("{}: Writing the referenceFrameRotationMatrix was not successfull.\n"
6017 "Target: DCM = {}\n"
6018 "Sensor: DCM = {}",
6019 nameId(), _referenceFrameRotationMatrix, vnReferenceFrameRotationMatrix);
6020 doDeinitialize();
6021 return false;
6022 }
6023
6024 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
6025 if (auto vnImuFilteringConfigurationRegister = _vs.readImuFilteringConfiguration();
6026 vnImuFilteringConfigurationRegister.magWindowSize != _imuFilteringConfigurationRegister.magWindowSize
6027 || vnImuFilteringConfigurationRegister.accelWindowSize != _imuFilteringConfigurationRegister.accelWindowSize
6028 || vnImuFilteringConfigurationRegister.gyroWindowSize != _imuFilteringConfigurationRegister.gyroWindowSize
6029 || vnImuFilteringConfigurationRegister.tempWindowSize != _imuFilteringConfigurationRegister.tempWindowSize
6030 || vnImuFilteringConfigurationRegister.presWindowSize != _imuFilteringConfigurationRegister.presWindowSize
6031 || vnImuFilteringConfigurationRegister.magFilterMode != _imuFilteringConfigurationRegister.magFilterMode
6032 || vnImuFilteringConfigurationRegister.accelFilterMode != _imuFilteringConfigurationRegister.accelFilterMode
6033 || vnImuFilteringConfigurationRegister.gyroFilterMode != _imuFilteringConfigurationRegister.gyroFilterMode
6034 || vnImuFilteringConfigurationRegister.tempFilterMode != _imuFilteringConfigurationRegister.tempFilterMode
6035 || vnImuFilteringConfigurationRegister.presFilterMode != _imuFilteringConfigurationRegister.presFilterMode)
6036 {
6037 LOG_ERROR("{}: Writing the imuFilteringConfigurationRegister was not successfull.\n"
6038 "Target: magWindowSize = {}, accelWindowSize = {}, gyroWindowSize = {}, tempWindowSize = {}, presWindowSize = {}, magFilterMode = {}, accelFilterMode = {}, gyroFilterMode = {}, tempFilterMode = {}, presFilterMode = {}\n"
6039 "Sensor: magWindowSize = {}, accelWindowSize = {}, gyroWindowSize = {}, tempWindowSize = {}, presWindowSize = {}, magFilterMode = {}, accelFilterMode = {}, gyroFilterMode = {}, tempFilterMode = {}, presFilterMode = {}",
6040 nameId(),
6041 _imuFilteringConfigurationRegister.magWindowSize, _imuFilteringConfigurationRegister.accelWindowSize, _imuFilteringConfigurationRegister.gyroWindowSize, _imuFilteringConfigurationRegister.tempWindowSize, _imuFilteringConfigurationRegister.presWindowSize, _imuFilteringConfigurationRegister.magFilterMode, _imuFilteringConfigurationRegister.accelFilterMode, _imuFilteringConfigurationRegister.gyroFilterMode, _imuFilteringConfigurationRegister.tempFilterMode, _imuFilteringConfigurationRegister.presFilterMode,
6042 vnImuFilteringConfigurationRegister.magWindowSize, vnImuFilteringConfigurationRegister.accelWindowSize, vnImuFilteringConfigurationRegister.gyroWindowSize, vnImuFilteringConfigurationRegister.tempWindowSize, vnImuFilteringConfigurationRegister.presWindowSize, vnImuFilteringConfigurationRegister.magFilterMode, vnImuFilteringConfigurationRegister.accelFilterMode, vnImuFilteringConfigurationRegister.gyroFilterMode, vnImuFilteringConfigurationRegister.tempFilterMode, vnImuFilteringConfigurationRegister.presFilterMode);
6043 doDeinitialize();
6044 return false;
6045 }
6046
6047 _vs.writeDeltaThetaAndDeltaVelocityConfiguration(_deltaThetaAndDeltaVelocityConfigurationRegister);
6048 if (auto vnDeltaThetaAndDeltaVelocityConfigurationRegister = _vs.readDeltaThetaAndDeltaVelocityConfiguration();
6049 vnDeltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame != _deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame
6050 || vnDeltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation != _deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation
6051 || vnDeltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation != _deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation
6052 || vnDeltaThetaAndDeltaVelocityConfigurationRegister.earthRateCorrection != _deltaThetaAndDeltaVelocityConfigurationRegister.earthRateCorrection)
6053 {
6054 LOG_ERROR("{}: Writing the deltaThetaAndDeltaVelocityConfigurationRegister was not successfull.\n"
6055 "Target: integrationFrame = {}, gyroCompensation = {}, accelCompensation = {}, earthRateCorrection = {}\n"
6056 "Sensor: integrationFrame = {}, gyroCompensation = {}, accelCompensation = {}, earthRateCorrection = {}",
6057 nameId(),
6058 _deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame, _deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation, _deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation, _deltaThetaAndDeltaVelocityConfigurationRegister.earthRateCorrection,
6059 vnDeltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame, vnDeltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation, vnDeltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation, vnDeltaThetaAndDeltaVelocityConfigurationRegister.earthRateCorrection);
6060 doDeinitialize();
6061 return false;
6062 }
6063
6064 // ###########################################################################################################
6065 // GNSS SUBSYSTEM
6066 // ###########################################################################################################
6067
6068 if (_sensorModel == VectorNavModel::VN310)
6069 {
6070 _vs.writeGpsConfiguration(_gpsConfigurationRegister);
6071 if (auto vnGpsConfigurationRegister = _vs.readGpsConfiguration();
6072 vnGpsConfigurationRegister.mode != _gpsConfigurationRegister.mode
6073 || vnGpsConfigurationRegister.ppsSource != _gpsConfigurationRegister.ppsSource
6074 || vnGpsConfigurationRegister.rate != _gpsConfigurationRegister.rate
6075 || vnGpsConfigurationRegister.antPow != _gpsConfigurationRegister.antPow)
6076 {
6077 LOG_ERROR("{}: Writing the gpsConfigurationRegister was not successfull.\n"
6078 "Target: mode = {}, ppsSource = {}, rate = {}, antPow = {}\n"
6079 "Sensor: mode = {}, ppsSource = {}, rate = {}, antPow = {}",
6080 nameId(),
6081 _gpsConfigurationRegister.mode, _gpsConfigurationRegister.ppsSource, _gpsConfigurationRegister.rate, _gpsConfigurationRegister.antPow,
6082 vnGpsConfigurationRegister.mode, vnGpsConfigurationRegister.ppsSource, vnGpsConfigurationRegister.rate, vnGpsConfigurationRegister.antPow);
6083 doDeinitialize();
6084 return false;
6085 }
6086
6087 _vs.writeGpsAntennaOffset(_gpsAntennaOffset);
6088 if (auto vnGpsAntennaOffset = _vs.readGpsAntennaOffset();
6089 vnGpsAntennaOffset != _gpsAntennaOffset)
6090 {
6091 LOG_ERROR("{}: Writing the gpsAntennaOffset was not successfull.\n"
6092 "Target: {}\n"
6093 "Sensor: {}",
6094 nameId(),
6095 _gpsAntennaOffset,
6096 vnGpsAntennaOffset);
6097 doDeinitialize();
6098 return false;
6099 }
6100
6101 _vs.writeGpsCompassBaseline(_gpsCompassBaselineRegister);
6102 if (auto vnGpsCompassBaselineRegister = _vs.readGpsCompassBaseline();
6103 vnGpsCompassBaselineRegister.position != _gpsCompassBaselineRegister.position
6104 || vnGpsCompassBaselineRegister.uncertainty != _gpsCompassBaselineRegister.uncertainty)
6105 {
6106 LOG_ERROR("{}: Writing the gpsCompassBaselineRegister was not successfull.\n"
6107 "Target: position = {}, uncertainty = {}\n"
6108 "Sensor: position = {}, uncertainty = {}",
6109 nameId(),
6110 _gpsCompassBaselineRegister.position, _gpsCompassBaselineRegister.uncertainty,
6111 vnGpsCompassBaselineRegister.position, vnGpsCompassBaselineRegister.uncertainty);
6112 doDeinitialize();
6113 return false;
6114 }
6115 }
6116
6117 // ###########################################################################################################
6118 // ATTITUDE SUBSYSTEM
6119 // ###########################################################################################################
6120 if (_sensorModel == VectorNavModel::VN100_VN110)
6121 {
6122 // _vs.tare(); // User manual VN-100 - 7.1.1 (p 92)
6123 // _vs.magneticDisturbancePresent(bool); // User manual VN-100 - 7.1.2 (p 92)
6124 // _vs.accelerationDisturbancePresent(true); // User manual VN-100 - 7.1.3 (p 92f)
6125 }
6126 // _vs.setGyroBias(); // User manual VN-310 - 11.1.1 (p 148) / VN-100 - 7.1.4 (p 93)
6127
6128 // TODO: Implement in vnproglib: _vs.setInitialHeading() - User manual VN-310 - 11.1.2 (p 148)
6129
6130 _vs.writeVpeBasicControl(_vpeBasicControlRegister);
6131 if (auto vnVpeBasicControlRegister = _vs.readVpeBasicControl();
6132 vnVpeBasicControlRegister.enable != _vpeBasicControlRegister.enable
6133 || vnVpeBasicControlRegister.headingMode != _vpeBasicControlRegister.headingMode
6134 || vnVpeBasicControlRegister.filteringMode != _vpeBasicControlRegister.filteringMode
6135 || vnVpeBasicControlRegister.tuningMode != _vpeBasicControlRegister.tuningMode)
6136 {
6137 LOG_ERROR("{}: Writing the vpeBasicControlRegister was not successfull.\n"
6138 "Target: enable = {}, headingMode = {}, filteringMode = {}, tuningMode = {}\n"
6139 "Sensor: enable = {}, headingMode = {}, filteringMode = {}, tuningMode = {}",
6140 nameId(),
6141 _vpeBasicControlRegister.enable, _vpeBasicControlRegister.headingMode, _vpeBasicControlRegister.filteringMode, _vpeBasicControlRegister.tuningMode,
6142 vnVpeBasicControlRegister.enable, vnVpeBasicControlRegister.headingMode, vnVpeBasicControlRegister.filteringMode, vnVpeBasicControlRegister.tuningMode);
6143 doDeinitialize();
6144 return false;
6145 }
6146
6147 if (_sensorModel == VectorNavModel::VN100_VN110)
6148 {
6149 _vs.writeVpeMagnetometerBasicTuning(_vpeMagnetometerBasicTuningRegister);
6150 if (auto vnVpeMagnetometerBasicTuningRegister = _vs.readVpeMagnetometerBasicTuning();
6151 vnVpeMagnetometerBasicTuningRegister.baseTuning != _vpeMagnetometerBasicTuningRegister.baseTuning
6152 || vnVpeMagnetometerBasicTuningRegister.adaptiveTuning != _vpeMagnetometerBasicTuningRegister.adaptiveTuning
6153 || vnVpeMagnetometerBasicTuningRegister.adaptiveFiltering != _vpeMagnetometerBasicTuningRegister.adaptiveFiltering)
6154 {
6155 LOG_ERROR("{}: Writing the vpeMagnetometerBasicTuningRegister was not successfull.\n"
6156 "Target: baseTuning = {}, adaptiveTuning = {}, adaptiveFiltering = {}\n"
6157 "Sensor: baseTuning = {}, adaptiveTuning = {}, adaptiveFiltering = {}",
6158 nameId(),
6159 _vpeMagnetometerBasicTuningRegister.baseTuning, _vpeMagnetometerBasicTuningRegister.adaptiveTuning, _vpeMagnetometerBasicTuningRegister.adaptiveFiltering,
6160 vnVpeMagnetometerBasicTuningRegister.baseTuning, vnVpeMagnetometerBasicTuningRegister.adaptiveTuning, vnVpeMagnetometerBasicTuningRegister.adaptiveFiltering);
6161 doDeinitialize();
6162 return false;
6163 }
6164
6165 _vs.writeVpeAccelerometerBasicTuning(_vpeAccelerometerBasicTuningRegister);
6166 if (auto vnVpeAccelerometerBasicTuningRegister = _vs.readVpeAccelerometerBasicTuning();
6167 vnVpeAccelerometerBasicTuningRegister.baseTuning != _vpeAccelerometerBasicTuningRegister.baseTuning
6168 || vnVpeAccelerometerBasicTuningRegister.adaptiveTuning != _vpeAccelerometerBasicTuningRegister.adaptiveTuning
6169 || vnVpeAccelerometerBasicTuningRegister.adaptiveFiltering != _vpeAccelerometerBasicTuningRegister.adaptiveFiltering)
6170 {
6171 LOG_ERROR("{}: Writing the vpeAccelerometerBasicTuningRegister was not successfull.\n"
6172 "Target: baseTuning = {}, adaptiveTuning = {}, adaptiveFiltering = {}\n"
6173 "Sensor: baseTuning = {}, adaptiveTuning = {}, adaptiveFiltering = {}",
6174 nameId(),
6175 _vpeAccelerometerBasicTuningRegister.baseTuning, _vpeAccelerometerBasicTuningRegister.adaptiveTuning, _vpeAccelerometerBasicTuningRegister.adaptiveFiltering,
6176 vnVpeAccelerometerBasicTuningRegister.baseTuning, vnVpeAccelerometerBasicTuningRegister.adaptiveTuning, vnVpeAccelerometerBasicTuningRegister.adaptiveFiltering);
6177 doDeinitialize();
6178 return false;
6179 }
6180
6181 _vs.writeFilterStartupGyroBias(_filterStartupGyroBias);
6182 if (auto vnFilterStartupGyroBias = _vs.readFilterStartupGyroBias();
6183 vnFilterStartupGyroBias != _filterStartupGyroBias)
6184 {
6185 LOG_ERROR("{}: Writing the filterStartupGyroBias was not successfull.\n"
6186 "Target: {}\n"
6187 "Sensor: {}",
6188 nameId(),
6189 _filterStartupGyroBias,
6190 vnFilterStartupGyroBias);
6191 doDeinitialize();
6192 return false;
6193 }
6194
6195 _vs.writeVpeGyroBasicTuning(_vpeGyroBasicTuningRegister);
6196 if (auto vnVpeGyroBasicTuningRegister = _vs.readVpeGyroBasicTuning();
6197 vnVpeGyroBasicTuningRegister.angularWalkVariance != _vpeGyroBasicTuningRegister.angularWalkVariance
6198 || vnVpeGyroBasicTuningRegister.baseTuning != _vpeGyroBasicTuningRegister.baseTuning
6199 || vnVpeGyroBasicTuningRegister.adaptiveTuning != _vpeGyroBasicTuningRegister.adaptiveTuning)
6200 {
6201 LOG_ERROR("{}: Writing the vpeGyroBasicTuningRegister was not successfull.\n"
6202 "Target: angularWalkVariance = {}, baseTuning = {}, adaptiveTuning = {}\n"
6203 "Sensor: angularWalkVariance = {}, baseTuning = {}, adaptiveTuning = {}",
6204 nameId(),
6205 _vpeGyroBasicTuningRegister.angularWalkVariance, _vpeGyroBasicTuningRegister.baseTuning, _vpeGyroBasicTuningRegister.adaptiveTuning,
6206 vnVpeGyroBasicTuningRegister.angularWalkVariance, vnVpeGyroBasicTuningRegister.baseTuning, vnVpeGyroBasicTuningRegister.adaptiveTuning);
6207 doDeinitialize();
6208 return false;
6209 }
6210 }
6211
6212 // ###########################################################################################################
6213 // INS SUBSYSTEM
6214 // ###########################################################################################################
6215
6216 if (_sensorModel == VectorNavModel::VN310)
6217 {
6218 _vs.writeInsBasicConfigurationVn300(_insBasicConfigurationRegisterVn300);
6219 if (auto vnInsBasicConfigurationRegisterVn300 = _vs.readInsBasicConfigurationVn300();
6220 vnInsBasicConfigurationRegisterVn300.scenario != _insBasicConfigurationRegisterVn300.scenario
6221 || vnInsBasicConfigurationRegisterVn300.ahrsAiding != _insBasicConfigurationRegisterVn300.ahrsAiding
6222 || vnInsBasicConfigurationRegisterVn300.estBaseline != _insBasicConfigurationRegisterVn300.estBaseline)
6223 {
6224 LOG_ERROR("{}: Writing the insBasicConfigurationRegisterVn300 was not successfull.\n"
6225 "Target: scenario = {}, ahrsAiding = {}, estBaseline = {}\n"
6226 "Sensor: scenario = {}, ahrsAiding = {}, estBaseline = {}",
6227 nameId(),
6228 _insBasicConfigurationRegisterVn300.scenario, _insBasicConfigurationRegisterVn300.ahrsAiding, _insBasicConfigurationRegisterVn300.estBaseline,
6229 vnInsBasicConfigurationRegisterVn300.scenario, vnInsBasicConfigurationRegisterVn300.ahrsAiding, vnInsBasicConfigurationRegisterVn300.estBaseline);
6230 doDeinitialize();
6231 return false;
6232 }
6233
6234 _vs.writeStartupFilterBiasEstimate(_startupFilterBiasEstimateRegister);
6235 if (auto vnStartupFilterBiasEstimateRegister = _vs.readStartupFilterBiasEstimate();
6236 vnStartupFilterBiasEstimateRegister.gyroBias != _startupFilterBiasEstimateRegister.gyroBias
6237 || vnStartupFilterBiasEstimateRegister.accelBias != _startupFilterBiasEstimateRegister.accelBias
6238 || vnStartupFilterBiasEstimateRegister.pressureBias != _startupFilterBiasEstimateRegister.pressureBias)
6239 {
6240 LOG_ERROR("{}: Writing the startupFilterBiasEstimateRegister was not successfull.\n"
6241 "Target: gyroBias = {}, accelBias = {}, pressureBias = {}\n"
6242 "Sensor: gyroBias = {}, accelBias = {}, pressureBias = {}",
6243 nameId(),
6244 _startupFilterBiasEstimateRegister.gyroBias, _startupFilterBiasEstimateRegister.accelBias, _startupFilterBiasEstimateRegister.pressureBias,
6245 vnStartupFilterBiasEstimateRegister.gyroBias, vnStartupFilterBiasEstimateRegister.accelBias, vnStartupFilterBiasEstimateRegister.pressureBias);
6246 doDeinitialize();
6247 return false;
6248 }
6249 }
6250
6251 // ###########################################################################################################
6252 // HARD/SOFT IRON ESTIMATOR SUBSYSTEM
6253 // ###########################################################################################################
6254
6255 _vs.writeMagnetometerCalibrationControl(_magnetometerCalibrationControlRegister);
6256 if (auto vnMagnetometerCalibrationControlRegister = _vs.readMagnetometerCalibrationControl();
6257 vnMagnetometerCalibrationControlRegister.hsiMode != _magnetometerCalibrationControlRegister.hsiMode
6258 || vnMagnetometerCalibrationControlRegister.hsiOutput != _magnetometerCalibrationControlRegister.hsiOutput
6259 || vnMagnetometerCalibrationControlRegister.convergeRate != _magnetometerCalibrationControlRegister.convergeRate)
6260 {
6261 LOG_ERROR("{}: Writing the magnetometerCalibrationControlRegister was not successfull.\n"
6262 "Target: hsiMode = {}, hsiOutput = {}, convergeRate = {}\n"
6263 "Sensor: hsiMode = {}, hsiOutput = {}, convergeRate = {}",
6264 nameId(),
6265 _magnetometerCalibrationControlRegister.hsiMode, _magnetometerCalibrationControlRegister.hsiOutput, _magnetometerCalibrationControlRegister.convergeRate,
6266 vnMagnetometerCalibrationControlRegister.hsiMode, vnMagnetometerCalibrationControlRegister.hsiOutput, vnMagnetometerCalibrationControlRegister.convergeRate);
6267 doDeinitialize();
6268 return false;
6269 }
6270
6271 // ###########################################################################################################
6272 // WORLD MAGNETIC & GRAVITY MODULE
6273 // ###########################################################################################################
6274
6275 _vs.writeMagneticAndGravityReferenceVectors(_magneticAndGravityReferenceVectorsRegister);
6276 if (auto vnMagneticAndGravityReferenceVectorsRegister = _vs.readMagneticAndGravityReferenceVectors();
6277 (!_referenceVectorConfigurationRegister.useMagModel && vnMagneticAndGravityReferenceVectorsRegister.magRef != _magneticAndGravityReferenceVectorsRegister.magRef)
6278 || (!_referenceVectorConfigurationRegister.useGravityModel && vnMagneticAndGravityReferenceVectorsRegister.accRef != _magneticAndGravityReferenceVectorsRegister.accRef))
6279 {
6280 LOG_ERROR("{}: Writing the magneticAndGravityReferenceVectorsRegister was not successfull.\n"
6281 "Target: magRef = {}, accRef = {}\n"
6282 "Sensor: magRef = {}, accRef = {}",
6283 nameId(),
6284 _magneticAndGravityReferenceVectorsRegister.magRef, _magneticAndGravityReferenceVectorsRegister.accRef,
6285 vnMagneticAndGravityReferenceVectorsRegister.magRef, vnMagneticAndGravityReferenceVectorsRegister.accRef);
6286 doDeinitialize();
6287 return false;
6288 }
6289
6290 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
6291 if (auto vnReferenceVectorConfigurationRegister = _vs.readReferenceVectorConfiguration();
6292 vnReferenceVectorConfigurationRegister.useMagModel != _referenceVectorConfigurationRegister.useMagModel
6293 || vnReferenceVectorConfigurationRegister.useGravityModel != _referenceVectorConfigurationRegister.useGravityModel
6294 || vnReferenceVectorConfigurationRegister.recalcThreshold != _referenceVectorConfigurationRegister.recalcThreshold
6295 || vnReferenceVectorConfigurationRegister.year != _referenceVectorConfigurationRegister.year
6296 || vnReferenceVectorConfigurationRegister.position != _referenceVectorConfigurationRegister.position)
6297 {
6298 LOG_ERROR("{}: Writing the referenceVectorConfigurationRegister was not successfull.\n"
6299 "Target: useMagModel = {}, useGravityModel = {}, recalcThreshold = {}, year = {}, position = {}\n"
6300 "Sensor: useMagModel = {}, useGravityModel = {}, recalcThreshold = {}, year = {}, position = {}",
6301 nameId(),
6302 _referenceVectorConfigurationRegister.useMagModel, _referenceVectorConfigurationRegister.useGravityModel, _referenceVectorConfigurationRegister.recalcThreshold, _referenceVectorConfigurationRegister.year, _referenceVectorConfigurationRegister.position,
6303 vnReferenceVectorConfigurationRegister.useMagModel, vnReferenceVectorConfigurationRegister.useGravityModel, vnReferenceVectorConfigurationRegister.recalcThreshold, vnReferenceVectorConfigurationRegister.year, vnReferenceVectorConfigurationRegister.position);
6304 doDeinitialize();
6305 return false;
6306 }
6307
6308 // ###########################################################################################################
6309 // Velocity Aiding
6310 // ###########################################################################################################
6311
6312 if (_sensorModel == VectorNavModel::VN100_VN110)
6313 {
6314 _vs.writeVelocityCompensationControl(_velocityCompensationControlRegister);
6315 if (auto vnVelocityCompensationControlRegister = _vs.readVelocityCompensationControl();
6316 vnVelocityCompensationControlRegister.mode != _velocityCompensationControlRegister.mode
6317 || vnVelocityCompensationControlRegister.velocityTuning != _velocityCompensationControlRegister.velocityTuning
6318 || vnVelocityCompensationControlRegister.rateTuning != _velocityCompensationControlRegister.rateTuning)
6319 {
6320 LOG_ERROR("{}: Writing the velocityCompensationControlRegister was not successfull.\n"
6321 "Target: mode = {}, velocityTuning = {}, rateTuning = {}\n"
6322 "Sensor: mode = {}, velocityTuning = {}, rateTuning = {}",
6323 nameId(),
6324 _velocityCompensationControlRegister.mode, _velocityCompensationControlRegister.velocityTuning, _velocityCompensationControlRegister.rateTuning,
6325 vnVelocityCompensationControlRegister.mode, vnVelocityCompensationControlRegister.velocityTuning, vnVelocityCompensationControlRegister.rateTuning);
6326 doDeinitialize();
6327 return false;
6328 }
6329
6330 // _vs.writeVelocityCompensationMeasurement(vn::math::vec3f); // User manual VN-100 - 10.3.1 (p 124)
6331 }
6332
6333 // ###########################################################################################################
6334 // Outputs
6335 // ###########################################################################################################
6336
6337 _vs.writeAsyncDataOutputType(_asyncDataOutputType);
6338 if (auto vnAsyncDataOutputType = _vs.readAsyncDataOutputType();
6339 vnAsyncDataOutputType != _asyncDataOutputType)
6340 {
6341 LOG_ERROR("{}: Writing the asyncDataOutputType was not successfull.\n"
6342 "Target: {}\n"
6343 "Sensor: {}",
6344 nameId(),
6345 _asyncDataOutputType,
6346 vnAsyncDataOutputType);
6347 doDeinitialize();
6348 return false;
6349 }
6350
6351 if (_asyncDataOutputType != vn::protocol::uart::AsciiAsync::VNOFF)
6352 {
6353 _vs.writeAsyncDataOutputFrequency(_asyncDataOutputFrequency);
6354 if (auto vnAsyncDataOutputFrequency = _vs.readAsyncDataOutputType();
6355 vnAsyncDataOutputFrequency != _asyncDataOutputFrequency)
6356 {
6357 LOG_ERROR("{}: Writing the asyncDataOutputFrequency was not successfull.\n"
6358 "Target: {}\n"
6359 "Sensor: {}",
6360 nameId(),
6361 _asyncDataOutputFrequency,
6362 vnAsyncDataOutputFrequency);
6363 doDeinitialize();
6364 return false;
6365 }
6366 }
6367
6368 [[maybe_unused]] size_t binaryOutputRegisterCounter = 1; // To give a proper error message
6369 try
6370 {
6371 // The sensor does somehow report wrong flags here, so we can't check if it actually worked
6372
6373 // auto checkBinaryRegister = [&](const vn::sensors::BinaryOutputRegister& current, const vn::sensors::BinaryOutputRegister& target) {
6374 // if (current.asyncMode != target.asyncMode
6375 // || current.rateDivisor != target.rateDivisor
6376 // || current.commonField != target.commonField
6377 // || current.timeField != target.timeField
6378 // || current.imuField != target.imuField
6379 // || current.gpsField != target.gpsField
6380 // || current.attitudeField != target.attitudeField
6381 // || current.insField != target.insField
6382 // || current.gps2Field != target.gps2Field)
6383 // {
6384 // LOG_ERROR("{}: Writing the binaryOutputRegister {} was not successfull.\n"
6385 // "Target: asyncMode = {}, rateDivisor = {}, commonField = {}, timeField = {}, imuField = {}, gpsField = {}, attitudeField = {}, insField = {}, gps2Field = {}\n"
6386 // "Sensor: asyncMode = {}, rateDivisor = {}, commonField = {}, timeField = {}, imuField = {}, gpsField = {}, attitudeField = {}, insField = {}, gps2Field = {}",
6387 // nameId(), binaryOutputRegisterCounter,
6388 // target.asyncMode, target.rateDivisor, target.commonField, target.timeField, target.imuField, target.gpsField, target.attitudeField, target.insField, target.gps2Field,
6389 // current.asyncMode, current.rateDivisor, current.commonField, current.timeField, current.imuField, current.gpsField, current.attitudeField, current.insField, current.gps2Field);
6390 // return false;
6391 // }
6392 // return true;
6393 // };
6394
6395 _vs.writeBinaryOutput1(_binaryOutputRegister.at(0));
6396 // if (!checkBinaryRegister(_vs.readBinaryOutput1(), _binaryOutputRegister.at(0)))
6397 // {
6398 // DoDeinitialize();;
6399 // return false;
6400 // }
6401
6402 binaryOutputRegisterCounter++;
6403 _vs.writeBinaryOutput2(_binaryOutputRegister.at(1));
6404 // if (!checkBinaryRegister(_vs.readBinaryOutput2(), _binaryOutputRegister.at(1)))
6405 // {
6406 // DoDeinitialize();;
6407 // return false;
6408 // }
6409
6410 binaryOutputRegisterCounter++;
6411 _vs.writeBinaryOutput3(_binaryOutputRegister.at(2));
6412 // if (!checkBinaryRegister(_vs.readBinaryOutput3(), _binaryOutputRegister.at(2)))
6413 // {
6414 // DoDeinitialize();;
6415 // return false;
6416 // }
6417 }
6418 catch (const std::exception& e)
6419 {
6420 LOG_ERROR("{}: Could not configure binary output register {}: {}", nameId(), binaryOutputRegisterCounter, e.what());
6421 doDeinitialize();
6422 return false;
6423 }
6424
6425 // Some changes need to be set at startup, therefore write the settings and reset the device
6426 LOG_DEBUG("{}: writing settings", nameId());
6427 _vs.writeSettings();
6428
6429 LOG_DEBUG("{}: Resetting device to apply permenent settings", nameId());
6430 _vs.reset();
6431
6432 // TODO: Implement in vnproglib: _vs.writeNmeaOutput1(...) - User manual VN-310 - 8.2.14 (p 103)
6433 // TODO: Implement in vnproglib: _vs.writeNmeaOutput2(...) - User manual VN-310 - 8.2.15 (p 104)
6434
6435 _vs.registerAsyncPacketReceivedHandler(this, asciiOrBinaryAsyncMessageReceived);
6436
6437 LOG_DEBUG("{}: successfully initialized", nameId());
6438
6439 return true;
6440 }
6441
6442 void NAV::VectorNavSensor::deinitialize()
6443 {
6444 LOG_TRACE("{}: called", nameId());
6445
6446 try
6447 {
6448 if (_vs.isConnected())
6449 {
6450 try
6451 {
6452 _vs.unregisterAsyncPacketReceivedHandler();
6453 }
6454 catch (const std::exception& e)
6455 {
6456 LOG_WARN("{}: Could not unregisterAsyncPacketReceivedHandler ({})", nameId(), e.what());
6457 }
6458
6459 try
6460 {
6461 vn::sensors::BinaryOutputRegister bor{
6462 vn::protocol::uart::AsyncMode::ASYNCMODE_NONE, // AsyncMode
6463 800, // RateDivisor
6464 vn::protocol::uart::CommonGroup::COMMONGROUP_NONE, // CommonGroup
6465 vn::protocol::uart::TimeGroup::TIMEGROUP_NONE, // TimeGroup
6466 vn::protocol::uart::ImuGroup::IMUGROUP_NONE, // IMUGroup
6467 vn::protocol::uart::GpsGroup::GPSGROUP_NONE, // GNSS1Group
6468 vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_NONE, // AttitudeGroup
6469 vn::protocol::uart::InsGroup::INSGROUP_NONE, // INSGroup
6470 vn::protocol::uart::GpsGroup::GPSGROUP_NONE // GNSS2Group
6471 };
6472
6473 _vs.writeBinaryOutput1(bor);
6474 _vs.writeBinaryOutput2(bor);
6475 _vs.writeBinaryOutput3(bor);
6476 _vs.writeAsyncDataOutputType(vn::protocol::uart::AsciiAsync::VNOFF);
6477 _vs.changeBaudRate(NAV::UartSensor::Baudrate::BAUDRATE_115200);
6478 _vs.writeSettings();
6479 LOG_DEBUG("{}: Sensor output turned off", nameId());
6480 }
6481 catch (const std::exception& e)
6482 {
6483 LOG_WARN("{}: Could not turn off sensor output ({})", nameId(), e.what());
6484 }
6485
6486 try
6487 {
6488 _vs.disconnect();
6489 LOG_DEBUG("{}: Sensor disconnected", nameId());
6490 }
6491 catch (const std::exception& e)
6492 {
6493 LOG_WARN("{}: Could not disconnect ({})", nameId(), e.what());
6494 }
6495 }
6496 }
6497 catch (const std::exception& e)
6498 {
6499 LOG_WARN("{}: Unhandled exception while deinitializing sensor ({})", nameId(), e.what());
6500 }
6501 }
6502
6503 void NAV::VectorNavSensor::mergeVectorNavBinaryObservations(const std::shared_ptr<VectorNavBinaryOutput>& target, const std::shared_ptr<VectorNavBinaryOutput>& source)
6504 {
6505 target->insTime = !target->insTime.empty() ? target->insTime : source->insTime;
6506 // Group 2 (Time)
6507 if (!target->timeOutputs && source->timeOutputs)
6508 {
6509 target->timeOutputs = source->timeOutputs;
6510 }
6511 else if (target->timeOutputs && source->timeOutputs)
6512 {
6513 target->timeOutputs->timeStartup = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP ? target->timeOutputs->timeStartup : source->timeOutputs->timeStartup;
6514 target->timeOutputs->timeGps = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPS ? target->timeOutputs->timeGps : source->timeOutputs->timeGps;
6515 target->timeOutputs->gpsTow = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW ? target->timeOutputs->gpsTow : source->timeOutputs->gpsTow;
6516 target->timeOutputs->gpsWeek = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK ? target->timeOutputs->gpsWeek : source->timeOutputs->gpsWeek;
6517 target->timeOutputs->timeSyncIn = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESYNCIN ? target->timeOutputs->timeSyncIn : source->timeOutputs->timeSyncIn;
6518 target->timeOutputs->timePPS = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPSPPS ? target->timeOutputs->timePPS : source->timeOutputs->timePPS;
6519 // target->timeOutputs->timeUtc = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEUTC ? target->timeOutputs->timeUtc : source->timeOutputs->timeUtc;
6520 target->timeOutputs->syncInCnt = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCINCNT ? target->timeOutputs->syncInCnt : source->timeOutputs->syncInCnt;
6521 target->timeOutputs->syncOutCnt = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCOUTCNT ? target->timeOutputs->syncOutCnt : source->timeOutputs->syncOutCnt;
6522 target->timeOutputs->timeStatus = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS ? target->timeOutputs->timeStatus : source->timeOutputs->timeStatus;
6523
6524 target->timeOutputs->timeField |= source->timeOutputs->timeField;
6525 }
6526 // Group 3 (IMU)
6527 if (!target->imuOutputs && source->imuOutputs)
6528 {
6529 target->imuOutputs = source->imuOutputs;
6530 }
6531 else if (target->imuOutputs && source->imuOutputs)
6532 {
6533 target->imuOutputs->imuStatus = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_IMUSTATUS ? target->imuOutputs->imuStatus : source->imuOutputs->imuStatus;
6534 target->imuOutputs->uncompMag = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPMAG ? target->imuOutputs->uncompMag : source->imuOutputs->uncompMag;
6535 target->imuOutputs->uncompAccel = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL ? target->imuOutputs->uncompAccel : source->imuOutputs->uncompAccel;
6536 target->imuOutputs->uncompGyro = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO ? target->imuOutputs->uncompGyro : source->imuOutputs->uncompGyro;
6537 target->imuOutputs->temp = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_TEMP ? target->imuOutputs->temp : source->imuOutputs->temp;
6538 target->imuOutputs->pres = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_PRES ? target->imuOutputs->pres : source->imuOutputs->pres;
6539 target->imuOutputs->deltaTime = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA ? target->imuOutputs->deltaTime : source->imuOutputs->deltaTime;
6540 target->imuOutputs->deltaTheta = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA ? target->imuOutputs->deltaTheta : source->imuOutputs->deltaTheta;
6541 target->imuOutputs->deltaV = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL ? target->imuOutputs->deltaV : source->imuOutputs->deltaV;
6542 target->imuOutputs->mag = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_MAG ? target->imuOutputs->mag : source->imuOutputs->mag;
6543 target->imuOutputs->accel = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL ? target->imuOutputs->accel : source->imuOutputs->accel;
6544 target->imuOutputs->angularRate = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE ? target->imuOutputs->angularRate : source->imuOutputs->angularRate;
6545
6546 target->imuOutputs->imuField |= source->imuOutputs->imuField;
6547 }
6548 // Group 4 (GNSS1)
6549 if (!target->gnss1Outputs && source->gnss1Outputs)
6550 {
6551 target->gnss1Outputs = source->gnss1Outputs;
6552 }
6553 else if (target->gnss1Outputs && source->gnss1Outputs)
6554 {
6555 // target->gnss1Outputs->timeUtc = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_UTC ? target->gnss1Outputs->timeUtc : source->gnss1Outputs->timeUtc;
6556 target->gnss1Outputs->tow = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_TOW ? target->gnss1Outputs->tow : source->gnss1Outputs->tow;
6557 target->gnss1Outputs->week = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK ? target->gnss1Outputs->week : source->gnss1Outputs->week;
6558 target->gnss1Outputs->numSats = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_NUMSATS ? target->gnss1Outputs->numSats : source->gnss1Outputs->numSats;
6559 target->gnss1Outputs->fix = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_FIX ? target->gnss1Outputs->fix : source->gnss1Outputs->fix;
6560 target->gnss1Outputs->posLla = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA ? target->gnss1Outputs->posLla : source->gnss1Outputs->posLla;
6561 target->gnss1Outputs->posEcef = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF ? target->gnss1Outputs->posEcef : source->gnss1Outputs->posEcef;
6562 target->gnss1Outputs->velNed = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED ? target->gnss1Outputs->velNed : source->gnss1Outputs->velNed;
6563 target->gnss1Outputs->velEcef = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF ? target->gnss1Outputs->velEcef : source->gnss1Outputs->velEcef;
6564 target->gnss1Outputs->posU = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_POSU ? target->gnss1Outputs->posU : source->gnss1Outputs->posU;
6565 target->gnss1Outputs->velU = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_VELU ? target->gnss1Outputs->velU : source->gnss1Outputs->velU;
6566 target->gnss1Outputs->timeU = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEU ? target->gnss1Outputs->timeU : source->gnss1Outputs->timeU;
6567 target->gnss1Outputs->timeInfo = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO ? target->gnss1Outputs->timeInfo : source->gnss1Outputs->timeInfo;
6568 target->gnss1Outputs->dop = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_DOP ? target->gnss1Outputs->dop : source->gnss1Outputs->dop;
6569 target->gnss1Outputs->satInfo = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_SATINFO ? target->gnss1Outputs->satInfo : source->gnss1Outputs->satInfo;
6570 target->gnss1Outputs->raw = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_RAWMEAS ? target->gnss1Outputs->raw : source->gnss1Outputs->raw;
6571
6572 target->gnss1Outputs->gnssField |= source->gnss1Outputs->gnssField;
6573 }
6574 // Group 5 (Attitude)
6575 if (!target->attitudeOutputs && source->attitudeOutputs)
6576 {
6577 target->attitudeOutputs = source->attitudeOutputs;
6578 }
6579 else if (target->attitudeOutputs && source->attitudeOutputs)
6580 {
6581 target->attitudeOutputs->vpeStatus = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_VPESTATUS ? target->attitudeOutputs->vpeStatus : source->attitudeOutputs->vpeStatus;
6582 target->attitudeOutputs->ypr = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YAWPITCHROLL ? target->attitudeOutputs->ypr : source->attitudeOutputs->ypr;
6583 target->attitudeOutputs->qtn = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_QUATERNION ? target->attitudeOutputs->qtn : source->attitudeOutputs->qtn;
6584 target->attitudeOutputs->dcm = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_DCM ? target->attitudeOutputs->dcm : source->attitudeOutputs->dcm;
6585 target->attitudeOutputs->magNed = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_MAGNED ? target->attitudeOutputs->magNed : source->attitudeOutputs->magNed;
6586 target->attitudeOutputs->accelNed = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_ACCELNED ? target->attitudeOutputs->accelNed : source->attitudeOutputs->accelNed;
6587 target->attitudeOutputs->linearAccelBody = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELBODY ? target->attitudeOutputs->linearAccelBody : source->attitudeOutputs->linearAccelBody;
6588 target->attitudeOutputs->linearAccelNed = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELNED ? target->attitudeOutputs->linearAccelNed : source->attitudeOutputs->linearAccelNed;
6589 target->attitudeOutputs->yprU = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YPRU ? target->attitudeOutputs->yprU : source->attitudeOutputs->yprU;
6590
6591 target->attitudeOutputs->attitudeField |= source->attitudeOutputs->attitudeField;
6592 }
6593 // Group 6 (INS)
6594 if (!target->insOutputs && source->insOutputs)
6595 {
6596 target->insOutputs = source->insOutputs;
6597 }
6598 else if (target->insOutputs && source->insOutputs)
6599 {
6600 target->insOutputs->insStatus = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_INSSTATUS ? target->insOutputs->insStatus : source->insOutputs->insStatus;
6601 target->insOutputs->posLla = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_POSLLA ? target->insOutputs->posLla : source->insOutputs->posLla;
6602 target->insOutputs->posEcef = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_POSECEF ? target->insOutputs->posEcef : source->insOutputs->posEcef;
6603 target->insOutputs->velBody = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_VELBODY ? target->insOutputs->velBody : source->insOutputs->velBody;
6604 target->insOutputs->velNed = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_VELNED ? target->insOutputs->velNed : source->insOutputs->velNed;
6605 target->insOutputs->velEcef = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_VELECEF ? target->insOutputs->velEcef : source->insOutputs->velEcef;
6606 target->insOutputs->magEcef = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_MAGECEF ? target->insOutputs->magEcef : source->insOutputs->magEcef;
6607 target->insOutputs->accelEcef = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_ACCELECEF ? target->insOutputs->accelEcef : source->insOutputs->accelEcef;
6608 target->insOutputs->linearAccelEcef = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_LINEARACCELECEF ? target->insOutputs->linearAccelEcef : source->insOutputs->linearAccelEcef;
6609 target->insOutputs->posU = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_POSU ? target->insOutputs->posU : source->insOutputs->posU;
6610 target->insOutputs->velU = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_VELU ? target->insOutputs->velU : source->insOutputs->velU;
6611
6612 target->insOutputs->insField |= source->insOutputs->insField;
6613 }
6614 // Group 7 (GNSS2)
6615 if (!target->gnss2Outputs && source->gnss2Outputs)
6616 {
6617 target->gnss2Outputs = source->gnss2Outputs;
6618 }
6619 else if (target->gnss2Outputs && source->gnss2Outputs)
6620 {
6621 // target->gnss2Outputs->timeUtc = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_UTC ? target->gnss2Outputs->timeUtc : source->gnss2Outputs->timeUtc;
6622 target->gnss2Outputs->tow = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_TOW ? target->gnss2Outputs->tow : source->gnss2Outputs->tow;
6623 target->gnss2Outputs->week = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK ? target->gnss2Outputs->week : source->gnss2Outputs->week;
6624 target->gnss2Outputs->numSats = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_NUMSATS ? target->gnss2Outputs->numSats : source->gnss2Outputs->numSats;
6625 target->gnss2Outputs->fix = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_FIX ? target->gnss2Outputs->fix : source->gnss2Outputs->fix;
6626 target->gnss2Outputs->posLla = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA ? target->gnss2Outputs->posLla : source->gnss2Outputs->posLla;
6627 target->gnss2Outputs->posEcef = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF ? target->gnss2Outputs->posEcef : source->gnss2Outputs->posEcef;
6628 target->gnss2Outputs->velNed = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED ? target->gnss2Outputs->velNed : source->gnss2Outputs->velNed;
6629 target->gnss2Outputs->velEcef = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF ? target->gnss2Outputs->velEcef : source->gnss2Outputs->velEcef;
6630 target->gnss2Outputs->posU = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_POSU ? target->gnss2Outputs->posU : source->gnss2Outputs->posU;
6631 target->gnss2Outputs->velU = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_VELU ? target->gnss2Outputs->velU : source->gnss2Outputs->velU;
6632 target->gnss2Outputs->timeU = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEU ? target->gnss2Outputs->timeU : source->gnss2Outputs->timeU;
6633 target->gnss2Outputs->timeInfo = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO ? target->gnss2Outputs->timeInfo : source->gnss2Outputs->timeInfo;
6634 target->gnss2Outputs->dop = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_DOP ? target->gnss2Outputs->dop : source->gnss2Outputs->dop;
6635 target->gnss2Outputs->satInfo = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_SATINFO ? target->gnss2Outputs->satInfo : source->gnss2Outputs->satInfo;
6636 target->gnss2Outputs->raw = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_RAWMEAS ? target->gnss2Outputs->raw : source->gnss2Outputs->raw;
6637
6638 target->gnss2Outputs->gnssField |= source->gnss2Outputs->gnssField;
6639 }
6640 }
6641
6642 void NAV::VectorNavSensor::asciiOrBinaryAsyncMessageReceived(void* userData, vn::protocol::uart::Packet& p, [[maybe_unused]] size_t index)
6643 {
6644 auto* vnSensor = static_cast<VectorNavSensor*>(userData);
6645
6646 LOG_DATA("{}: Received message", vnSensor->nameId());
6647
6648 if (p.getPacketLength() > 2500)
6649 {
6650 LOG_ERROR("{} Packet size is {} bytes. VectorNav internal buffer overflows happen if the size is > 2550 bytes. "
6651 "You potentially already lost packages without noticing. "
6652 "Consider splitting the packet to different Binary outputs.",
6653 vnSensor->nameId(), p.getPacketLength());
6654 }
6655 else if (p.getPacketLength() > 2200)
6656 {
6657 LOG_WARN("{} Packet size is {} bytes. VectorNav internal buffer overflows happen if the size is > 2550 bytes. "
6658 "Consider splitting the packet to different Binary outputs.",
6659 vnSensor->nameId(), p.getPacketLength());
6660 }
6661
6662 if (p.type() == vn::protocol::uart::Packet::TYPE_BINARY)
6663 {
6664 for (size_t b = 0; b < 3; b++)
6665 {
6666 // Make sure that the binary packet is from the type we expect
6667 if (p.isCompatible(vnSensor->_binaryOutputRegister.at(b).commonField,
6668 vnSensor->_binaryOutputRegister.at(b).timeField,
6669 vnSensor->_binaryOutputRegister.at(b).imuField,
6670 vnSensor->_binaryOutputRegister.at(b).gpsField,
6671 vnSensor->_binaryOutputRegister.at(b).attitudeField,
6672 vnSensor->_binaryOutputRegister.at(b).insField,
6673 vnSensor->_binaryOutputRegister.at(b).gps2Field))
6674 {
6675 auto obs = std::make_shared<VectorNavBinaryOutput>(vnSensor->_imuPos);
6676
6677 // Group 2 (Time)
6678 if (vnSensor->_binaryOutputRegister.at(b).timeField != vn::protocol::uart::TimeGroup::TIMEGROUP_NONE)
6679 {
6680 if (!obs->timeOutputs)
6681 {
6682 obs->timeOutputs = std::make_shared<NAV::vendor::vectornav::TimeOutputs>();
6683 obs->timeOutputs->timeField |= vnSensor->_binaryOutputRegister.at(b).timeField;
6684 }
6685
6686 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP)
6687 {
6688 obs->timeOutputs->timeStartup = p.extractUint64();
6689 }
6690 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPS)
6691 {
6692 obs->timeOutputs->timeGps = p.extractUint64();
6693 }
6694 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)
6695 {
6696 obs->timeOutputs->gpsTow = p.extractUint64();
6697 }
6698 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK)
6699 {
6700 obs->timeOutputs->gpsWeek = p.extractUint16();
6701 }
6702 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESYNCIN)
6703 {
6704 obs->timeOutputs->timeSyncIn = p.extractUint64();
6705 }
6706 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPSPPS)
6707 {
6708 obs->timeOutputs->timePPS = p.extractUint64();
6709 }
6710 // if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEUTC)
6711 // {
6712 // obs->timeOutputs->timeUtc.year = p.extractInt8();
6713 // obs->timeOutputs->timeUtc.month = p.extractUint8();
6714 // obs->timeOutputs->timeUtc.day = p.extractUint8();
6715 // obs->timeOutputs->timeUtc.hour = p.extractUint8();
6716 // obs->timeOutputs->timeUtc.min = p.extractUint8();
6717 // obs->timeOutputs->timeUtc.sec = p.extractUint8();
6718 // obs->timeOutputs->timeUtc.ms = p.extractUint16();
6719 // }
6720 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCINCNT)
6721 {
6722 obs->timeOutputs->syncInCnt = p.extractUint32();
6723 }
6724 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCOUTCNT)
6725 {
6726 obs->timeOutputs->syncOutCnt = p.extractUint32();
6727 }
6728 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS)
6729 {
6730 obs->timeOutputs->timeStatus = p.extractUint8();
6731 }
6732 }
6733 // Group 3 (IMU)
6734 if (vnSensor->_binaryOutputRegister.at(b).imuField != vn::protocol::uart::ImuGroup::IMUGROUP_NONE)
6735 {
6736 if (!obs->imuOutputs)
6737 {
6738 obs->imuOutputs = std::make_shared<NAV::vendor::vectornav::ImuOutputs>();
6739 obs->imuOutputs->imuField |= vnSensor->_binaryOutputRegister.at(b).imuField;
6740 }
6741
6742 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_IMUSTATUS)
6743 {
6744 obs->imuOutputs->imuStatus = p.extractUint16();
6745 }
6746 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPMAG)
6747 {
6748 auto vec = p.extractVec3f();
6749 obs->imuOutputs->uncompMag = { vec.x, vec.y, vec.z };
6750 }
6751 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL)
6752 {
6753 auto vec = p.extractVec3f();
6754 obs->imuOutputs->uncompAccel = { vec.x, vec.y, vec.z };
6755 }
6756 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO)
6757 {
6758 auto vec = p.extractVec3f();
6759 obs->imuOutputs->uncompGyro = { vec.x, vec.y, vec.z };
6760 }
6761 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_TEMP)
6762 {
6763 obs->imuOutputs->temp = p.extractFloat();
6764 }
6765 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_PRES)
6766 {
6767 obs->imuOutputs->pres = p.extractFloat();
6768 }
6769 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA)
6770 {
6771 obs->imuOutputs->deltaTime = p.extractFloat();
6772 auto vec = p.extractVec3f();
6773 obs->imuOutputs->deltaTheta = { vec.x, vec.y, vec.z };
6774 }
6775 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL)
6776 {
6777 auto vec = p.extractVec3f();
6778 obs->imuOutputs->deltaV = { vec.x, vec.y, vec.z };
6779 }
6780 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_MAG)
6781 {
6782 auto vec = p.extractVec3f();
6783 obs->imuOutputs->mag = { vec.x, vec.y, vec.z };
6784 }
6785 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL)
6786 {
6787 auto vec = p.extractVec3f();
6788 obs->imuOutputs->accel = { vec.x, vec.y, vec.z };
6789 }
6790 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE)
6791 {
6792 auto vec = p.extractVec3f();
6793 obs->imuOutputs->angularRate = { vec.x, vec.y, vec.z };
6794 }
6795 }
6796 // Group 4 (GNSS1)
6797 if (vnSensor->_binaryOutputRegister.at(b).gpsField != vn::protocol::uart::GpsGroup::GPSGROUP_NONE)
6798 {
6799 if (!obs->gnss1Outputs)
6800 {
6801 obs->gnss1Outputs = std::make_shared<NAV::vendor::vectornav::GnssOutputs>();
6802 obs->gnss1Outputs->gnssField |= vnSensor->_binaryOutputRegister.at(b).gpsField;
6803 }
6804
6805 // if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_UTC)
6806 // {
6807 // obs->gnss1Outputs->timeUtc.year = p.extractInt8();
6808 // obs->gnss1Outputs->timeUtc.month = p.extractUint8();
6809 // obs->gnss1Outputs->timeUtc.day = p.extractUint8();
6810 // obs->gnss1Outputs->timeUtc.hour = p.extractUint8();
6811 // obs->gnss1Outputs->timeUtc.min = p.extractUint8();
6812 // obs->gnss1Outputs->timeUtc.sec = p.extractUint8();
6813 // obs->gnss1Outputs->timeUtc.ms = p.extractUint16();
6814 // }
6815 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
6816 {
6817 obs->gnss1Outputs->tow = p.extractUint64();
6818 }
6819 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
6820 {
6821 obs->gnss1Outputs->week = p.extractUint16();
6822 }
6823 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_NUMSATS)
6824 {
6825 obs->gnss1Outputs->numSats = p.extractUint8();
6826 }
6827 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
6828 {
6829 obs->gnss1Outputs->fix = p.extractUint8();
6830 }
6831 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA)
6832 {
6833 auto vec = p.extractVec3d();
6834 obs->gnss1Outputs->posLla = { vec.x, vec.y, vec.z };
6835 }
6836 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF)
6837 {
6838 auto vec = p.extractVec3d();
6839 obs->gnss1Outputs->posEcef = { vec.x, vec.y, vec.z };
6840 }
6841 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED)
6842 {
6843 auto vec = p.extractVec3f();
6844 obs->gnss1Outputs->velNed = { vec.x, vec.y, vec.z };
6845 }
6846 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF)
6847 {
6848 auto vec = p.extractVec3f();
6849 obs->gnss1Outputs->velEcef = { vec.x, vec.y, vec.z };
6850 }
6851 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_POSU)
6852 {
6853 auto vec = p.extractVec3f();
6854 obs->gnss1Outputs->posU = { vec.x, vec.y, vec.z };
6855 }
6856 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_VELU)
6857 {
6858 obs->gnss1Outputs->velU = p.extractFloat();
6859 }
6860 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEU)
6861 {
6862 obs->gnss1Outputs->timeU = p.extractFloat();
6863 }
6864 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO)
6865 {
6866 obs->gnss1Outputs->timeInfo.status = p.extractUint8();
6867 obs->gnss1Outputs->timeInfo.leapSeconds = p.extractInt8();
6868 }
6869 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_DOP)
6870 {
6871 obs->gnss1Outputs->dop.gDop = p.extractFloat();
6872 obs->gnss1Outputs->dop.pDop = p.extractFloat();
6873 obs->gnss1Outputs->dop.tDop = p.extractFloat();
6874 obs->gnss1Outputs->dop.vDop = p.extractFloat();
6875 obs->gnss1Outputs->dop.hDop = p.extractFloat();
6876 obs->gnss1Outputs->dop.nDop = p.extractFloat();
6877 obs->gnss1Outputs->dop.eDop = p.extractFloat();
6878 }
6879 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_SATINFO)
6880 {
6881 obs->gnss1Outputs->satInfo.numSats = p.extractUint8();
6882 p.extractUint8(); // Reserved for future use
6883
6884 LOG_DATA("{}: SatInfo: numSats {}", vnSensor->nameId(), obs->gnss1Outputs->satInfo.numSats);
6885 for (size_t i = 0; i < obs->gnss1Outputs->satInfo.numSats; i++)
6886 {
6887 auto sys = p.extractInt8();
6888 auto svId = p.extractUint8();
6889 auto flags = p.extractUint8();
6890 auto cno = p.extractUint8();
6891 auto qi = p.extractUint8();
6892 auto el = p.extractInt8();
6893 auto az = p.extractInt16();
6894 obs->gnss1Outputs->satInfo.satellites.emplace_back(sys, svId, flags, cno, qi, el, az);
6895 LOG_DATA("{}: SatInfo: sys {}, svId {}, flags {}, cno {}, qi {}, el {}, az {}", vnSensor->nameId(),
6896 sys, svId, flags, cno, qi, el, az);
6897 }
6898 }
6899 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_RAWMEAS)
6900 {
6901 obs->gnss1Outputs->raw.tow = p.extractDouble();
6902 obs->gnss1Outputs->raw.week = p.extractUint16();
6903 obs->gnss1Outputs->raw.numSats = p.extractUint8();
6904 p.extractUint8(); // Reserved for future use
6905 LOG_DATA("{}: RawMeas: tow {}, week {}, numSats {}", vnSensor->nameId(),
6906 obs->gnss1Outputs->raw.tow, obs->gnss1Outputs->raw.week, obs->gnss1Outputs->raw.numSats);
6907
6908 for (size_t i = 0; i < obs->gnss1Outputs->raw.numSats; i++)
6909 {
6910 auto sys = p.extractUint8();
6911 auto svId = p.extractUint8();
6912 auto freq = p.extractUint8();
6913 auto chan = p.extractUint8();
6914 auto slot = p.extractInt8();
6915 auto cno = p.extractUint8();
6916 auto flags = p.extractUint16();
6917 auto pr = p.extractDouble();
6918 auto cp = p.extractDouble();
6919 auto dp = p.extractFloat();
6920 obs->gnss1Outputs->raw.satellites.emplace_back(sys, svId, freq, chan, slot, cno, flags, pr, cp, dp);
6921 LOG_DATA("{}: RawMeas: sys {}, svId {}, freq {}, chan {}, slot {}, cno {}, flags {}, pr {}, cp {}, dp {}",
6922 vnSensor->nameId(), static_cast<int>(sys), static_cast<int>(svId), static_cast<int>(freq), static_cast<int>(chan),
6923 static_cast<int>(slot), static_cast<int>(cno), static_cast<int>(flags), pr, cp, dp);
6924 }
6925 }
6926 }
6927 // Group 5 (Attitude)
6928 if (vnSensor->_binaryOutputRegister.at(b).attitudeField != vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_NONE)
6929 {
6930 if (!obs->attitudeOutputs)
6931 {
6932 obs->attitudeOutputs = std::make_shared<NAV::vendor::vectornav::AttitudeOutputs>();
6933 obs->attitudeOutputs->attitudeField |= vnSensor->_binaryOutputRegister.at(b).attitudeField;
6934 }
6935
6936 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_VPESTATUS)
6937 {
6938 obs->attitudeOutputs->vpeStatus = p.extractUint16();
6939 }
6940 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YAWPITCHROLL)
6941 {
6942 auto vec = p.extractVec3f();
6943 obs->attitudeOutputs->ypr = { vec.x, vec.y, vec.z };
6944 }
6945 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_QUATERNION)
6946 {
6947 auto vec = p.extractVec4f();
6948 obs->attitudeOutputs->qtn = { vec.w, vec.x, vec.y, vec.z };
6949 }
6950 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_DCM)
6951 {
6952 auto col0 = p.extractVec3f();
6953 auto col1 = p.extractVec3f();
6954 auto col2 = p.extractVec3f();
6955 obs->attitudeOutputs->dcm << col0.x, col1.x, col2.x,
6956 col0.y, col1.y, col2.y,
6957 col0.z, col1.z, col2.z;
6958 }
6959 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_MAGNED)
6960 {
6961 auto vec = p.extractVec3f();
6962 obs->attitudeOutputs->magNed = { vec.x, vec.y, vec.z };
6963 }
6964 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_ACCELNED)
6965 {
6966 auto vec = p.extractVec3f();
6967 obs->attitudeOutputs->accelNed = { vec.x, vec.y, vec.z };
6968 }
6969 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELBODY)
6970 {
6971 auto vec = p.extractVec3f();
6972 obs->attitudeOutputs->linearAccelBody = { vec.x, vec.y, vec.z };
6973 }
6974 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELNED)
6975 {
6976 auto vec = p.extractVec3f();
6977 obs->attitudeOutputs->linearAccelNed = { vec.x, vec.y, vec.z };
6978 }
6979 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YPRU)
6980 {
6981 auto vec = p.extractVec3f();
6982 obs->attitudeOutputs->yprU = { vec.x, vec.y, vec.z };
6983 }
6984 }
6985 // Group 6 (INS)
6986 if (vnSensor->_binaryOutputRegister.at(b).insField != vn::protocol::uart::InsGroup::INSGROUP_NONE)
6987 {
6988 if (!obs->insOutputs)
6989 {
6990 obs->insOutputs = std::make_shared<NAV::vendor::vectornav::InsOutputs>();
6991 obs->insOutputs->insField |= vnSensor->_binaryOutputRegister.at(b).insField;
6992 }
6993
6994 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_INSSTATUS)
6995 {
6996 obs->insOutputs->insStatus = p.extractUint16();
6997 }
6998 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_POSLLA)
6999 {
7000 auto vec = p.extractVec3d();
7001 obs->insOutputs->posLla = { vec.x, vec.y, vec.z };
7002 }
7003 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_POSECEF)
7004 {
7005 auto vec = p.extractVec3d();
7006 obs->insOutputs->posEcef = { vec.x, vec.y, vec.z };
7007 }
7008 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_VELBODY)
7009 {
7010 auto vec = p.extractVec3f();
7011 obs->insOutputs->velBody = { vec.x, vec.y, vec.z };
7012 }
7013 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_VELNED)
7014 {
7015 auto vec = p.extractVec3f();
7016 obs->insOutputs->velNed = { vec.x, vec.y, vec.z };
7017 }
7018 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_VELECEF)
7019 {
7020 auto vec = p.extractVec3f();
7021 obs->insOutputs->velEcef = { vec.x, vec.y, vec.z };
7022 }
7023 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_MAGECEF)
7024 {
7025 auto vec = p.extractVec3f();
7026 obs->insOutputs->magEcef = { vec.x, vec.y, vec.z };
7027 }
7028 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_ACCELECEF)
7029 {
7030 auto vec = p.extractVec3f();
7031 obs->insOutputs->accelEcef = { vec.x, vec.y, vec.z };
7032 }
7033 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_LINEARACCELECEF)
7034 {
7035 auto vec = p.extractVec3f();
7036 obs->insOutputs->linearAccelEcef = { vec.x, vec.y, vec.z };
7037 }
7038 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_POSU)
7039 {
7040 obs->insOutputs->posU = p.extractFloat();
7041 }
7042 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_VELU)
7043 {
7044 obs->insOutputs->velU = p.extractFloat();
7045 }
7046 }
7047 // Group 7 (GNSS2)
7048 if (vnSensor->_binaryOutputRegister.at(b).gps2Field != vn::protocol::uart::GpsGroup::GPSGROUP_NONE)
7049 {
7050 if (!obs->gnss2Outputs)
7051 {
7052 obs->gnss2Outputs = std::make_shared<NAV::vendor::vectornav::GnssOutputs>();
7053 obs->gnss2Outputs->gnssField |= vnSensor->_binaryOutputRegister.at(b).gps2Field;
7054 }
7055
7056 // if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_UTC)
7057 // {
7058 // obs->gnss2Outputs->timeUtc.year = p.extractInt8();
7059 // obs->gnss2Outputs->timeUtc.month = p.extractUint8();
7060 // obs->gnss2Outputs->timeUtc.day = p.extractUint8();
7061 // obs->gnss2Outputs->timeUtc.hour = p.extractUint8();
7062 // obs->gnss2Outputs->timeUtc.min = p.extractUint8();
7063 // obs->gnss2Outputs->timeUtc.sec = p.extractUint8();
7064 // obs->gnss2Outputs->timeUtc.ms = p.extractUint16();
7065 // }
7066 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
7067 {
7068 obs->gnss2Outputs->tow = p.extractUint64();
7069 }
7070 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
7071 {
7072 obs->gnss2Outputs->week = p.extractUint16();
7073 }
7074 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_NUMSATS)
7075 {
7076 obs->gnss2Outputs->numSats = p.extractUint8();
7077 }
7078 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
7079 {
7080 obs->gnss2Outputs->fix = p.extractUint8();
7081 }
7082 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA)
7083 {
7084 auto vec = p.extractVec3d();
7085 obs->gnss2Outputs->posLla = { vec.x, vec.y, vec.z };
7086 }
7087 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF)
7088 {
7089 auto vec = p.extractVec3d();
7090 obs->gnss2Outputs->posEcef = { vec.x, vec.y, vec.z };
7091 }
7092 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED)
7093 {
7094 auto vec = p.extractVec3f();
7095 obs->gnss2Outputs->velNed = { vec.x, vec.y, vec.z };
7096 }
7097 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF)
7098 {
7099 auto vec = p.extractVec3f();
7100 obs->gnss2Outputs->velEcef = { vec.x, vec.y, vec.z };
7101 }
7102 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_POSU)
7103 {
7104 auto vec = p.extractVec3f();
7105 obs->gnss2Outputs->posU = { vec.x, vec.y, vec.z };
7106 }
7107 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_VELU)
7108 {
7109 obs->gnss2Outputs->velU = p.extractFloat();
7110 }
7111 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEU)
7112 {
7113 obs->gnss2Outputs->timeU = p.extractFloat();
7114 }
7115 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO)
7116 {
7117 obs->gnss2Outputs->timeInfo.status = p.extractUint8();
7118 obs->gnss2Outputs->timeInfo.leapSeconds = p.extractInt8();
7119 }
7120 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_DOP)
7121 {
7122 obs->gnss2Outputs->dop.gDop = p.extractFloat();
7123 obs->gnss2Outputs->dop.pDop = p.extractFloat();
7124 obs->gnss2Outputs->dop.tDop = p.extractFloat();
7125 obs->gnss2Outputs->dop.vDop = p.extractFloat();
7126 obs->gnss2Outputs->dop.hDop = p.extractFloat();
7127 obs->gnss2Outputs->dop.nDop = p.extractFloat();
7128 obs->gnss2Outputs->dop.eDop = p.extractFloat();
7129 }
7130 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_SATINFO)
7131 {
7132 obs->gnss2Outputs->satInfo.numSats = p.extractUint8();
7133 p.extractUint8(); // Reserved for future use
7134 for (size_t i = 0; i < obs->gnss2Outputs->satInfo.numSats; i++)
7135 {
7136 auto sys = p.extractInt8();
7137 auto svId = p.extractUint8();
7138 auto flags = p.extractUint8();
7139 auto cno = p.extractUint8();
7140 auto qi = p.extractUint8();
7141 auto el = p.extractInt8();
7142 auto az = p.extractInt16();
7143 obs->gnss2Outputs->satInfo.satellites.emplace_back(sys, svId, flags, cno, qi, el, az);
7144 }
7145 }
7146 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_RAWMEAS)
7147 {
7148 obs->gnss2Outputs->raw.tow = p.extractDouble();
7149 obs->gnss2Outputs->raw.week = p.extractUint16();
7150 obs->gnss2Outputs->raw.numSats = p.extractUint8();
7151 p.extractUint8(); // Reserved for future use
7152 for (size_t i = 0; i < obs->gnss2Outputs->raw.numSats; i++)
7153 {
7154 auto sys = p.extractUint8();
7155 auto svId = p.extractUint8();
7156 auto freq = p.extractUint8();
7157 auto chan = p.extractUint8();
7158 auto slot = p.extractInt8();
7159 auto cno = p.extractUint8();
7160 auto flags = p.extractUint16();
7161 auto pr = p.extractDouble();
7162 auto cp = p.extractDouble();
7163 auto dp = p.extractFloat();
7164 obs->gnss2Outputs->raw.satellites.emplace_back(sys, svId, freq, chan, slot, cno, flags, pr, cp, dp);
7165 }
7166 }
7167 }
7168
7169 if (p.getCurExtractLoc() != p.getPacketLength() - 2) // 2 Bytes CRC should be left
7170 {
7171 LOG_DEBUG("{}: Only {} of {} bytes were extracted from the Binary Output {}", vnSensor->nameId(), p.getCurExtractLoc(), p.getPacketLength(), b + 1);
7172 }
7173
7174 // --------------------------------------------- Fetch InsTime -----------------------------------------------
7175 auto updateSyncOut = [vnSensor, obs](const InsTime& ppsTime) {
7176 if (vnSensor->_synchronizationControlRegister.syncOutMode == vn::protocol::uart::SYNCOUTMODE_GPSPPS
7177 && (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCOUTCNT))
7178 {
7179 if (obs->timeOutputs->syncOutCnt > 0)
7180 {
7181 if (vnSensor->_timeSyncOut.syncOutCnt == 0)
7182 {
7183 LOG_INFO("{}: Found PPS time {} and is providing it to its connected nodes", vnSensor->nameId(), ppsTime.toYMDHMS(GPST));
7184 }
7185 if (vnSensor->_timeSyncOut.ppsTime != ppsTime || vnSensor->_timeSyncOut.syncOutCnt != obs->timeOutputs->syncOutCnt)
7186 {
7187 LOG_DATA("{}: SyncOut time {}, pps {}, syncOutCnt {}",
7188 vnSensor->nameId(), obs->insTime.toYMDHMS(GPST),
7189 ppsTime.toYMDHMS(GPST), obs->timeOutputs->syncOutCnt);
7190 }
7191 vnSensor->_timeSyncOut.ppsTime = ppsTime;
7192 vnSensor->_timeSyncOut.syncOutCnt = obs->timeOutputs->syncOutCnt;
7193 }
7194 }
7195 };
7196
7197 // Group 2 (Time)
7198 if (obs->timeOutputs && (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS))
7199 {
7200 if (obs->timeOutputs->timeStatus.dateOk())
7201 {
7202 if (obs->timeOutputs->timeStatus.timeOk()
7203 && (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)
7204 && (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK))
7205 {
7206 obs->insTime = InsTime(InsTime_GPSweekTow(0, obs->timeOutputs->gpsWeek, obs->timeOutputs->gpsTow * 1e-9L));
7207 updateSyncOut(InsTime(0, obs->timeOutputs->gpsWeek, std::floor(obs->timeOutputs->gpsTow * 1e-9L)));
7208 }
7209 else if (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPS)
7210 {
7211 auto secondsSinceEpoche = static_cast<long double>(obs->timeOutputs->timeGps) * 1e-9L;
7212 auto week = static_cast<uint16_t>(secondsSinceEpoche / static_cast<long double>(InsTimeUtil::SECONDS_PER_DAY * InsTimeUtil::DAYS_PER_WEEK));
7213 auto tow = secondsSinceEpoche - week * InsTimeUtil::SECONDS_PER_DAY * InsTimeUtil::DAYS_PER_WEEK;
7214
7215 obs->insTime = InsTime(InsTime_GPSweekTow(0, week, tow));
7216 updateSyncOut(InsTime(0, week, std::floor(tow)));
7217 }
7218 }
7219 }
7220
7221 if (obs->insTime.empty() // Look for master to give GNSS time
7222 && (obs->timeOutputs->timeField & vn::protocol::uart::TIMEGROUP_SYNCINCNT) // We need syncin cnt for this
7223 && (obs->timeOutputs->timeField & vn::protocol::uart::TIMEGROUP_TIMESYNCIN) // We need syncin time for this
7224 && vnSensor->_syncInPin && vnSensor->inputPins.front().isPinLinked()) // Try to get a sync from the master
7225 {
7226 if (auto timeSyncMaster = vnSensor->getInputValue<TimeSync>(0);
7227 timeSyncMaster && !timeSyncMaster->v->ppsTime.empty())
7228 {
7229 // This can have the following values
7230 // - -1: PPS -> VN310 message -> VN100 message (which happened before the VN310 message)
7231 // - 0: PPS -> VN310 message -> VN100 message
7232 // - 1: PPS -> VN100 message -> VN310 message
7233 int64_t syncCntDiff = obs->timeOutputs->syncInCnt - timeSyncMaster->v->syncOutCnt - vnSensor->_syncCntOffset;
7234
7235 if (std::abs(syncCntDiff) > 1) // Counters did not start at the same time
7236 {
7237 vnSensor->_syncCntOffset = obs->timeOutputs->syncInCnt - timeSyncMaster->v->syncOutCnt;
7238 syncCntDiff -= vnSensor->_syncCntOffset;
7239 LOG_DEBUG("{}: Counters did not start at the same time (_syncCntOffset = {})", vnSensor->nameId(), vnSensor->_syncCntOffset);
7240 }
7241 vnSensor->_lastSyncOutCnt = timeSyncMaster->v->syncOutCnt;
7242 vnSensor->_lastSyncInCnt = obs->timeOutputs->syncInCnt;
7243
7244 obs->insTime = timeSyncMaster->v->ppsTime + std::chrono::nanoseconds(obs->timeOutputs->timeSyncIn)
7245 + std::chrono::seconds(syncCntDiff);
7246 LOG_DATA("{}: SyncIn time {}, pps {}, syncOutCnt {}, syncInCnt {}, syncCntDiff {}",
7247 vnSensor->nameId(), obs->insTime.toYMDHMS(GPST), timeSyncMaster->v->ppsTime.toYMDHMS(GPST),
7248 timeSyncMaster->v->syncOutCnt, obs->timeOutputs->syncInCnt, syncCntDiff);
7249 }
7250 else
7251 {
7252 return;
7253 }
7254 }
7255
7256 if (obs->insTime.empty()
7257 && !vnSensor->_lastMessageTime.at(b).empty()
7258 && obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP)
7259 {
7260 obs->insTime = vnSensor->_lastMessageTime.at(b) + std::chrono::nanoseconds(obs->timeOutputs->timeStartup - vnSensor->_lastMessageTimeSinceStartup.at(b));
7261 }
7262
7263 if (obs->insTime.empty() // Look if other sensors have set a global time
7264 && !(obs->timeOutputs // but only if we did not request the time from the sensor via GNSS
7265 && (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS)
7266 && (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)
7267 && (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK)))
7268 {
7269 if (InsTime currentTime = util::time::GetCurrentInsTime();
7270 !currentTime.empty())
7271 {
7272 obs->insTime = currentTime;
7273 }
7274 }
7275
7276 if (!obs->insTime.empty())
7277 {
7278 if (!vnSensor->_lastMessageTime.at(b).empty())
7279 {
7280 // FIXME: This seems like a bug in clang-tidy. Check if it is working in future versions of clang-tidy
7281 // NOLINTNEXTLINE(hicpp-use-nullptr, modernize-use-nullptr)
7282 if (obs->insTime - vnSensor->_lastMessageTime.at(b) >= std::chrono::duration<double>(1.5 * (vnSensor->_binaryOutputRegister.at(b).rateDivisor / IMU_DEFAULT_FREQUENCY)))
7283 {
7284 LOG_WARN("{}: Potentially lost a message. dt = {:.4} s, expect {} s. (Previous message at [{}], current message [{}])", vnSensor->nameId(),
7285 static_cast<double>((obs->insTime - vnSensor->_lastMessageTime.at(b)).count()),
7286 1. / IMU_DEFAULT_FREQUENCY * vnSensor->_binaryOutputRegister.at(b).rateDivisor,
7287 vnSensor->_lastMessageTime.at(b), obs->insTime);
7288 }
7289 }
7290 vnSensor->_lastMessageTime.at(b) = obs->insTime;
7291 if (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP)
7292 {
7293 vnSensor->_lastMessageTimeSinceStartup.at(b) = obs->timeOutputs->timeStartup;
7294 }
7295 }
7296
7297 if ((vnSensor->_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output2 && (b == 0 || b == 1))
7298 || (vnSensor->_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output3 && (b == 0 || b == 2))
7299 || (vnSensor->_binaryOutputRegisterMerge == BinaryRegisterMerge::Output2_Output3 && (b == 1 || b == 2)))
7300 {
7301 if (vnSensor->_binaryOutputRegisterMergeObservation == nullptr)
7302 {
7303 std::stringstream sstream;
7304 if (!obs->insTime.empty())
7305 {
7306 sstream << obs->insTime;
7307 }
7308 else
7309 {
7310 sstream << obs->timeOutputs->timeStartup;
7311 }
7312 LOG_DATA("{}: {} - Storing message with time {}", vnSensor->nameId(), b, sstream.str());
7313
7314 vnSensor->_binaryOutputRegisterMergeObservation = obs;
7315 vnSensor->_binaryOutputRegisterMergeIndex = b;
7316 }
7317 else
7318 {
7319 auto allowedTimeDiff = std::chrono::microseconds(static_cast<int>(0.5 * (vnSensor->_binaryOutputRegister.at(b).rateDivisor / IMU_DEFAULT_FREQUENCY) * 1e6));
7320 if (vnSensor->_binaryOutputRegisterMergeIndex != b
7321 && ((!obs->insTime.empty() && !vnSensor->_binaryOutputRegisterMergeObservation->insTime.empty()
7322 && (obs->insTime - vnSensor->_binaryOutputRegisterMergeObservation->insTime < allowedTimeDiff)) // NOLINT(hicpp-use-nullptr, modernize-use-nullptr)
7323 || (obs->timeOutputs != nullptr && vnSensor->_binaryOutputRegisterMergeObservation->timeOutputs != nullptr
7324 && obs->timeOutputs->timeField & vn::protocol::uart::TIMEGROUP_TIMESTARTUP
7325 && vnSensor->_binaryOutputRegisterMergeObservation->timeOutputs->timeField & vn::protocol::uart::TIMEGROUP_TIMESTARTUP
7326 && (std::chrono::nanoseconds(obs->timeOutputs->timeStartup - vnSensor->_binaryOutputRegisterMergeObservation->timeOutputs->timeStartup) < allowedTimeDiff)))) // NOLINT(hicpp-use-nullptr, modernize-use-nullptr)
7327 {
7328 // Stored and new observation have same time, so merge them
7329 mergeVectorNavBinaryObservations(obs, vnSensor->_binaryOutputRegisterMergeObservation);
7330 vnSensor->_binaryOutputRegisterMergeObservation = nullptr;
7331
7332 std::stringstream sstream;
7333 if (!obs->insTime.empty())
7334 {
7335 sstream << obs->insTime;
7336 }
7337 else
7338 {
7339 sstream << obs->timeOutputs->timeStartup;
7340 }
7341 LOG_DATA("{}: {} - Merged message with time {}", vnSensor->nameId(), b, sstream.str());
7342
7343 vnSensor->invokeCallbacks(std::min(b, vnSensor->_binaryOutputRegisterMergeIndex) + 1, obs);
7344 }
7345 else
7346 {
7347 [[maybe_unused]] long double timeDiff = 0.0L;
7348 std::stringstream sstreamOld;
7349 std::stringstream sstreamNew;
7350 if (!obs->insTime.empty() && !vnSensor->_binaryOutputRegisterMergeObservation->insTime.empty())
7351 {
7352 timeDiff = (obs->insTime - vnSensor->_binaryOutputRegisterMergeObservation->insTime).count();
7353 sstreamOld << vnSensor->_binaryOutputRegisterMergeObservation->insTime;
7354 sstreamNew << obs->insTime;
7355 }
7356 else
7357 {
7358 sstreamOld << vnSensor->_binaryOutputRegisterMergeObservation->timeOutputs->timeStartup;
7359 sstreamNew << obs->timeOutputs->timeStartup;
7360 timeDiff = static_cast<double>(obs->timeOutputs->timeStartup
7361 - vnSensor->_binaryOutputRegisterMergeObservation->timeOutputs->timeStartup)
7362 * 1e-9;
7363 }
7364
7365 LOG_WARN("{}: Merging failed, because timestamps do not match. Potentially lost a message (diff {} sec, old ({}) {}, new ({}) {})",
7366 vnSensor->nameId(), timeDiff, vnSensor->_binaryOutputRegisterMergeIndex, sstreamOld.str(), b, sstreamNew.str());
7367
7368 // Send out old message and save new one
7369 vnSensor->invokeCallbacks(vnSensor->_binaryOutputRegisterMergeIndex + 1, vnSensor->_binaryOutputRegisterMergeObservation);
7370 vnSensor->_binaryOutputRegisterMergeObservation = obs;
7371 vnSensor->_binaryOutputRegisterMergeIndex = b;
7372 }
7373 }
7374 }
7375 else
7376 {
7377 if (!obs->insTime.empty())
7378 {
7379 LOG_DATA("{}: {} - Normal message with time {}", vnSensor->nameId(), b, obs->insTime);
7380 }
7381 // Calls all the callbacks
7382 vnSensor->invokeCallbacks(b + 1, obs);
7383 }
7384 }
7385 }
7386 }
7387 else if (p.type() == vn::protocol::uart::Packet::TYPE_ASCII)
7388 {
7389 LOG_DATA("{} received an ASCII Async message: {}", vnSensor->nameId(), p.datastr());
7390 vnSensor->_asciiOutputBuffer.push_back(p.datastr());
7391
7392 auto obs = std::make_shared<StringObs>(p.datastr());
7393
7394 if (InsTime currentTime = util::time::GetCurrentInsTime();
7395 !currentTime.empty())
7396 {
7397 obs->insTime = currentTime;
7398 }
7399 // Calls all the callbacks
7400 vnSensor->invokeCallbacks(VectorNavSensor::OUTPUT_PORT_INDEX_ASCII_OUTPUT, obs);
7401 }
7402 }
7403