INSTINCT Code Coverage Report


Directory: src/
File: Nodes/DataProvider/IMU/Sensors/VectorNavSensor.cpp
Date: 2025-02-07 16:54:41
Exec Total Coverage
Lines: 29 3813 0.8%
Functions: 5 202 2.5%
Branches: 40 8802 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 namespace nm = NAV::NodeManager;
20 #include "internal/FlowManager.hpp"
21
22 #include <imgui_internal.h>
23
24 #include "NodeData/General/StringObs.hpp"
25
26 #include "util/Time/TimeBase.hpp"
27 #include "Navigation/Transformations/CoordinateFrames.hpp"
28 #include "Navigation/Transformations/Units.hpp"
29
30 #include <map>
31
32 // to_json / from_json
33 namespace vn
34 {
35 namespace math
36 {
37 static void to_json(json& j, const vn::math::vec3f& vec) // NOLINT(misc-use-anonymous-namespace)
38 {
39 j = vec.c;
40 }
41 static void from_json(const json& j, vn::math::vec3f& vec) // NOLINT(misc-use-anonymous-namespace)
42 {
43 j.get_to(vec.c);
44 }
45 static void to_json(json& j, const vn::math::vec3d& vec) // NOLINT(misc-use-anonymous-namespace)
46 {
47 j = vec.c;
48 }
49 static void from_json(const json& j, vn::math::vec3d& vec) // NOLINT(misc-use-anonymous-namespace)
50 {
51 j.get_to(vec.c);
52 }
53 static void to_json(json& j, const vn::math::mat3f& mat) // NOLINT(misc-use-anonymous-namespace)
54 {
55 j = mat.e;
56 }
57 static void from_json(const json& j, vn::math::mat3f& mat) // NOLINT(misc-use-anonymous-namespace)
58 {
59 j.get_to(mat.e);
60 }
61 } // namespace math
62
63 namespace sensors
64 {
65 // ###########################################################################################################
66 // SYSTEM MODULE
67 // ###########################################################################################################
68
69 static void to_json(json& j, const SynchronizationControlRegister& synchronizationControlRegister) // NOLINT(misc-use-anonymous-namespace)
70 {
71 j = json{
72 { "syncInMode", synchronizationControlRegister.syncInMode },
73 { "syncInEdge", synchronizationControlRegister.syncInEdge },
74 { "syncInSkipFactor", synchronizationControlRegister.syncInSkipFactor },
75 { "syncOutMode", synchronizationControlRegister.syncOutMode },
76 { "syncOutPolarity", synchronizationControlRegister.syncOutPolarity },
77 { "syncOutSkipFactor", synchronizationControlRegister.syncOutSkipFactor },
78 { "syncOutPulseWidth", synchronizationControlRegister.syncOutPulseWidth },
79 };
80 }
81 static void from_json(const json& j, SynchronizationControlRegister& synchronizationControlRegister) // NOLINT(misc-use-anonymous-namespace)
82 {
83 if (j.contains("syncInMode"))
84 {
85 j.at("syncInMode").get_to(synchronizationControlRegister.syncInMode);
86 }
87 if (j.contains("syncInEdge"))
88 {
89 j.at("syncInEdge").get_to(synchronizationControlRegister.syncInEdge);
90 }
91 if (j.contains("syncInSkipFactor"))
92 {
93 j.at("syncInSkipFactor").get_to(synchronizationControlRegister.syncInSkipFactor);
94 }
95 if (j.contains("syncOutMode"))
96 {
97 j.at("syncOutMode").get_to(synchronizationControlRegister.syncOutMode);
98 }
99 if (j.contains("syncOutPolarity"))
100 {
101 j.at("syncOutPolarity").get_to(synchronizationControlRegister.syncOutPolarity);
102 }
103 if (j.contains("syncOutSkipFactor"))
104 {
105 j.at("syncOutSkipFactor").get_to(synchronizationControlRegister.syncOutSkipFactor);
106 }
107 if (j.contains("syncOutPulseWidth"))
108 {
109 j.at("syncOutPulseWidth").get_to(synchronizationControlRegister.syncOutPulseWidth);
110 }
111 }
112
113 static void to_json(json& j, const CommunicationProtocolControlRegister& communicationProtocolControlRegister) // NOLINT(misc-use-anonymous-namespace)
114 {
115 j = json{
116 { "serialCount", communicationProtocolControlRegister.serialCount },
117 { "serialStatus", communicationProtocolControlRegister.serialStatus },
118 { "spiCount", communicationProtocolControlRegister.spiCount },
119 { "spiStatus", communicationProtocolControlRegister.spiStatus },
120 { "serialChecksum", communicationProtocolControlRegister.serialChecksum },
121 { "spiChecksum", communicationProtocolControlRegister.spiChecksum },
122 { "errorMode", communicationProtocolControlRegister.errorMode },
123 };
124 }
125 static void from_json(const json& j, CommunicationProtocolControlRegister& communicationProtocolControlRegister) // NOLINT(misc-use-anonymous-namespace)
126 {
127 if (j.contains("serialCount"))
128 {
129 j.at("serialCount").get_to(communicationProtocolControlRegister.serialCount);
130 }
131 if (j.contains("serialStatus"))
132 {
133 j.at("serialStatus").get_to(communicationProtocolControlRegister.serialStatus);
134 }
135 if (j.contains("spiCount"))
136 {
137 j.at("spiCount").get_to(communicationProtocolControlRegister.spiCount);
138 }
139 if (j.contains("spiStatus"))
140 {
141 j.at("spiStatus").get_to(communicationProtocolControlRegister.spiStatus);
142 }
143 if (j.contains("serialChecksum"))
144 {
145 j.at("serialChecksum").get_to(communicationProtocolControlRegister.serialChecksum);
146 }
147 if (j.contains("spiChecksum"))
148 {
149 j.at("spiChecksum").get_to(communicationProtocolControlRegister.spiChecksum);
150 }
151 if (j.contains("errorMode"))
152 {
153 j.at("errorMode").get_to(communicationProtocolControlRegister.errorMode);
154 }
155 }
156
157 static void to_json(json& j, const BinaryOutputRegister& binaryOutputRegister) // NOLINT(misc-use-anonymous-namespace)
158 {
159 j = json{
160 { "asyncMode", binaryOutputRegister.asyncMode },
161 { "rateDivisor", binaryOutputRegister.rateDivisor },
162 { "commonField", binaryOutputRegister.commonField },
163 { "timeField", binaryOutputRegister.timeField },
164 { "imuField", binaryOutputRegister.imuField },
165 { "gpsField", binaryOutputRegister.gpsField },
166 { "attitudeField", binaryOutputRegister.attitudeField },
167 { "insField", binaryOutputRegister.insField },
168 { "gps2Field", binaryOutputRegister.gps2Field },
169 };
170 }
171 static void from_json(const json& j, BinaryOutputRegister& binaryOutputRegister) // NOLINT(misc-use-anonymous-namespace)
172 {
173 if (j.contains("asyncMode"))
174 {
175 j.at("asyncMode").get_to(binaryOutputRegister.asyncMode);
176 }
177 if (j.contains("rateDivisor"))
178 {
179 j.at("rateDivisor").get_to(binaryOutputRegister.rateDivisor);
180 }
181 if (j.contains("commonField"))
182 {
183 j.at("commonField").get_to(binaryOutputRegister.commonField);
184 }
185 if (j.contains("timeField"))
186 {
187 j.at("timeField").get_to(binaryOutputRegister.timeField);
188 }
189 if (j.contains("imuField"))
190 {
191 j.at("imuField").get_to(binaryOutputRegister.imuField);
192 }
193 if (j.contains("gpsField"))
194 {
195 j.at("gpsField").get_to(binaryOutputRegister.gpsField);
196 }
197 if (j.contains("attitudeField"))
198 {
199 j.at("attitudeField").get_to(binaryOutputRegister.attitudeField);
200 }
201 if (j.contains("insField"))
202 {
203 j.at("insField").get_to(binaryOutputRegister.insField);
204 }
205 if (j.contains("gps2Field"))
206 {
207 j.at("gps2Field").get_to(binaryOutputRegister.gps2Field);
208 }
209 }
210
211 // ###########################################################################################################
212 // IMU SUBSYSTEM
213 // ###########################################################################################################
214
215 static void to_json(json& j, const ImuFilteringConfigurationRegister& imuFilteringConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
216 {
217 j = json{
218 { "magWindowSize", imuFilteringConfigurationRegister.magWindowSize },
219 { "accelWindowSize", imuFilteringConfigurationRegister.accelWindowSize },
220 { "gyroWindowSize", imuFilteringConfigurationRegister.gyroWindowSize },
221 { "tempWindowSize", imuFilteringConfigurationRegister.tempWindowSize },
222 { "presWindowSize", imuFilteringConfigurationRegister.presWindowSize },
223 { "magFilterMode", imuFilteringConfigurationRegister.magFilterMode },
224 { "accelFilterMode", imuFilteringConfigurationRegister.accelFilterMode },
225 { "gyroFilterMode", imuFilteringConfigurationRegister.gyroFilterMode },
226 { "tempFilterMode", imuFilteringConfigurationRegister.tempFilterMode },
227 { "presFilterMode", imuFilteringConfigurationRegister.presFilterMode },
228 };
229 }
230 static void from_json(const json& j, ImuFilteringConfigurationRegister& imuFilteringConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
231 {
232 if (j.contains("magWindowSize"))
233 {
234 j.at("magWindowSize").get_to(imuFilteringConfigurationRegister.magWindowSize);
235 }
236 if (j.contains("accelWindowSize"))
237 {
238 j.at("accelWindowSize").get_to(imuFilteringConfigurationRegister.accelWindowSize);
239 }
240 if (j.contains("gyroWindowSize"))
241 {
242 j.at("gyroWindowSize").get_to(imuFilteringConfigurationRegister.gyroWindowSize);
243 }
244 if (j.contains("tempWindowSize"))
245 {
246 j.at("tempWindowSize").get_to(imuFilteringConfigurationRegister.tempWindowSize);
247 }
248 if (j.contains("presWindowSize"))
249 {
250 j.at("presWindowSize").get_to(imuFilteringConfigurationRegister.presWindowSize);
251 }
252 if (j.contains("magFilterMode"))
253 {
254 j.at("magFilterMode").get_to(imuFilteringConfigurationRegister.magFilterMode);
255 }
256 if (j.contains("accelFilterMode"))
257 {
258 j.at("accelFilterMode").get_to(imuFilteringConfigurationRegister.accelFilterMode);
259 }
260 if (j.contains("gyroFilterMode"))
261 {
262 j.at("gyroFilterMode").get_to(imuFilteringConfigurationRegister.gyroFilterMode);
263 }
264 if (j.contains("tempFilterMode"))
265 {
266 j.at("tempFilterMode").get_to(imuFilteringConfigurationRegister.tempFilterMode);
267 }
268 if (j.contains("presFilterMode"))
269 {
270 j.at("presFilterMode").get_to(imuFilteringConfigurationRegister.presFilterMode);
271 }
272 }
273
274 static void to_json(json& j, const DeltaThetaAndDeltaVelocityConfigurationRegister& deltaThetaAndDeltaVelocityConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
275 {
276 j = json{
277 { "integrationFrame", deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame },
278 { "gyroCompensation", deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation },
279 { "accelCompensation", deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation },
280 { "earthRateCorrection", deltaThetaAndDeltaVelocityConfigurationRegister.earthRateCorrection },
281 };
282 }
283 static void from_json(const json& j, DeltaThetaAndDeltaVelocityConfigurationRegister& deltaThetaAndDeltaVelocityConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
284 {
285 if (j.contains("integrationFrame"))
286 {
287 j.at("integrationFrame").get_to(deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame);
288 }
289 if (j.contains("gyroCompensation"))
290 {
291 j.at("gyroCompensation").get_to(deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation);
292 }
293 if (j.contains("accelCompensation"))
294 {
295 j.at("accelCompensation").get_to(deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation);
296 }
297 if (j.contains("earthRateCorrection"))
298 {
299 j.at("earthRateCorrection").get_to(deltaThetaAndDeltaVelocityConfigurationRegister.earthRateCorrection);
300 }
301 }
302
303 // ###########################################################################################################
304 // GNSS SUBSYSTEM
305 // ###########################################################################################################
306
307 static void to_json(json& j, const GpsConfigurationRegister& gpsConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
308 {
309 j = json{
310 { "mode", gpsConfigurationRegister.mode },
311 { "ppsSource", gpsConfigurationRegister.ppsSource },
312 { "rate", gpsConfigurationRegister.rate },
313 { "antPow", gpsConfigurationRegister.antPow },
314 };
315 }
316 static void from_json(const json& j, GpsConfigurationRegister& gpsConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
317 {
318 if (j.contains("mode"))
319 {
320 j.at("mode").get_to(gpsConfigurationRegister.mode);
321 }
322 if (j.contains("ppsSource"))
323 {
324 j.at("ppsSource").get_to(gpsConfigurationRegister.ppsSource);
325 }
326 if (j.contains("rate"))
327 {
328 j.at("rate").get_to(gpsConfigurationRegister.rate);
329 }
330 if (j.contains("antPow"))
331 {
332 j.at("antPow").get_to(gpsConfigurationRegister.antPow);
333 }
334 }
335
336 static void to_json(json& j, const GpsCompassBaselineRegister& gpsCompassBaselineRegister) // NOLINT(misc-use-anonymous-namespace)
337 {
338 j = json{
339 { "position", gpsCompassBaselineRegister.position },
340 { "uncertainty", gpsCompassBaselineRegister.uncertainty },
341 };
342 }
343 static void from_json(const json& j, GpsCompassBaselineRegister& gpsCompassBaselineRegister) // NOLINT(misc-use-anonymous-namespace)
344 {
345 if (j.contains("position"))
346 {
347 j.at("position").get_to(gpsCompassBaselineRegister.position);
348 }
349 if (j.contains("uncertainty"))
350 {
351 j.at("uncertainty").get_to(gpsCompassBaselineRegister.uncertainty);
352 }
353 }
354
355 // ###########################################################################################################
356 // ATTITUDE SUBSYSTEM
357 // ###########################################################################################################
358
359 static void to_json(json& j, const VpeBasicControlRegister& vpeBasicControlRegister) // NOLINT(misc-use-anonymous-namespace)
360 {
361 j = json{
362 { "enable", vpeBasicControlRegister.enable },
363 { "headingMode", vpeBasicControlRegister.headingMode },
364 { "filteringMode", vpeBasicControlRegister.filteringMode },
365 { "tuningMode", vpeBasicControlRegister.tuningMode },
366 };
367 }
368 static void from_json(const json& j, VpeBasicControlRegister& vpeBasicControlRegister) // NOLINT(misc-use-anonymous-namespace)
369 {
370 if (j.contains("enable"))
371 {
372 j.at("enable").get_to(vpeBasicControlRegister.enable);
373 }
374 if (j.contains("headingMode"))
375 {
376 j.at("headingMode").get_to(vpeBasicControlRegister.headingMode);
377 }
378 if (j.contains("filteringMode"))
379 {
380 j.at("filteringMode").get_to(vpeBasicControlRegister.filteringMode);
381 }
382 if (j.contains("tuningMode"))
383 {
384 j.at("tuningMode").get_to(vpeBasicControlRegister.tuningMode);
385 }
386 }
387
388 static void to_json(json& j, const VpeMagnetometerBasicTuningRegister& vpeMagnetometerBasicTuningRegister) // NOLINT(misc-use-anonymous-namespace)
389 {
390 j = json{
391 { "baseTuning", vpeMagnetometerBasicTuningRegister.baseTuning },
392 { "adaptiveTuning", vpeMagnetometerBasicTuningRegister.adaptiveTuning },
393 { "adaptiveFiltering", vpeMagnetometerBasicTuningRegister.adaptiveFiltering },
394 };
395 }
396 static void from_json(const json& j, VpeMagnetometerBasicTuningRegister& vpeMagnetometerBasicTuningRegister) // NOLINT(misc-use-anonymous-namespace)
397 {
398 if (j.contains("baseTuning"))
399 {
400 j.at("baseTuning").get_to(vpeMagnetometerBasicTuningRegister.baseTuning);
401 }
402 if (j.contains("adaptiveTuning"))
403 {
404 j.at("adaptiveTuning").get_to(vpeMagnetometerBasicTuningRegister.adaptiveTuning);
405 }
406 if (j.contains("adaptiveFiltering"))
407 {
408 j.at("adaptiveFiltering").get_to(vpeMagnetometerBasicTuningRegister.adaptiveFiltering);
409 }
410 }
411
412 static void to_json(json& j, const VpeAccelerometerBasicTuningRegister& vpeAccelerometerBasicTuningRegister) // NOLINT(misc-use-anonymous-namespace)
413 {
414 j = json{
415 { "baseTuning", vpeAccelerometerBasicTuningRegister.baseTuning },
416 { "adaptiveTuning", vpeAccelerometerBasicTuningRegister.adaptiveTuning },
417 { "adaptiveFiltering", vpeAccelerometerBasicTuningRegister.adaptiveFiltering },
418 };
419 }
420 static void from_json(const json& j, VpeAccelerometerBasicTuningRegister& vpeAccelerometerBasicTuningRegister) // NOLINT(misc-use-anonymous-namespace)
421 {
422 if (j.contains("baseTuning"))
423 {
424 j.at("baseTuning").get_to(vpeAccelerometerBasicTuningRegister.baseTuning);
425 }
426 if (j.contains("adaptiveTuning"))
427 {
428 j.at("adaptiveTuning").get_to(vpeAccelerometerBasicTuningRegister.adaptiveTuning);
429 }
430 if (j.contains("adaptiveFiltering"))
431 {
432 j.at("adaptiveFiltering").get_to(vpeAccelerometerBasicTuningRegister.adaptiveFiltering);
433 }
434 }
435
436 static void to_json(json& j, const VpeGyroBasicTuningRegister& vpeGyroBasicTuningRegister) // NOLINT(misc-use-anonymous-namespace)
437 {
438 j = json{
439 { "angularWalkVariance", vpeGyroBasicTuningRegister.angularWalkVariance },
440 { "baseTuning", vpeGyroBasicTuningRegister.baseTuning },
441 { "adaptiveTuning", vpeGyroBasicTuningRegister.adaptiveTuning },
442 };
443 }
444 static void from_json(const json& j, VpeGyroBasicTuningRegister& vpeGyroBasicTuningRegister) // NOLINT(misc-use-anonymous-namespace)
445 {
446 if (j.contains("angularWalkVariance"))
447 {
448 j.at("angularWalkVariance").get_to(vpeGyroBasicTuningRegister.angularWalkVariance);
449 }
450 if (j.contains("baseTuning"))
451 {
452 j.at("baseTuning").get_to(vpeGyroBasicTuningRegister.baseTuning);
453 }
454 if (j.contains("adaptiveTuning"))
455 {
456 j.at("adaptiveTuning").get_to(vpeGyroBasicTuningRegister.adaptiveTuning);
457 }
458 }
459
460 // ###########################################################################################################
461 // INS SUBSYSTEM
462 // ###########################################################################################################
463
464 static void to_json(json& j, const InsBasicConfigurationRegisterVn300& insBasicConfigurationRegisterVn300) // NOLINT(misc-use-anonymous-namespace)
465 {
466 j = json{
467 { "scenario", insBasicConfigurationRegisterVn300.scenario },
468 { "ahrsAiding", insBasicConfigurationRegisterVn300.ahrsAiding },
469 { "estBaseline", insBasicConfigurationRegisterVn300.estBaseline },
470 };
471 }
472 static void from_json(const json& j, InsBasicConfigurationRegisterVn300& insBasicConfigurationRegisterVn300) // NOLINT(misc-use-anonymous-namespace)
473 {
474 if (j.contains("scenario"))
475 {
476 j.at("scenario").get_to(insBasicConfigurationRegisterVn300.scenario);
477 }
478 if (j.contains("ahrsAiding"))
479 {
480 j.at("ahrsAiding").get_to(insBasicConfigurationRegisterVn300.ahrsAiding);
481 }
482 if (j.contains("estBaseline"))
483 {
484 j.at("estBaseline").get_to(insBasicConfigurationRegisterVn300.estBaseline);
485 }
486 }
487
488 static void to_json(json& j, const StartupFilterBiasEstimateRegister& startupFilterBiasEstimateRegister) // NOLINT(misc-use-anonymous-namespace)
489 {
490 j = json{
491 { "gyroBias", startupFilterBiasEstimateRegister.gyroBias },
492 { "accelBias", startupFilterBiasEstimateRegister.accelBias },
493 { "pressureBias", startupFilterBiasEstimateRegister.pressureBias },
494 };
495 }
496 static void from_json(const json& j, StartupFilterBiasEstimateRegister& startupFilterBiasEstimateRegister) // NOLINT(misc-use-anonymous-namespace)
497 {
498 if (j.contains("gyroBias"))
499 {
500 j.at("gyroBias").get_to(startupFilterBiasEstimateRegister.gyroBias);
501 }
502 if (j.contains("accelBias"))
503 {
504 j.at("accelBias").get_to(startupFilterBiasEstimateRegister.accelBias);
505 }
506 if (j.contains("pressureBias"))
507 {
508 j.at("pressureBias").get_to(startupFilterBiasEstimateRegister.pressureBias);
509 }
510 }
511
512 // ###########################################################################################################
513 // HARD/SOFT IRON ESTIMATOR SUBSYSTEM
514 // ###########################################################################################################
515
516 static void to_json(json& j, const MagnetometerCalibrationControlRegister& magnetometerCalibrationControlRegister) // NOLINT(misc-use-anonymous-namespace)
517 {
518 j = json{
519 { "hsiMode", magnetometerCalibrationControlRegister.hsiMode },
520 { "hsiOutput", magnetometerCalibrationControlRegister.hsiOutput },
521 { "convergeRate", magnetometerCalibrationControlRegister.convergeRate },
522 };
523 }
524 static void from_json(const json& j, MagnetometerCalibrationControlRegister& magnetometerCalibrationControlRegister) // NOLINT(misc-use-anonymous-namespace)
525 {
526 if (j.contains("hsiMode"))
527 {
528 j.at("hsiMode").get_to(magnetometerCalibrationControlRegister.hsiMode);
529 }
530 if (j.contains("hsiOutput"))
531 {
532 j.at("hsiOutput").get_to(magnetometerCalibrationControlRegister.hsiOutput);
533 }
534 if (j.contains("convergeRate"))
535 {
536 j.at("convergeRate").get_to(magnetometerCalibrationControlRegister.convergeRate);
537 }
538 }
539
540 // ###########################################################################################################
541 // WORLD MAGNETIC & GRAVITY MODULE
542 // ###########################################################################################################
543
544 static void to_json(json& j, const MagneticAndGravityReferenceVectorsRegister& magneticAndGravityReferenceVectorsRegister) // NOLINT(misc-use-anonymous-namespace)
545 {
546 j = json{
547 { "magRef", magneticAndGravityReferenceVectorsRegister.magRef },
548 { "accRef", magneticAndGravityReferenceVectorsRegister.accRef },
549 };
550 }
551 static void from_json(const json& j, MagneticAndGravityReferenceVectorsRegister& magneticAndGravityReferenceVectorsRegister) // NOLINT(misc-use-anonymous-namespace)
552 {
553 if (j.contains("magRef"))
554 {
555 j.at("magRef").get_to(magneticAndGravityReferenceVectorsRegister.magRef);
556 }
557 if (j.contains("accRef"))
558 {
559 j.at("accRef").get_to(magneticAndGravityReferenceVectorsRegister.accRef);
560 }
561 }
562
563 static void to_json(json& j, const ReferenceVectorConfigurationRegister& referenceVectorConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
564 {
565 j = json{
566 { "useMagModel", referenceVectorConfigurationRegister.useMagModel },
567 { "useGravityModel", referenceVectorConfigurationRegister.useGravityModel },
568 { "recalcThreshold", referenceVectorConfigurationRegister.recalcThreshold },
569 { "year", referenceVectorConfigurationRegister.year },
570 { "position", referenceVectorConfigurationRegister.position },
571 };
572 }
573 static void from_json(const json& j, ReferenceVectorConfigurationRegister& referenceVectorConfigurationRegister) // NOLINT(misc-use-anonymous-namespace)
574 {
575 if (j.contains("useMagModel"))
576 {
577 j.at("useMagModel").get_to(referenceVectorConfigurationRegister.useMagModel);
578 }
579 if (j.contains("useGravityModel"))
580 {
581 j.at("useGravityModel").get_to(referenceVectorConfigurationRegister.useGravityModel);
582 }
583 if (j.contains("recalcThreshold"))
584 {
585 j.at("recalcThreshold").get_to(referenceVectorConfigurationRegister.recalcThreshold);
586 }
587 if (j.contains("year"))
588 {
589 j.at("year").get_to(referenceVectorConfigurationRegister.year);
590 }
591 if (j.contains("position"))
592 {
593 j.at("position").get_to(referenceVectorConfigurationRegister.position);
594 }
595 }
596
597 // ###########################################################################################################
598 // VELOCITY AIDING
599 // ###########################################################################################################
600
601 static void to_json(json& j, const VelocityCompensationControlRegister& velocityCompensationControlRegister) // NOLINT(misc-use-anonymous-namespace)
602 {
603 j = json{
604 { "mode", velocityCompensationControlRegister.mode },
605 { "velocityTuning", velocityCompensationControlRegister.velocityTuning },
606 { "rateTuning", velocityCompensationControlRegister.rateTuning },
607 };
608 }
609 static void from_json(const json& j, VelocityCompensationControlRegister& velocityCompensationControlRegister) // NOLINT(misc-use-anonymous-namespace)
610 {
611 if (j.contains("mode"))
612 {
613 j.at("mode").get_to(velocityCompensationControlRegister.mode);
614 }
615 if (j.contains("velocityTuning"))
616 {
617 j.at("velocityTuning").get_to(velocityCompensationControlRegister.velocityTuning);
618 }
619 if (j.contains("rateTuning"))
620 {
621 j.at("rateTuning").get_to(velocityCompensationControlRegister.rateTuning);
622 }
623 }
624
625 } // namespace sensors
626 } // namespace vn
627
628 void NAV::VectorNavSensor::coupleImuFilterRates(NAV::VectorNavSensor* sensor, vn::sensors::BinaryOutputRegister& bor, uint32_t& /* binaryField */)
629 {
630 // Set 'TimeStartup' and 'TimeStatus' always
631 (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS);
632
633 if (sensor->_sensorModel == VectorNavModel::VN310)
634 {
635 (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK);
636 }
637
638 if (sensor->_coupleImuRateOutput)
639 {
640 if (bor.imuField & (vn::protocol::uart::IMUGROUP_ACCEL | vn::protocol::uart::IMUGROUP_UNCOMPACCEL))
641 {
642 sensor->_imuFilteringConfigurationRegister.accelWindowSize = bor.rateDivisor;
643 }
644 if (bor.imuField & (vn::protocol::uart::IMUGROUP_ANGULARRATE | vn::protocol::uart::IMUGROUP_UNCOMPGYRO))
645 {
646 sensor->_imuFilteringConfigurationRegister.gyroWindowSize = bor.rateDivisor;
647 }
648 if (bor.imuField & (vn::protocol::uart::IMUGROUP_MAG | vn::protocol::uart::IMUGROUP_UNCOMPMAG))
649 {
650 sensor->_imuFilteringConfigurationRegister.magWindowSize = bor.rateDivisor;
651 }
652 if (bor.imuField & (vn::protocol::uart::IMUGROUP_MAG | vn::protocol::uart::IMUGROUP_UNCOMPMAG))
653 {
654 sensor->_imuFilteringConfigurationRegister.magWindowSize = bor.rateDivisor;
655 }
656 if (bor.imuField & vn::protocol::uart::IMUGROUP_TEMP)
657 {
658 sensor->_imuFilteringConfigurationRegister.tempWindowSize = bor.rateDivisor;
659 }
660 if (bor.imuField & vn::protocol::uart::IMUGROUP_PRES)
661 {
662 sensor->_imuFilteringConfigurationRegister.presWindowSize = bor.rateDivisor;
663 }
664 LOG_DATA("Frequency of ImuFilter matches output frequency (coupled 'rateDivisor' and 'windowSize' of moving average filter.)");
665
666 if (sensor->isInitialized() && sensor->_vs.isConnected() && sensor->_vs.verifySensorConnectivity())
667 {
668 try
669 {
670 sensor->_vs.writeImuFilteringConfiguration(sensor->_imuFilteringConfigurationRegister);
671 }
672 catch (const std::exception& e)
673 {
674 LOG_ERROR("Could not configure the imuFilteringConfigurationRegister: {}", e.what());
675 sensor->doDeinitialize();
676 }
677 }
678 else
679 {
680 sensor->doDeinitialize();
681 }
682 }
683 };
684
685 // NOLINTBEGIN
686
687 // const std::array<NAV::VectorNavSensor::BinaryGroupData, 15> NAV::VectorNavSensor::_binaryGroupCommon = { {
688 // { /* 0 */ "TimeStartup", vn::protocol::uart::CommonGroup::COMMONGROUP_TIMESTARTUP,
689 // []() { 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."); } },
690 // { /* 1 */ "TimeGps", vn::protocol::uart::CommonGroup::COMMONGROUP_TIMEGPS,
691 // []() { 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."); },
692 // [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
693 // [](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); } },
694 // { /* 2 */ "TimeSyncIn", vn::protocol::uart::CommonGroup::COMMONGROUP_TIMESYNCIN,
695 // []() { 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."); } },
696 // { /* 3 */ "YawPitchRoll", vn::protocol::uart::CommonGroup::COMMONGROUP_YAWPITCHROLL,
697 // []() { 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°]"); } },
698 // { /* 4 */ "Quaternion", vn::protocol::uart::CommonGroup::COMMONGROUP_QUATERNION,
699 // []() { 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."); } },
700 // { /* 5 */ "AngularRate", vn::protocol::uart::CommonGroup::COMMONGROUP_ANGULARRATE,
701 // []() { 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."); } },
702 // { /* 6 */ "Position", vn::protocol::uart::CommonGroup::COMMONGROUP_POSITION,
703 // []() { 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."); },
704 // [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
705 // [](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); } },
706 // { /* 7 */ "Velocity", vn::protocol::uart::CommonGroup::COMMONGROUP_VELOCITY,
707 // []() { 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."); },
708 // [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
709 // [](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); } },
710 // { /* 8 */ "Accel", vn::protocol::uart::CommonGroup::COMMONGROUP_ACCEL,
711 // []() { 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."); } },
712 // { /* 9 */ "Imu", vn::protocol::uart::CommonGroup::COMMONGROUP_IMU,
713 // []() { 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."); } },
714 // { /* 10 */ "MagPres", vn::protocol::uart::CommonGroup::COMMONGROUP_MAGPRES,
715 // []() { 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."); } },
716 // { /* 11 */ "DeltaTheta", vn::protocol::uart::CommonGroup::COMMONGROUP_DELTATHETA,
717 // []() { 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."); } },
718 // { /* 12 */ "Ins/VpeStatus", vn::protocol::uart::CommonGroup::COMMONGROUP_INSSTATUS,
719 // []() { 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.");
720 // ImGui::Separator();
721 // 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."); },
722 // [](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); } },
723 // { /* 13 */ "SyncInCnt", vn::protocol::uart::CommonGroup::COMMONGROUP_SYNCINCNT,
724 // []() { 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."); } },
725 // { /* 14 */ "TimeGpsPps", vn::protocol::uart::CommonGroup::COMMONGROUP_TIMEGPSPPS,
726 // []() { 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."); },
727 // [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; } },
728 // } };
729
730 const std::array<NAV::VectorNavSensor::BinaryGroupData, 10> NAV::VectorNavSensor::_binaryGroupTime = { {
731 { /* 0 */ "TimeStartup", vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP,
732 []() { 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)."); },
733 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& bor, uint32_t /* binaryField */) { return (sensorModel != VectorNavModel::VN310
734 || !((bor.gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
735 || (bor.gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
736 || (bor.gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
737 || (bor.gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)))
738 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPMAG)
739 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL)
740 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO)
741 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_TEMP)
742 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_PRES)
743 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA)
744 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL)
745 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_MAG)
746 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL)
747 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE)
748 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YAWPITCHROLL)
749 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_QUATERNION)
750 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_DCM)
751 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_MAGNED)
752 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_ACCELNED)
753 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELBODY)
754 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELNED)
755 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YPRU)
756 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_INSSTATUS)
757 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSLLA)
758 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSECEF)
759 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELBODY)
760 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELNED)
761 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELECEF)
762 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_MAGECEF)
763 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_ACCELECEF)
764 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_LINEARACCELECEF)
765 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSU)
766 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELU); } },
767 { /* 1 */ "TimeGps", vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPS,
768 []() { ImGui::TextUnformatted("Absolute GPS time.\n\nThe absolute GPS time since start of GPS epoch 1980 expressed in nano seconds."); },
769 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
770 [](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); } },
771 { /* 2 */ "GpsTow", vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW,
772 []() { ImGui::TextUnformatted("Time since start of GPS week.\n\nThe time since the start of the current GPS time week expressed in nano seconds."); },
773 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& bor, uint32_t /* binaryField */) { return (sensorModel == VectorNavModel::VN310)
774 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPMAG)
775 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL)
776 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO)
777 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_TEMP)
778 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_PRES)
779 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA)
780 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL)
781 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_MAG)
782 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL)
783 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE)
784 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YAWPITCHROLL)
785 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_QUATERNION)
786 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_DCM)
787 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_MAGNED)
788 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_ACCELNED)
789 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELBODY)
790 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELNED)
791 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YPRU)
792 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_INSSTATUS)
793 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSLLA)
794 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSECEF)
795 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELBODY)
796 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELNED)
797 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELECEF)
798 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_MAGECEF)
799 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_ACCELECEF)
800 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_LINEARACCELECEF)
801 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSU)
802 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELU); },
803 [](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); } },
804 { /* 3 */ "GpsWeek", vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK,
805 []() { ImGui::TextUnformatted("GPS week.\n\nThe current GPS week."); },
806 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& bor, uint32_t /* binaryField */) { return (sensorModel == VectorNavModel::VN310)
807 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPMAG)
808 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL)
809 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO)
810 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_TEMP)
811 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_PRES)
812 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA)
813 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL)
814 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_MAG)
815 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL)
816 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE)
817 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YAWPITCHROLL)
818 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_QUATERNION)
819 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_DCM)
820 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_MAGNED)
821 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_ACCELNED)
822 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELBODY)
823 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELNED)
824 && !(bor.attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YPRU)
825 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_INSSTATUS)
826 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSLLA)
827 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSECEF)
828 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELBODY)
829 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELNED)
830 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELECEF)
831 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_MAGECEF)
832 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_ACCELECEF)
833 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_LINEARACCELECEF)
834 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_POSU)
835 && !(bor.insField & vn::protocol::uart::InsGroup::INSGROUP_VELU); },
836 [](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); } },
837 { /* 4 */ "TimeSyncIn", vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESYNCIN,
838 []() { ImGui::TextUnformatted("Time since last SyncIn trigger.\n\nThe time since the last SyncIn event trigger expressed in nano seconds."); } },
839 { /* 5 */ "TimeGpsPps", vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPSPPS,
840 []() { ImGui::TextUnformatted("Time since last GPS PPS trigger.\n\nThe time since the last GPS PPS trigger event expressed in nano seconds."); },
841 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; } },
842 { /* 6 */ "TimeUTC", vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEUTC,
843 []() { 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."); },
844 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return false; },
845 [](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); } },
846 { /* 7 */ "SyncInCnt", vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCINCNT,
847 []() { ImGui::TextUnformatted("SyncIn trigger count.\n\nThe number of SyncIn trigger events that have occurred."); } },
848 { /* 8 */ "SyncOutCnt", vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCOUTCNT,
849 []() { ImGui::TextUnformatted("SyncOut trigger count.\n\nThe number of SyncOut trigger events that have occurred."); } },
850 { /* 9 */ "TimeStatus", vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS,
851 []() { ImGui::TextUnformatted("Time valid status flags.");
852 if (ImGui::BeginTable("VectorNavTimeStatusTooltip", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
853 {
854 ImGui::TableSetupColumn("Bit Offset", ImGuiTableColumnFlags_WidthFixed);
855 ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthFixed);
856 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthFixed);
857 ImGui::TableHeadersRow();
858
859 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
860 ImGui::TableNextColumn(); ImGui::TextUnformatted("timeOk");
861 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 - GpsTow is valid");
862
863 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
864 ImGui::TableNextColumn(); ImGui::TextUnformatted("dateOk");
865 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 - TimeGps and GpsWeek are valid");
866
867 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
868 ImGui::TableNextColumn(); ImGui::TextUnformatted("utcTimeValid");
869 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 - UTC time is valid");
870
871 ImGui::TableNextColumn(); ImGui::TextUnformatted("3 - 7");
872 ImGui::TableNextColumn(); ImGui::TextUnformatted("resv");
873 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved for future use");
874
875 ImGui::EndTable();
876 } },
877 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& bor, uint32_t /* binaryField */) { return !(bor.commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_TIMEGPS)
878 && !(bor.timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)
879 && !(bor.timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK)
880 && !(bor.timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPS)
881 && !(bor.timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEUTC)
882 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPMAG)
883 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL)
884 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO)
885 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_TEMP)
886 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_PRES)
887 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA)
888 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL)
889 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_MAG)
890 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL)
891 && !(bor.imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE); } },
892 } };
893
894 const std::array<NAV::VectorNavSensor::BinaryGroupData, 11> NAV::VectorNavSensor::_binaryGroupIMU{ {
895 { /* 0 */ "ImuStatus", vn::protocol::uart::ImuGroup::IMUGROUP_IMUSTATUS,
896 []() { ImGui::TextUnformatted("Status is reserved for future use. Not currently used in the current code, as such will always report 0."); },
897 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return false; } },
898 { /* 1 */ "UncompMag", vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPMAG,
899 []() { 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."); },
900 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
901 { /* 2 */ "UncompAccel", vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL,
902 []() { 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."); },
903 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
904 { /* 3 */ "UncompGyro", vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO,
905 []() { 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."); },
906 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
907 { /* 4 */ "Temp", vn::protocol::uart::ImuGroup::IMUGROUP_TEMP,
908 []() { ImGui::TextUnformatted("Temperature measurement.\n\nThe IMU temperature measured in units of Celsius."); },
909 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
910 { /* 5 */ "Pres", vn::protocol::uart::ImuGroup::IMUGROUP_PRES,
911 []() { 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."); },
912 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
913 { /* 6 */ "DeltaTheta", vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA,
914 []() { 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."); } },
915 { /* 7 */ "DeltaVel", vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL,
916 []() { 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."); } },
917 { /* 8 */ "Mag", vn::protocol::uart::ImuGroup::IMUGROUP_MAG,
918 []() { 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."); },
919 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
920 { /* 9 */ "Accel", vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL,
921 []() { 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."); },
922 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
923 { /* 10 */ "AngularRate", vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE,
924 []() { 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."); },
925 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; }, coupleImuFilterRates },
926 } };
927
928 const std::array<NAV::VectorNavSensor::BinaryGroupData, 16> NAV::VectorNavSensor::_binaryGroupGNSS{ {
929 { /* 0 */ "UTC", vn::protocol::uart::GpsGroup::GPSGROUP_UTC,
930 []() { 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."); },
931 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return false; },
932 [](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); } },
933 { /* 1 */ "Tow", vn::protocol::uart::GpsGroup::GPSGROUP_TOW,
934 []() { ImGui::TextUnformatted("GPS time of week\n\nThe GPS time of week given in nano seconds."); },
935 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t binaryField) { return sensorModel == VectorNavModel::VN310
936 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA)
937 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF)
938 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED)
939 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF)
940 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSU)
941 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELU); },
942 [](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)); } },
943 { /* 2 */ "Week", vn::protocol::uart::GpsGroup::GPSGROUP_WEEK,
944 []() { ImGui::TextUnformatted("GPS week\n\nThe current GPS week."); },
945 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t binaryField) { return sensorModel == VectorNavModel::VN310
946 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA)
947 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF)
948 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED)
949 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF)
950 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSU)
951 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELU); },
952 [](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)); } },
953 { /* 3 */ "NumSats", vn::protocol::uart::GpsGroup::GPSGROUP_NUMSATS,
954 []() { ImGui::TextUnformatted("Number of tracked satellites\n\nThe number of tracked GNSS satellites."); },
955 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; } },
956 { /* 4 */ "Fix", vn::protocol::uart::GpsGroup::GPSGROUP_FIX,
957 []() { ImGui::TextUnformatted("GNSS fix\n\nThe current GNSS fix.");
958 if (ImGui::BeginTable("VectorNavFixTooltip", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
959 {
960 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed);
961 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthFixed);
962 ImGui::TableHeadersRow();
963
964 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
965 ImGui::TableNextColumn(); ImGui::TextUnformatted("No fix");
966
967 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
968 ImGui::TableNextColumn(); ImGui::TextUnformatted("Time only");
969
970 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
971 ImGui::TableNextColumn(); ImGui::TextUnformatted("2D");
972
973 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
974 ImGui::TableNextColumn(); ImGui::TextUnformatted("3D");
975
976 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
977 ImGui::TableNextColumn(); ImGui::TextUnformatted("SBAS");
978
979 ImGui::TableNextColumn(); ImGui::TextUnformatted("7");
980 ImGui::TableNextColumn(); ImGui::TextUnformatted("RTK Float (only GNSS1)");
981
982 ImGui::TableNextColumn(); ImGui::TextUnformatted("8");
983 ImGui::TableNextColumn(); ImGui::TextUnformatted("RTK Fixed (only GNSS1)");
984
985 ImGui::EndTable();
986 } },
987 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t binaryField) { return sensorModel == VectorNavModel::VN310
988 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA)
989 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF)
990 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED)
991 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF)
992 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSU)
993 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELU); } },
994 { /* 5 */ "PosLla", vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA,
995 []() { 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."); },
996 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
997 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) {
998 (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA)
999 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
1000 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
1001 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
1002 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO);
1003 bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP;
1004 } },
1005 { /* 6 */ "PosEcef", vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF,
1006 []() { ImGui::TextUnformatted("GNSS position (ECEF)\n\nThe current GNSS position given in the Earth centered Earth fixed (ECEF) coordinate frame, given in meters."); },
1007 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1008 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) {
1009 (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF)
1010 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
1011 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
1012 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
1013 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO);
1014 bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP;
1015 } },
1016 { /* 7 */ "VelNed", vn::protocol::uart::GpsGroup::GPSGROUP_VELNED,
1017 []() { ImGui::TextUnformatted("GNSS velocity (NED)\n\nThe current GNSS velocity in the North East Down (NED) coordinate frame, given in m/s."); },
1018 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1019 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) {
1020 (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED)
1021 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
1022 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
1023 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
1024 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO);
1025 bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP;
1026 } },
1027 { /* 8 */ "VelEcef", vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF,
1028 []() { ImGui::TextUnformatted("GNSS velocity (ECEF)\n\nThe current GNSS velocity in the Earth centered Earth fixed (ECEF) coordinate frame, given in m/s."); },
1029 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1030 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) {
1031 (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF)
1032 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
1033 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
1034 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
1035 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO);
1036 bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP;
1037 } },
1038 { /* 9 */ "PosU", vn::protocol::uart::GpsGroup::GPSGROUP_POSU,
1039 []() { 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)."); },
1040 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1041 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) {
1042 (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_POSU)
1043 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
1044 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
1045 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
1046 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO);
1047 bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP;
1048 } },
1049 { /* 10 */ "VelU", vn::protocol::uart::GpsGroup::GPSGROUP_VELU,
1050 []() { ImGui::TextUnformatted("GNSS velocity uncertainty\n\nThe current GNSS velocity uncertainty, given in m/s (1 Sigma)."); },
1051 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1052 [](VectorNavSensor* /* sensor */, vn::sensors::BinaryOutputRegister& bor, uint32_t& binaryField) {
1053 (static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_VELU)
1054 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
1055 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
1056 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
1057 && (binaryField |= vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO);
1058 bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP;
1059 } },
1060 { /* 11 */ "TimeU", vn::protocol::uart::GpsGroup::GPSGROUP_TIMEU,
1061 []() { ImGui::TextUnformatted("GNSS time uncertainty\n\nThe current GPS time uncertainty, given in seconds (1 Sigma)."); },
1062 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1063 [](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); } },
1064 { /* 12 */ "TimeInfo", vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO,
1065 []() { ImGui::TextUnformatted("GNSS time status and leap seconds\n\nFlags for valid GPS TOW, week number and UTC and current leap seconds.");
1066 if (ImGui::BeginTable("VectorNavTimeInfoTooltip", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1067 {
1068 ImGui::TableSetupColumn("Bit Offset");
1069 ImGui::TableSetupColumn("Field");
1070 ImGui::TableSetupColumn("Description");
1071 ImGui::TableHeadersRow();
1072
1073 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1074 ImGui::TableNextColumn(); ImGui::TextUnformatted("timeOk");
1075 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 - GpsTow is valid");
1076
1077 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1078 ImGui::TableNextColumn(); ImGui::TextUnformatted("dateOk");
1079 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 - TimeGps and GpsWeek are valid");
1080
1081 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1082 ImGui::TableNextColumn(); ImGui::TextUnformatted("utcTimeValid");
1083 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 - UTC time is valid");
1084
1085 ImGui::TableNextColumn(); ImGui::TextUnformatted("3 - 7");
1086 ImGui::TableNextColumn(); ImGui::TextUnformatted("resv");
1087 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved for future use");
1088
1089 ImGui::EndTable();
1090 } },
1091 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t binaryField) { return sensorModel == VectorNavModel::VN310
1092 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
1093 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
1094 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_UTC)
1095 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEU)
1096 && !(static_cast<vn::protocol::uart::GpsGroup>(binaryField) & vn::protocol::uart::GpsGroup::GPSGROUP_RAWMEAS); } },
1097 { /* 13 */ "DOP", vn::protocol::uart::GpsGroup::GPSGROUP_DOP,
1098 []() { ImGui::TextUnformatted("Dilution of precision"); },
1099 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; } },
1100 { /* 14 */ "SatInfo", vn::protocol::uart::GpsGroup::GPSGROUP_SATINFO,
1101 []() { ImGui::TextUnformatted("Satellite Information\n\nInformation and measurements pertaining to each GNSS satellite in view.\n\nSatInfo Element:");
1102 if (ImGui::BeginTable("VectorNavSatInfoTooltip", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1103 {
1104 ImGui::TableSetupColumn("Name");
1105 ImGui::TableSetupColumn("Description");
1106 ImGui::TableHeadersRow();
1107
1108 ImGui::TableNextColumn(); ImGui::TextUnformatted("sys");
1109 ImGui::TableNextColumn(); ImGui::TextUnformatted("GNSS constellation indicator. See table below for details.");
1110
1111 ImGui::TableNextColumn(); ImGui::TextUnformatted("svId");
1112 ImGui::TableNextColumn(); ImGui::TextUnformatted("Space vehicle Id");
1113
1114 ImGui::TableNextColumn(); ImGui::TextUnformatted("flags");
1115 ImGui::TableNextColumn(); ImGui::TextUnformatted("Tracking info flags. See table below for details.");
1116
1117 ImGui::TableNextColumn(); ImGui::TextUnformatted("cno");
1118 ImGui::TableNextColumn(); ImGui::TextUnformatted("Carrier-to-noise density ratio (signal strength) [dB-Hz]");
1119
1120 ImGui::TableNextColumn(); ImGui::TextUnformatted("qi");
1121 ImGui::TableNextColumn(); ImGui::TextUnformatted("Quality Indicator. See table below for details.");
1122
1123 ImGui::TableNextColumn(); ImGui::TextUnformatted("el");
1124 ImGui::TableNextColumn(); ImGui::TextUnformatted("Elevation in degrees");
1125
1126 ImGui::TableNextColumn(); ImGui::TextUnformatted("az");
1127 ImGui::TableNextColumn(); ImGui::TextUnformatted("Azimuth angle in degrees");
1128
1129 ImGui::EndTable();
1130 }
1131 ImGui::BeginChild("VectorNavSatInfoTooltipGNSSConstelationChild", ImVec2(230, 217));
1132 ImGui::TextUnformatted("\nGNSS constellation indicator:");
1133 if (ImGui::BeginTable("VectorNavSatInfoTooltipGNSSConstelation", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1134 {
1135 ImGui::TableSetupColumn("Value");
1136 ImGui::TableSetupColumn("Description");
1137 ImGui::TableHeadersRow();
1138
1139 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1140 ImGui::TableNextColumn(); ImGui::TextUnformatted("GPS");
1141
1142 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1143 ImGui::TableNextColumn(); ImGui::TextUnformatted("SBAS");
1144
1145 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1146 ImGui::TableNextColumn(); ImGui::TextUnformatted("Galileo");
1147
1148 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1149 ImGui::TableNextColumn(); ImGui::TextUnformatted("BeiDou");
1150
1151 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1152 ImGui::TableNextColumn(); ImGui::TextUnformatted("IMES");
1153
1154 ImGui::TableNextColumn(); ImGui::TextUnformatted("5");
1155 ImGui::TableNextColumn(); ImGui::TextUnformatted("QZSS");
1156
1157 ImGui::TableNextColumn(); ImGui::TextUnformatted("6");
1158 ImGui::TableNextColumn(); ImGui::TextUnformatted("GLONASS");
1159
1160 ImGui::EndTable();
1161 }
1162 ImGui::EndChild();
1163 ImGui::SameLine();
1164 ImGui::BeginChild("VectorNavSatInfoTooltipFlagsChild", ImVec2(260, 217));
1165 ImGui::TextUnformatted("\nTracking info flags:");
1166 if (ImGui::BeginTable("VectorNavSatInfoTooltipFlags", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1167 {
1168 ImGui::TableSetupColumn("Bit Offset");
1169 ImGui::TableSetupColumn("Description");
1170 ImGui::TableHeadersRow();
1171
1172 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1173 ImGui::TableNextColumn(); ImGui::TextUnformatted("Healthy");
1174
1175 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1176 ImGui::TableNextColumn(); ImGui::TextUnformatted("Almanac");
1177
1178 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1179 ImGui::TableNextColumn(); ImGui::TextUnformatted("Ephemeris");
1180
1181 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1182 ImGui::TableNextColumn(); ImGui::TextUnformatted("Differential Correction");
1183
1184 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1185 ImGui::TableNextColumn(); ImGui::TextUnformatted("Used for Navigation");
1186
1187 ImGui::TableNextColumn(); ImGui::TextUnformatted("5");
1188 ImGui::TableNextColumn(); ImGui::TextUnformatted("Azimuth / Elevation Valid");
1189
1190 ImGui::TableNextColumn(); ImGui::TextUnformatted("6");
1191 ImGui::TableNextColumn(); ImGui::TextUnformatted("Used for RTK");
1192
1193 ImGui::EndTable();
1194 }
1195 ImGui::EndChild();
1196 ImGui::TextUnformatted("\nQuality Indicators:");
1197 if (ImGui::BeginTable("VectorNavSatInfoTooltipQuality", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1198 {
1199 ImGui::TableSetupColumn("Value");
1200 ImGui::TableSetupColumn("Description");
1201 ImGui::TableHeadersRow();
1202
1203 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1204 ImGui::TableNextColumn(); ImGui::TextUnformatted("No signal");
1205
1206 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1207 ImGui::TableNextColumn(); ImGui::TextUnformatted("Searching signal");
1208
1209 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1210 ImGui::TableNextColumn(); ImGui::TextUnformatted("Signal acquired");
1211
1212 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1213 ImGui::TableNextColumn(); ImGui::TextUnformatted("Signal detected but unstable");
1214
1215 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1216 ImGui::TableNextColumn(); ImGui::TextUnformatted("Code locked and time synchronized");
1217
1218 ImGui::TableNextColumn(); ImGui::TextUnformatted("5, 6, 7");
1219 ImGui::TableNextColumn(); ImGui::TextUnformatted("Code and carrier locked and time synchronized");
1220
1221 ImGui::EndTable();
1222 } },
1223 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; } },
1224 { /* 15 */ "RawMeas", vn::protocol::uart::GpsGroup::GPSGROUP_RAWMEAS,
1225 []() { ImGui::TextUnformatted("GNSS Raw Measurements.\n\nSatRaw Element:");
1226 if (ImGui::BeginTable("VectorNavSatRawTooltip", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1227 {
1228 ImGui::TableSetupColumn("Name");
1229 ImGui::TableSetupColumn("Description");
1230 ImGui::TableHeadersRow();
1231
1232 ImGui::TableNextColumn(); ImGui::TextUnformatted("sys");
1233 ImGui::TableNextColumn(); ImGui::TextUnformatted("GNSS constellation indicator. See table below for details.");
1234
1235 ImGui::TableNextColumn(); ImGui::TextUnformatted("svId");
1236 ImGui::TableNextColumn(); ImGui::TextUnformatted("Space vehicle Id");
1237
1238 ImGui::TableNextColumn(); ImGui::TextUnformatted("freq");
1239 ImGui::TableNextColumn(); ImGui::TextUnformatted("Frequency indicator. See table below for details.");
1240
1241 ImGui::TableNextColumn(); ImGui::TextUnformatted("chan");
1242 ImGui::TableNextColumn(); ImGui::TextUnformatted("Channel Indicator. See table below for details.");
1243
1244 ImGui::TableNextColumn(); ImGui::TextUnformatted("slot");
1245 ImGui::TableNextColumn(); ImGui::TextUnformatted("Slot Id");
1246
1247 ImGui::TableNextColumn(); ImGui::TextUnformatted("cno");
1248 ImGui::TableNextColumn(); ImGui::TextUnformatted("Carrier-to-noise density ratio (signal strength) [dB-Hz]");
1249
1250 ImGui::TableNextColumn(); ImGui::TextUnformatted("flags");
1251 ImGui::TableNextColumn(); ImGui::TextUnformatted("Tracking info flags. See table below for details.");
1252
1253 ImGui::TableNextColumn(); ImGui::TextUnformatted("pr");
1254 ImGui::TableNextColumn(); ImGui::TextUnformatted("Pseudorange measurement in meters.");
1255
1256 ImGui::TableNextColumn(); ImGui::TextUnformatted("cp");
1257 ImGui::TableNextColumn(); ImGui::TextUnformatted("Carrier phase measurement in cycles.");
1258
1259 ImGui::TableNextColumn(); ImGui::TextUnformatted("dp");
1260 ImGui::TableNextColumn(); ImGui::TextUnformatted("Doppler measurement in Hz. Positive sign for approaching satellites.");
1261
1262 ImGui::EndTable();
1263 }
1264 ImGui::BeginChild("VectorNavSatRawTooltipGNSSConstelationChild", ImVec2(180, 217));
1265 ImGui::TextUnformatted("\nConstellation indicator:");
1266 if (ImGui::BeginTable("VectorNavSatRawTooltipGNSSConstelation", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1267 {
1268 ImGui::TableSetupColumn("Value");
1269 ImGui::TableSetupColumn("Description");
1270 ImGui::TableHeadersRow();
1271
1272 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1273 ImGui::TableNextColumn(); ImGui::TextUnformatted("GPS");
1274
1275 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1276 ImGui::TableNextColumn(); ImGui::TextUnformatted("SBAS");
1277
1278 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1279 ImGui::TableNextColumn(); ImGui::TextUnformatted("Galileo");
1280
1281 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1282 ImGui::TableNextColumn(); ImGui::TextUnformatted("BeiDou");
1283
1284 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1285 ImGui::TableNextColumn(); ImGui::TextUnformatted("IMES");
1286
1287 ImGui::TableNextColumn(); ImGui::TextUnformatted("5");
1288 ImGui::TableNextColumn(); ImGui::TextUnformatted("QZSS");
1289
1290 ImGui::TableNextColumn(); ImGui::TextUnformatted("6");
1291 ImGui::TableNextColumn(); ImGui::TextUnformatted("GLONASS");
1292
1293 ImGui::EndTable();
1294 }
1295 ImGui::EndChild();
1296 ImGui::SameLine();
1297 ImGui::BeginChild("VectorNavSatRawTooltipFreqChild", ImVec2(270, 235));
1298 ImGui::TextUnformatted("\nFrequency indicator:");
1299 if (ImGui::BeginTable("VectorNavSatRawTooltipFreq", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1300 {
1301 ImGui::TableSetupColumn("Value");
1302 ImGui::TableSetupColumn("Description");
1303 ImGui::TableHeadersRow();
1304
1305 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1306 ImGui::TableNextColumn(); ImGui::TextUnformatted("Rx Channel");
1307
1308 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1309 ImGui::TableNextColumn(); ImGui::TextUnformatted("L1(GPS,QZSS,SBAS), G1(GLO),\nE2-L1-E1(GAL), B1(BDS)");
1310
1311 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1312 ImGui::TableNextColumn(); ImGui::TextUnformatted("L2(GPS,QZSS), G2(GLO)");
1313
1314 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1315 ImGui::TableNextColumn(); ImGui::TextUnformatted("L5(GPS,QZSS,SBAS), E5a(GAL)");
1316
1317 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1318 ImGui::TableNextColumn(); ImGui::TextUnformatted("E6(GAL), LEX(QZSS), B3(BDS)");
1319
1320 ImGui::TableNextColumn(); ImGui::TextUnformatted("5");
1321 ImGui::TableNextColumn(); ImGui::TextUnformatted("E5b(GAL), B2(BDS)");
1322
1323 ImGui::TableNextColumn(); ImGui::TextUnformatted("6");
1324 ImGui::TableNextColumn(); ImGui::TextUnformatted("E5a+b(GAL)");
1325
1326 ImGui::EndTable();
1327 }
1328 ImGui::EndChild();
1329 ImGui::SameLine();
1330 ImGui::BeginChild("VectorNavSatRawTooltipFlagChild", ImVec2(255, 260));
1331 ImGui::TextUnformatted("\nTracking info flags:");
1332 if (ImGui::BeginTable("VectorNavSatRawTooltipFlags", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1333 {
1334 ImGui::TableSetupColumn("Bit Offset");
1335 ImGui::TableSetupColumn("Description");
1336 ImGui::TableHeadersRow();
1337
1338 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1339 ImGui::TableNextColumn(); ImGui::TextUnformatted("Searching");
1340
1341 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1342 ImGui::TableNextColumn(); ImGui::TextUnformatted("Tracking");
1343
1344 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1345 ImGui::TableNextColumn(); ImGui::TextUnformatted("Time Valid");
1346
1347 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1348 ImGui::TableNextColumn(); ImGui::TextUnformatted("Code Lock");
1349
1350 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1351 ImGui::TableNextColumn(); ImGui::TextUnformatted("Phase Lock");
1352
1353 ImGui::TableNextColumn(); ImGui::TextUnformatted("5");
1354 ImGui::TableNextColumn(); ImGui::TextUnformatted("Phase Half Ambiguity");
1355
1356 ImGui::TableNextColumn(); ImGui::TextUnformatted("6");
1357 ImGui::TableNextColumn(); ImGui::TextUnformatted("Phase Half Sub");
1358
1359 ImGui::TableNextColumn(); ImGui::TextUnformatted("7");
1360 ImGui::TableNextColumn(); ImGui::TextUnformatted("Phase Slip");
1361
1362 ImGui::TableNextColumn(); ImGui::TextUnformatted("8");
1363 ImGui::TableNextColumn(); ImGui::TextUnformatted("Pseudorange Smoothed");
1364
1365 ImGui::EndTable();
1366 }
1367 ImGui::EndChild();
1368 ImGui::TextUnformatted("\nChannel indicator:");
1369 if (ImGui::BeginTable("VectorNavSatRawTooltipChan", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1370 {
1371 ImGui::TableSetupColumn("Value");
1372 ImGui::TableSetupColumn("Description");
1373 ImGui::TableHeadersRow();
1374
1375 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1376 ImGui::TableNextColumn(); ImGui::TextUnformatted("P-code (GPS,GLO)");
1377
1378 ImGui::TableNextColumn(); ImGui::TextUnformatted("1");
1379 ImGui::TableNextColumn(); ImGui::TextUnformatted("C/A-code (GPS,GLO,SBAS,QZSS), C chan (GAL)");
1380
1381 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1382 ImGui::TableNextColumn(); ImGui::TextUnformatted("semi-codeless (GPS)");
1383
1384 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1385 ImGui::TableNextColumn(); ImGui::TextUnformatted("Y-code (GPS)");
1386
1387 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1388 ImGui::TableNextColumn(); ImGui::TextUnformatted("M-code (GPS)");
1389
1390 ImGui::TableNextColumn(); ImGui::TextUnformatted("5");
1391 ImGui::TableNextColumn(); ImGui::TextUnformatted("codeless (GPS)");
1392
1393 ImGui::TableNextColumn(); ImGui::TextUnformatted("6");
1394 ImGui::TableNextColumn(); ImGui::TextUnformatted("A chan (GAL)");
1395
1396 ImGui::TableNextColumn(); ImGui::TextUnformatted("7");
1397 ImGui::TableNextColumn(); ImGui::TextUnformatted("B chan (GAL)");
1398
1399 ImGui::TableNextColumn(); ImGui::TextUnformatted("8");
1400 ImGui::TableNextColumn(); ImGui::TextUnformatted("I chan (GPS,GAL,QZSS,BDS)");
1401
1402 ImGui::TableNextColumn(); ImGui::TextUnformatted("9");
1403 ImGui::TableNextColumn(); ImGui::TextUnformatted("Q chan (GPS,GAL,QZSS,BDS)");
1404
1405 ImGui::TableNextColumn(); ImGui::TextUnformatted("10");
1406 ImGui::TableNextColumn(); ImGui::TextUnformatted("M chan (L2CGPS, L2CQZSS), D chan (GPS,QZSS)");
1407
1408 ImGui::TableNextColumn(); ImGui::TextUnformatted("11");
1409 ImGui::TableNextColumn(); ImGui::TextUnformatted("L chan (L2CGPS, L2CQZSS), P chan (GPS,QZSS)");
1410
1411 ImGui::TableNextColumn(); ImGui::TextUnformatted("12");
1412 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)");
1413
1414 ImGui::TableNextColumn(); ImGui::TextUnformatted("13");
1415 ImGui::TableNextColumn(); ImGui::TextUnformatted("based on Z-tracking (GPS)");
1416
1417 ImGui::TableNextColumn(); ImGui::TextUnformatted("14");
1418 ImGui::TableNextColumn(); ImGui::TextUnformatted("A+B+C (GAL)");
1419
1420 ImGui::EndTable();
1421 } },
1422 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1423 [](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); } },
1424 } };
1425
1426 const std::array<NAV::VectorNavSensor::BinaryGroupData, 9> NAV::VectorNavSensor::_binaryGroupAttitude{ {
1427 { /* 0 */ "VpeStatus", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_VPESTATUS,
1428 []() { ImGui::TextUnformatted("VPE Status bitfield\n\n");
1429 if (ImGui::BeginTable("VectorNavSatRawTooltipChan", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
1430 {
1431 ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
1432 ImGui::TableSetupColumn("Bit Offset", ImGuiTableColumnFlags_WidthFixed);
1433 ImGui::TableSetupColumn("Format", ImGuiTableColumnFlags_WidthFixed);
1434 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthFixed);
1435 ImGui::TableHeadersRow();
1436
1437 ImGui::TableNextColumn(); ImGui::TextUnformatted("AttitudeQuality");
1438 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1439 ImGui::TableNextColumn(); ImGui::TextUnformatted("2 bits");
1440 ImGui::TableNextColumn(); ImGui::TextUnformatted("Provides an indication of the quality of the attitude solution.\n0 - Excellent\n1 - Good\n2 - Bad\n3 - Not tracking");
1441
1442 ImGui::TableNextColumn(); ImGui::TextUnformatted("GyroSaturation");
1443 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1444 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1445 ImGui::TableNextColumn(); ImGui::TextUnformatted("At least one gyro axis is currently saturated.");
1446
1447 ImGui::TableNextColumn(); ImGui::TextUnformatted("GyroSaturationRecovery");
1448 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1449 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1450 ImGui::TableNextColumn(); ImGui::TextUnformatted("Filter is in the process of recovering from a gyro\nsaturation event.");
1451
1452 ImGui::TableNextColumn(); ImGui::TextUnformatted("MagDisturbance");
1453 ImGui::TableNextColumn(); ImGui::TextUnformatted("4");
1454 ImGui::TableNextColumn(); ImGui::TextUnformatted("2 bits");
1455 ImGui::TableNextColumn(); ImGui::TextUnformatted("A magnetic DC disturbance has been detected.\n0 - No magnetic disturbance\n1 to 3 - Magnetic disturbance is present.");
1456
1457 ImGui::TableNextColumn(); ImGui::TextUnformatted("MagSaturation");
1458 ImGui::TableNextColumn(); ImGui::TextUnformatted("6");
1459 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1460 ImGui::TableNextColumn(); ImGui::TextUnformatted("At least one magnetometer axis is currently saturated.");
1461
1462 ImGui::TableNextColumn(); ImGui::TextUnformatted("AccDisturbance");
1463 ImGui::TableNextColumn(); ImGui::TextUnformatted("7");
1464 ImGui::TableNextColumn(); ImGui::TextUnformatted("2 bits");
1465 ImGui::TableNextColumn(); ImGui::TextUnformatted("A strong acceleration disturbance has been detected.\n0 - No acceleration disturbance.\n1 to 3 - Acceleration disturbance has been detected.");
1466
1467 ImGui::TableNextColumn(); ImGui::TextUnformatted("AccSaturation");
1468 ImGui::TableNextColumn(); ImGui::TextUnformatted("9");
1469 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1470 ImGui::TableNextColumn(); ImGui::TextUnformatted("At least one accelerometer axis is currently saturated.");
1471
1472 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved");
1473 ImGui::TableNextColumn(); ImGui::TextUnformatted("10");
1474 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1475 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved for internal use. May change state at run- time.");
1476
1477 ImGui::TableNextColumn(); ImGui::TextUnformatted("KnownMagDisturbance");
1478 ImGui::TableNextColumn(); ImGui::TextUnformatted("11");
1479 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1480 ImGui::TableNextColumn(); ImGui::TextUnformatted("A known magnetic disturbance has been reported by\nthe user and the magnetometer is currently tuned out.");
1481
1482 ImGui::TableNextColumn(); ImGui::TextUnformatted("KnownAccelDisturbance");
1483 ImGui::TableNextColumn(); ImGui::TextUnformatted("12");
1484 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1485 ImGui::TableNextColumn(); ImGui::TextUnformatted("A known acceleration disturbance has been reported by\nthe user and the accelerometer is currently tuned out.");
1486
1487 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved");
1488 ImGui::TableNextColumn(); ImGui::TextUnformatted("13");
1489 ImGui::TableNextColumn(); ImGui::TextUnformatted("3 bits");
1490 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved for future use.");
1491
1492 ImGui::EndTable();
1493 } },
1494 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN100_VN110; } },
1495 { /* 1 */ "YawPitchRoll", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YAWPITCHROLL,
1496 []() { 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°]"); },
1497 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1498 [](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)
1499 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1500 { /* 2 */ "Quaternion", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_QUATERNION,
1501 []() { 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."); },
1502 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1503 [](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)
1504 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1505 { /* 3 */ "DCM", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_DCM,
1506 []() { 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."); },
1507 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1508 [](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)
1509 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1510 { /* 4 */ "MagNed", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_MAGNED,
1511 []() { 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."); },
1512 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1513 [](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)
1514 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1515 { /* 5 */ "AccelNed", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_ACCELNED,
1516 []() { 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)."); },
1517 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1518 [](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)
1519 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1520 { /* 6 */ "LinearAccelBody", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELBODY,
1521 []() { 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."); },
1522 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1523 [](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)
1524 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1525 { /* 7 */ "LinearAccelNed", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELNED,
1526 []() { 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."); },
1527 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1528 [](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)
1529 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1530 { /* 8 */ "YprU", vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YPRU,
1531 []() { 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."); },
1532 [](VectorNavModel /* sensorModel */, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return true; },
1533 [](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)
1534 && ((sensor->_sensorModel == VectorNavModel::VN310) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK) && (bor.timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)); } },
1535 } };
1536
1537 const std::array<NAV::VectorNavSensor::BinaryGroupData, 11> NAV::VectorNavSensor::_binaryGroupINS{ {
1538 { /* 0 */ "InsStatus", vn::protocol::uart::InsGroup::INSGROUP_INSSTATUS,
1539 []() { ImGui::TextUnformatted("Ins Status bitfield:");
1540 if (ImGui::BeginTable("VectorNavInsStatusTooltip", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
1541 {
1542 ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
1543 ImGui::TableSetupColumn("Bit Offset", ImGuiTableColumnFlags_WidthFixed);
1544 ImGui::TableSetupColumn("Format", ImGuiTableColumnFlags_WidthFixed);
1545 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthFixed);
1546 ImGui::TableHeadersRow();
1547
1548 ImGui::TableNextColumn(); ImGui::TextUnformatted("Mode");
1549 ImGui::TableNextColumn(); ImGui::TextUnformatted("0");
1550 ImGui::TableNextColumn(); ImGui::TextUnformatted("2 bits");
1551 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.");
1552
1553 ImGui::TableNextColumn(); ImGui::TextUnformatted("GpsFix");
1554 ImGui::TableNextColumn(); ImGui::TextUnformatted("2");
1555 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1556 ImGui::TableNextColumn(); ImGui::TextUnformatted("Indicates whether the GNSS has a proper fix.");
1557
1558 ImGui::TableNextColumn(); ImGui::TextUnformatted("Error");
1559 ImGui::TableNextColumn(); ImGui::TextUnformatted("3");
1560 ImGui::TableNextColumn(); ImGui::TextUnformatted("4 bits");
1561 ImGui::TableNextColumn(); ImGui::TextUnformatted("Sensor measurement error code. See table below.\n0 = No errors detected.");
1562
1563 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved");
1564 ImGui::TableNextColumn(); ImGui::TextUnformatted("7");
1565 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1566 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved for internal use. May toggle state during runtime and should be ignored.");
1567
1568 ImGui::TableNextColumn(); ImGui::TextUnformatted("GpsHeadingIns");
1569 ImGui::TableNextColumn(); ImGui::TextUnformatted("8");
1570 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1571 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.");
1572
1573 ImGui::TableNextColumn(); ImGui::TextUnformatted("GpsCompass");
1574 ImGui::TableNextColumn(); ImGui::TextUnformatted("9");
1575 ImGui::TableNextColumn(); ImGui::TextUnformatted("1 bits");
1576 ImGui::TableNextColumn(); ImGui::TextUnformatted("Indicates if the GNSS compass is operational and reporting a heading\nsolution.");
1577
1578 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved");
1579 ImGui::TableNextColumn(); ImGui::TextUnformatted("10");
1580 ImGui::TableNextColumn(); ImGui::TextUnformatted("6 bits");
1581 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved for internal use. These bits will toggle state and should be ignored.");
1582
1583 ImGui::EndTable();
1584 }
1585 ImGui::TextUnformatted("\nError Bitfield:");
1586 if (ImGui::BeginTable("VectorNavInsStatusTooltipError", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
1587 {
1588 ImGui::TableSetupColumn("Name");
1589 ImGui::TableSetupColumn("Description");
1590 ImGui::TableHeadersRow();
1591
1592 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved");
1593 ImGui::TableNextColumn(); ImGui::TextUnformatted("Reserved for future use and not currently used.");
1594
1595 ImGui::TableNextColumn(); ImGui::TextUnformatted("IMU Error");
1596 ImGui::TableNextColumn(); ImGui::TextUnformatted("High if IMU communication error is detected.");
1597
1598 ImGui::TableNextColumn(); ImGui::TextUnformatted("Mag/Pres Error");
1599 ImGui::TableNextColumn(); ImGui::TextUnformatted("High if Magnetometer or Pressure sensor error is detected.");
1600
1601 ImGui::TableNextColumn(); ImGui::TextUnformatted("GNSS Error");
1602 ImGui::TableNextColumn(); ImGui::TextUnformatted("High if GNSS communication error is detected.");
1603
1604 ImGui::EndTable();
1605 } },
1606 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1607 [](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); } },
1608 { /* 1 */ "PosLla", vn::protocol::uart::InsGroup::INSGROUP_POSLLA,
1609 []() { ImGui::TextUnformatted("Ins Position (latitude, longitude, altitude)\n\nThe estimated position given as latitude, longitude, and altitude given in [deg, deg, m] respectively."); },
1610 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1611 [](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); } },
1612 { /* 2 */ "PosEcef", vn::protocol::uart::InsGroup::INSGROUP_POSECEF,
1613 []() { ImGui::TextUnformatted("Ins Position (ECEF)\n\nThe estimated position given in the Earth centered Earth fixed (ECEF) frame, reported in meters."); },
1614 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1615 [](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); } },
1616 { /* 3 */ "VelBody", vn::protocol::uart::InsGroup::INSGROUP_VELBODY,
1617 []() { ImGui::TextUnformatted("Ins Velocity (Body)\n\nThe estimated velocity in the body frame, given in m/s."); },
1618 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1619 [](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); } },
1620 { /* 4 */ "VelNed", vn::protocol::uart::InsGroup::INSGROUP_VELNED,
1621 []() { ImGui::TextUnformatted("Ins Velocity (NED)\n\nThe estimated velocity in the North East Down (NED) frame, given in m/s."); },
1622 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1623 [](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); } },
1624 { /* 5 */ "VelEcef", vn::protocol::uart::InsGroup::INSGROUP_VELECEF,
1625 []() { ImGui::TextUnformatted("Ins Velocity (ECEF)\n\nThe estimated velocity in the Earth centered Earth fixed (ECEF) frame, given in m/s."); },
1626 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1627 [](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); } },
1628 { /* 6 */ "MagEcef", vn::protocol::uart::InsGroup::INSGROUP_MAGECEF,
1629 []() { ImGui::TextUnformatted("Compensated magnetic (ECEF)\n\nThe compensated magnetic measurement in the Earth centered Earth fixed (ECEF) frame, given in Gauss."); },
1630 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1631 [](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); } },
1632 { /* 7 */ "AccelEcef", vn::protocol::uart::InsGroup::INSGROUP_ACCELECEF,
1633 []() { 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)."); },
1634 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1635 [](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); } },
1636 { /* 8 */ "LinearAccelEcef", vn::protocol::uart::InsGroup::INSGROUP_LINEARACCELECEF,
1637 []() { 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."); },
1638 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1639 [](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); } },
1640 { /* 9 */ "PosU", vn::protocol::uart::InsGroup::INSGROUP_POSU,
1641 []() { ImGui::TextUnformatted("Ins Position Uncertainty\n\nThe estimated uncertainty (1 Sigma) in the current position estimate, given in meters."); },
1642 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1643 [](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); } },
1644 { /* 10 */ "VelU", vn::protocol::uart::InsGroup::INSGROUP_VELU,
1645 []() { ImGui::TextUnformatted("Ins Velocity Uncertainty\n\nThe estimated uncertainty (1 Sigma) in the current velocity estimate, given in m/s."); },
1646 [](VectorNavModel sensorModel, const vn::sensors::BinaryOutputRegister& /* bor */, uint32_t /* binaryField */) { return sensorModel == VectorNavModel::VN310; },
1647 [](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); } },
1648 } };
1649
1650 // NOLINTEND
1651
1652 112 NAV::VectorNavSensor::VectorNavSensor()
1653
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())
1654 {
1655 LOG_TRACE("{}: called", name);
1656
1657 112 _onlyRealTime = true;
1658 112 _hasConfig = true;
1659 112 _guiConfigDefaultWindowSize = { 954, 783 };
1660
1661
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() });
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, "Binary Output 1", Pin::Type::Flow, { NAV::VectorNavBinaryOutput::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 2", 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 3", Pin::Type::Flow, { NAV::VectorNavBinaryOutput::type() });
1665
1666 _dividerFrequency = []() {
1667 112 std::map<int, int, std::greater<>> divFreq;
1668
2/2
✓ Branch 0 taken 89600 times.
✓ Branch 1 taken 112 times.
89712 for (int freq = 1; freq <= IMU_DEFAULT_FREQUENCY; freq++)
1669 {
1670 89600 int divider = static_cast<int>(std::round(IMU_DEFAULT_FREQUENCY / freq));
1671
1672
1/2
✓ Branch 1 taken 89600 times.
✗ Branch 2 not taken.
89600 if (!divFreq.contains(divider)
1673
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)))
1674 {
1675
1/2
✓ Branch 1 taken 57568 times.
✗ Branch 2 not taken.
57568 divFreq[divider] = freq;
1676 }
1677 }
1678 112 std::vector<uint16_t> divs;
1679 112 std::vector<std::string> freqs;
1680
2/2
✓ Branch 6 taken 6272 times.
✓ Branch 7 taken 112 times.
6384 for (auto& [divider, freq] : divFreq)
1681 {
1682
1/2
✓ Branch 1 taken 6272 times.
✗ Branch 2 not taken.
6272 divs.push_back(static_cast<uint16_t>(divider));
1683
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");
1684 LOG_DATA("VectorNavSensor: RateDivisor {} = {}", divs.back(), freqs.back());
1685 }
1686
1/2
✓ Branch 1 taken 112 times.
✗ Branch 2 not taken.
224 return std::make_pair(divs, freqs);
1687
1/2
✓ Branch 1 taken 112 times.
✗ Branch 2 not taken.
224 }();
1688 560 }
1689
1690 224 NAV::VectorNavSensor::~VectorNavSensor()
1691 {
1692 LOG_TRACE("{}: called", nameId());
1693 224 }
1694
1695 224 std::string NAV::VectorNavSensor::typeStatic()
1696 {
1697
1/2
✓ Branch 1 taken 224 times.
✗ Branch 2 not taken.
448 return "VectorNavSensor";
1698 }
1699
1700 std::string NAV::VectorNavSensor::type() const
1701 {
1702 return typeStatic();
1703 }
1704
1705 112 std::string NAV::VectorNavSensor::category()
1706 {
1707
1/2
✓ Branch 1 taken 112 times.
✗ Branch 2 not taken.
224 return "Data Provider";
1708 }
1709
1710 void NAV::VectorNavSensor::guiConfig()
1711 {
1712 if (auto sensorModel = static_cast<int>(_sensorModel);
1713 ImGui::Combo("Sensor", &sensorModel, "VN-100 / VN-110\0VN-310\0\0"))
1714 {
1715 _sensorModel = static_cast<decltype(_sensorModel)>(sensorModel);
1716 LOG_DEBUG("{}: Sensor changed to {}", nameId(), fmt::underlying(_sensorModel));
1717 flow::ApplyChanges();
1718 doDeinitialize();
1719
1720 if (_sensorModel != VectorNavModel::VN310
1721 && (_asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNGPS
1722 || _asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNGPE
1723 || _asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNINS
1724 || _asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNINE
1725 || _asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNISL
1726 || _asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNISE
1727 || _asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNG2S
1728 || _asyncDataOutputType == vn::protocol::uart::AsciiAsync::VNG2E))
1729 {
1730 _asyncDataOutputType = vn::protocol::uart::AsciiAsync::VNOFF;
1731 }
1732
1733 if (_sensorModel == VectorNavModel::VN310)
1734 {
1735 _communicationProtocolControlRegister.spiCount = vn::protocol::uart::CountMode::COUNTMODE_NONE;
1736 _communicationProtocolControlRegister.spiStatus = vn::protocol::uart::StatusMode::STATUSMODE_OFF;
1737 _communicationProtocolControlRegister.spiChecksum = vn::protocol::uart::ChecksumMode::CHECKSUMMODE_OFF;
1738 }
1739
1740 for (auto& binaryOutput : _binaryOutputRegister)
1741 {
1742 // for (const auto& item : _binaryGroupCommon)
1743 // {
1744 // if (!item.isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.commonField)))
1745 // {
1746 // binaryOutput.commonField &= ~vn::protocol::uart::CommonGroup(item.flagsValue);
1747 // }
1748 // }
1749 for (size_t i = 0; i < _binaryGroupTime.size(); i++)
1750 {
1751 const auto& item = _binaryGroupTime.at(i);
1752
1753 if (i == 0 /* TimeStartup */ || i == 9 /* TimeStatus */) { continue; } // Do not reset the field
1754
1755 // Check the fields if conditions are met
1756 if ((i == 2 /* GpsTow */ || i == 3 /* GpsWeek */)
1757 && _sensorModel == VectorNavModel::VN310
1758 && binaryOutput.timeField & vn::protocol::uart::TIMEGROUP_TIMESTARTUP
1759 && !_binaryGroupTime.at(0).isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.timeField)))
1760 {
1761 binaryOutput.timeField |= vn::protocol::uart::TimeGroup(item.flagsValue);
1762 continue;
1763 }
1764
1765 if (!item.isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.timeField)))
1766 {
1767 binaryOutput.timeField &= ~vn::protocol::uart::TimeGroup(item.flagsValue);
1768 }
1769 }
1770 for (const auto& item : _binaryGroupIMU)
1771 {
1772 if (!item.isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.imuField)))
1773 {
1774 binaryOutput.imuField &= ~vn::protocol::uart::ImuGroup(item.flagsValue);
1775 }
1776 }
1777 for (const auto& item : _binaryGroupGNSS)
1778 {
1779 if (!item.isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.gpsField)))
1780 {
1781 binaryOutput.gpsField &= ~vn::protocol::uart::GpsGroup(item.flagsValue);
1782 }
1783 if (!item.isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.gps2Field)))
1784 {
1785 binaryOutput.gps2Field &= ~vn::protocol::uart::GpsGroup(item.flagsValue);
1786 }
1787 }
1788 for (const auto& item : _binaryGroupAttitude)
1789 {
1790 if (!item.isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.attitudeField)))
1791 {
1792 binaryOutput.attitudeField &= ~vn::protocol::uart::AttitudeGroup(item.flagsValue);
1793 }
1794 }
1795 for (const auto& item : _binaryGroupINS)
1796 {
1797 if (!item.isEnabled(_sensorModel, binaryOutput, static_cast<uint32_t>(binaryOutput.insField)))
1798 {
1799 binaryOutput.insField &= ~vn::protocol::uart::InsGroup(item.flagsValue);
1800 }
1801 }
1802 }
1803 }
1804
1805 if (ImGui::InputTextWithHint("SensorPort", _connectedSensorPort.empty() ? "/dev/ttyUSB0" : _connectedSensorPort.c_str(), &_sensorPort))
1806 {
1807 LOG_DEBUG("{}: SensorPort changed to {}", nameId(), _sensorPort);
1808 flow::ApplyChanges();
1809 if (isInitialized())
1810 {
1811 doDeinitialize();
1812 }
1813 }
1814 ImGui::SameLine();
1815 gui::widgets::HelpMarker("COM port where the sensor is attached to\n"
1816 "- \"COM1\" (Windows format for physical and virtual (USB) serial port)\n"
1817 "- \"/dev/ttyS1\" (Linux format for physical serial port)\n"
1818 "- \"/dev/ttyUSB0\" (Linux format for virtual (USB) serial port)\n"
1819 "- \"/dev/tty.usbserial-FTXXXXXX\" (Mac OS X format for virtual (USB) serial port)\n"
1820 "- \"/dev/ttyS0\" (CYGWIN format. Usually the Windows COM port number minus 1. This would connect to COM1)");
1821
1822 bool isNodeInitialized = isInitialized();
1823 if (!isNodeInitialized)
1824 {
1825 ImGui::BeginDisabled();
1826 }
1827 if (ImGui::Button("Write settings"))
1828 {
1829 try
1830 {
1831 _vs.writeSettings();
1832 }
1833 catch (const std::exception& e)
1834 {
1835 LOG_ERROR("{}: Write settings threw an exception: {}", nameId(), e.what());
1836 }
1837 }
1838 if (!isNodeInitialized)
1839 {
1840 ImGui::EndDisabled();
1841 }
1842 if (ImGui::IsItemHovered())
1843 {
1844 ImGui::SetTooltip("This command will write the current register settings into non-volatile memory. Once the settings are stored\n"
1845 "in non-volatile (Flash) memory, the VN-310E module can be power cycled or reset, and the register will be\n"
1846 "reloaded from non-volatile memory.\n\n"
1847 "Due to limitations in the flash write speed the write settings command takes ~ 500ms to\n"
1848 "complete. Any commands that are sent to the sensor during this time will be responded to after\n"
1849 "the operation is complete.\n\n"
1850 "The sensor must be stationary when issuing a Write Settings Command otherwise a Reset\n"
1851 "command must also be issued to prevent the Kalman Filter from diverging during the write\n"
1852 "settings process.\n\n"
1853 "A write settings command is automatically send after initializing the node.");
1854 }
1855 ImGui::SameLine();
1856 if (!isNodeInitialized)
1857 {
1858 ImGui::BeginDisabled();
1859 }
1860 if (ImGui::Button("Restore factory settings"))
1861 {
1862 try
1863 {
1864 _vs.restoreFactorySettings();
1865 }
1866 catch (const std::exception& e)
1867 {
1868 LOG_ERROR("{}: Restore factory settings threw an exception: {}", nameId(), e.what());
1869 }
1870 }
1871 if (!isNodeInitialized)
1872 {
1873 ImGui::EndDisabled();
1874 }
1875 if (ImGui::IsItemHovered())
1876 {
1877 ImGui::SetTooltip("This command will restore the VN-310E module's factory default settings and will reset the module. There\n"
1878 "are no parameters for this command. The module will respond to this command before restoring the factory\n"
1879 "settings.");
1880 }
1881 ImGui::SameLine();
1882 if (!isNodeInitialized)
1883 {
1884 ImGui::BeginDisabled();
1885 }
1886 if (ImGui::Button("Reset sensor"))
1887 {
1888 try
1889 {
1890 _vs.reset();
1891 }
1892 catch (const std::exception& e)
1893 {
1894 LOG_ERROR("{}: Resetting threw an exception: {}", nameId(), e.what());
1895 }
1896 }
1897 if (!isNodeInitialized)
1898 {
1899 ImGui::EndDisabled();
1900 }
1901 if (ImGui::IsItemHovered())
1902 {
1903 ImGui::SetTooltip("This command will reset the module. There are no parameters required for this command. The module will\n"
1904 "first respond to the command and will then perform a reset. Upon a reset all registers will be reloaded with\n"
1905 "the values saved in non-volatile memory. If no values are stored in non-volatile memory, the device will default\n"
1906 "to factory settings. Also upon reset the VN-310E will re-initialize its Kalman filter, thus the filter will take a\n"
1907 "few seconds to completely converge on the correct attitude and correct for gyro bias.");
1908 }
1909
1910 // ###########################################################################################################
1911 // SYSTEM MODULE
1912 // ###########################################################################################################
1913
1914 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
1915 if (ImGui::CollapsingHeader(fmt::format("System Module##{}", size_t(id)).c_str()))
1916 {
1917 // ------------------------------------------- Serial Baud Rate ----------------------------------------------
1918
1919 std::array<const char*, 10> items = { "Fastest", "9600", "19200", "38400", "57600", "115200", "128000", "230400", "460800", "921600" };
1920 if (ImGui::Combo("Baudrate", &_selectedBaudrate, items.data(), items.size()))
1921 {
1922 LOG_DEBUG("{}: Baudrate changed to {}", nameId(), sensorBaudrate());
1923 flow::ApplyChanges();
1924 doDeinitialize();
1925 }
1926
1927 if (ImGui::TreeNode(fmt::format("Async Ascii Output##{}", size_t(id)).c_str()))
1928 {
1929 std::vector<std::pair<vn::protocol::uart::AsciiAsync, const char*>> asciiAsyncItems{
1930 { vn::protocol::uart::AsciiAsync::VNOFF, "Asynchronous output turned off" },
1931 { vn::protocol::uart::AsciiAsync::VNYPR, "Yaw, Pitch, Roll" },
1932 { vn::protocol::uart::AsciiAsync::VNQTN, "Quaternion" },
1933 { vn::protocol::uart::AsciiAsync::VNQMR, "Quaternion, Magnetic, Acceleration and Angular Rates" },
1934 { vn::protocol::uart::AsciiAsync::VNMAG, "Magnetic Measurements" },
1935 { vn::protocol::uart::AsciiAsync::VNACC, "Acceleration Measurements" },
1936 { vn::protocol::uart::AsciiAsync::VNGYR, "Angular Rate Measurements" },
1937 { vn::protocol::uart::AsciiAsync::VNMAR, "Magnetic, Acceleration, and Angular Rate Measurements" },
1938 { vn::protocol::uart::AsciiAsync::VNYMR, "Yaw, Pitch, Roll, Magnetic, Acceleration, and Angular Rate Measurements" },
1939 { vn::protocol::uart::AsciiAsync::VNYBA, "Yaw, Pitch, Roll, Body True Acceleration, and Angular Rates" },
1940 { vn::protocol::uart::AsciiAsync::VNYIA, "Yaw, Pitch, Roll, Inertial True Acceleration, and Angular Rates" },
1941 { vn::protocol::uart::AsciiAsync::VNIMU, "IMU Measurements" }
1942 };
1943 if (_sensorModel == VectorNavModel::VN310)
1944 {
1945 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNGPS, "GNSS LLA");
1946 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNGPE, "GNSS ECEF");
1947 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNINS, "INS LLA");
1948 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNINE, "INS ECEF");
1949 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNISL, "INS LLA 2");
1950 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNISE, "INS ECEF 2");
1951 }
1952 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNDTV, "Delta theta and delta velocity");
1953 if (_sensorModel == VectorNavModel::VN310)
1954 {
1955 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNG2S, "GNSS2 LLA");
1956 asciiAsyncItems.emplace_back(vn::protocol::uart::AsciiAsync::VNG2E, "GNSS2 ECEF");
1957 }
1958
1959 if (ImGui::BeginCombo(fmt::format("Async Ascii Output Type##{}", size_t(id)).c_str(), vn::protocol::uart::str(_asyncDataOutputType).c_str()))
1960 {
1961 for (const auto& asciiAsyncItem : asciiAsyncItems)
1962 {
1963 const bool isSelected = (_asyncDataOutputType == asciiAsyncItem.first);
1964 if (ImGui::Selectable(vn::protocol::uart::str(asciiAsyncItem.first).c_str(), isSelected))
1965 {
1966 _asyncDataOutputType = asciiAsyncItem.first;
1967 LOG_DEBUG("{}: _asyncDataOutputType changed to {}", nameId(), vn::protocol::uart::str(_asyncDataOutputType));
1968 flow::ApplyChanges();
1969 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
1970 {
1971 try
1972 {
1973 _vs.writeAsyncDataOutputType(_asyncDataOutputType);
1974 }
1975 catch (const std::exception& e)
1976 {
1977 LOG_ERROR("{}: Could not configure the asyncDataOutputType: {}", nameId(), e.what());
1978 doDeinitialize();
1979 }
1980 }
1981 else
1982 {
1983 doDeinitialize();
1984 }
1985 }
1986 if (ImGui::IsItemHovered())
1987 {
1988 ImGui::BeginTooltip();
1989 ImGui::TextUnformatted(asciiAsyncItem.second);
1990 ImGui::EndTooltip();
1991 }
1992
1993 // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
1994 if (isSelected)
1995 {
1996 ImGui::SetItemDefaultFocus();
1997 }
1998 }
1999 ImGui::EndCombo();
2000 }
2001 ImGui::SameLine();
2002 gui::widgets::HelpMarker("This register controls the type of data that will be asynchronously outputted by the module. With this "
2003 "register, the user can specify which data register will be automatically outputted when it gets updated "
2004 "with a new reading. The table below lists which registers can be set to asynchronously output, the value "
2005 "to specify which register to output, and the header of the asynchronous data packet. Asynchronous data "
2006 "output can be disabled by setting this register to zero. The asynchronous data output will be sent out "
2007 "automatically at a frequency specified by the Async Data Output Frequency Register.");
2008
2009 if (ImGui::SliderInt(fmt::format("Async Ascii Output Frequency##{}", size_t(id)).c_str(),
2010 &_asyncDataOutputFrequencySelected,
2011 0, _possibleAsyncDataOutputFrequency.size() - 1,
2012 fmt::format("{} Hz", _possibleAsyncDataOutputFrequency.at(static_cast<size_t>(_asyncDataOutputFrequencySelected))).c_str()))
2013 {
2014 _asyncDataOutputFrequency = static_cast<uint32_t>(_possibleAsyncDataOutputFrequency.at(static_cast<size_t>(_asyncDataOutputFrequencySelected)));
2015 LOG_DEBUG("{}: asyncDataOutputFrequency changed to {} Hz", nameId(), _asyncDataOutputFrequency);
2016 flow::ApplyChanges();
2017 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2018 {
2019 try
2020 {
2021 _vs.writeAsyncDataOutputFrequency(_asyncDataOutputFrequency);
2022 }
2023 catch (const std::exception& e)
2024 {
2025 LOG_ERROR("{}: Could not configure the asyncDataOutputFrequency: {}", nameId(), e.what());
2026 doDeinitialize();
2027 }
2028 }
2029 else
2030 {
2031 doDeinitialize();
2032 }
2033 }
2034 ImGui::SameLine();
2035 gui::widgets::HelpMarker("Asynchronous data output frequency.\nThe ADOF will be changed for the active serial port.");
2036
2037 if (ImGui::DragInt(fmt::format("Async Ascii Output buffer size##{}", size_t(id)).c_str(), &_asciiOutputBufferSize, 1.0F, 0, INT32_MAX / 2))
2038 {
2039 _asciiOutputBuffer.resize(static_cast<size_t>(_asciiOutputBufferSize));
2040 LOG_DEBUG("{}: asciiOutputBufferSize changed to {}", nameId(), _asciiOutputBufferSize);
2041 flow::ApplyChanges();
2042 }
2043
2044 std::string messages;
2045 for (const auto& msg : _asciiOutputBuffer)
2046 {
2047 messages.append(msg);
2048 }
2049 ImGui::TextUnformatted("Async Ascii Messages:");
2050 ImGui::BeginChild(fmt::format("##Ascii Mesages {}", size_t(id)).c_str(), ImVec2(0, 300), true);
2051 ImGui::PushTextWrapPos();
2052 ImGui::TextUnformatted(messages.c_str());
2053 ImGui::PopTextWrapPos();
2054 ImGui::EndChild();
2055
2056 ImGui::TreePop();
2057 }
2058
2059 if (ImGui::TreeNode(fmt::format("Synchronization Control##{}", size_t(id)).c_str()))
2060 {
2061 ImGui::TextUnformatted("Contains parameters which allow the timing of the VN-310E to be\n"
2062 "synchronized with external devices.");
2063
2064 if (ImGui::Checkbox(fmt::format("Show SyncIn Pin##{}", size_t(id)).c_str(), &_syncInPin))
2065 {
2066 LOG_DEBUG("{}: syncInPin changed to {}", nameId(), _syncInPin);
2067 if (_syncInPin)
2068 {
2069 _synchronizationControlRegister.syncInMode = vn::protocol::uart::SyncInMode::SYNCINMODE_COUNT;
2070 LOG_DEBUG("{}: synchronizationControlRegister.syncInMode changed to {}", nameId(), vn::protocol::uart::str(_synchronizationControlRegister.syncInMode));
2071 }
2072 flow::ApplyChanges();
2073 if (_syncInPin && inputPins.empty())
2074 {
2075 nm::CreateInputPin(this, "SyncIn", Pin::Type::Object, { "TimeSync" });
2076 }
2077 else if (!_syncInPin && !inputPins.empty())
2078 {
2079 nm::DeleteInputPin(inputPins.front());
2080 }
2081 }
2082
2083 static constexpr std::array<std::pair<vn::protocol::uart::SyncInMode, const char*>, 4> synchronizationControlSyncInModes = {
2084 { { vn::protocol::uart::SyncInMode::SYNCINMODE_COUNT, "Count number of trigger events on SYNC_IN" },
2085 { vn::protocol::uart::SyncInMode::SYNCINMODE_IMU, "Start IMU sampling on trigger of SYNC_IN" },
2086 { vn::protocol::uart::SyncInMode::SYNCINMODE_ASYNC, "Output asynchronous message on trigger of SYNC_IN" },
2087 { 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"
2088 "In ASYNC3 mode messages configured with an output rate = 0 are output each time the appropriate\n"
2089 "transistion edge of the SyncIn pin is captured according to the edge settings in the SyncInEdge field.\n"
2090 "Messages configured with output rate > 0 are not affected by the SyncIn pulse. This applies to the ASCII\n"
2091 "Async message set by the Async Data Output Register, the user configurate binary output messages set\n"
2092 "by the Binary Output Registers, as well as the NMEA messages configured by the NMEA Output Registers." } }
2093 };
2094 if (_syncInPin)
2095 {
2096 ImGui::BeginDisabled();
2097 }
2098
2099 if (ImGui::BeginCombo(fmt::format("SyncIn Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_synchronizationControlRegister.syncInMode).c_str()))
2100 {
2101 for (const auto& synchronizationControlSyncInMode : synchronizationControlSyncInModes)
2102 {
2103 const bool isSelected = (_synchronizationControlRegister.syncInMode == synchronizationControlSyncInMode.first);
2104 if (ImGui::Selectable(vn::protocol::uart::str(synchronizationControlSyncInMode.first).c_str(), isSelected))
2105 {
2106 _synchronizationControlRegister.syncInMode = synchronizationControlSyncInMode.first;
2107 LOG_DEBUG("{}: synchronizationControlRegister.syncInMode changed to {}", nameId(), vn::protocol::uart::str(_synchronizationControlRegister.syncInMode));
2108 flow::ApplyChanges();
2109 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2110 {
2111 try
2112 {
2113 _vs.writeSynchronizationControl(_synchronizationControlRegister);
2114 }
2115 catch (const std::exception& e)
2116 {
2117 LOG_ERROR("{}: Could not configure the synchronizationControlRegister: {}", nameId(), e.what());
2118 doDeinitialize();
2119 }
2120 }
2121 else
2122 {
2123 doDeinitialize();
2124 }
2125 }
2126 if (ImGui::IsItemHovered())
2127 {
2128 ImGui::BeginTooltip();
2129 ImGui::TextUnformatted(synchronizationControlSyncInMode.second);
2130 ImGui::EndTooltip();
2131 }
2132
2133 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2134 {
2135 ImGui::SetItemDefaultFocus();
2136 }
2137 }
2138 ImGui::EndCombo();
2139 }
2140 ImGui::SameLine();
2141 gui::widgets::HelpMarker("The SyncInMode register controls the behavior of the SyncIn event. If the mode is set to COUNT then the "
2142 "internal clock will be used to control the IMU sampling. If SyncInMode is set to IMU then the IMU sampling "
2143 "loop will run on a SyncIn event. The relationship between the SyncIn event and a SyncIn trigger is defined "
2144 "by the SyncInEdge and SyncInSkipFactor parameters. If set to ASYNC then the VN-100 will output "
2145 "asynchronous serial messages upon each trigger event.");
2146 if (_syncInPin)
2147 {
2148 ImGui::EndDisabled();
2149 }
2150
2151 static constexpr std::array<std::pair<vn::protocol::uart::SyncInEdge, const char*>, 2> synchronizationControlSyncInEdges = {
2152 { { vn::protocol::uart::SyncInEdge::SYNCINEDGE_RISING, "Trigger on rising edge" },
2153 { vn::protocol::uart::SyncInEdge::SYNCINEDGE_FALLING, "Trigger on falling edge" } }
2154 };
2155 if (ImGui::BeginCombo(fmt::format("SyncIn Edge##{}", size_t(id)).c_str(), vn::protocol::uart::str(_synchronizationControlRegister.syncInEdge).c_str()))
2156 {
2157 for (const auto& synchronizationControlSyncInEdge : synchronizationControlSyncInEdges)
2158 {
2159 const bool isSelected = (_synchronizationControlRegister.syncInEdge == synchronizationControlSyncInEdge.first);
2160 if (ImGui::Selectable(vn::protocol::uart::str(synchronizationControlSyncInEdge.first).c_str(), isSelected))
2161 {
2162 _synchronizationControlRegister.syncInEdge = synchronizationControlSyncInEdge.first;
2163 LOG_DEBUG("{}: synchronizationControlRegister.syncInEdge changed to {}", nameId(), vn::protocol::uart::str(_synchronizationControlRegister.syncInEdge));
2164 flow::ApplyChanges();
2165 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2166 {
2167 try
2168 {
2169 _vs.writeSynchronizationControl(_synchronizationControlRegister);
2170 }
2171 catch (const std::exception& e)
2172 {
2173 LOG_ERROR("{}: Could not configure the synchronizationControlRegister: {}", nameId(), e.what());
2174 doDeinitialize();
2175 }
2176 }
2177 else
2178 {
2179 doDeinitialize();
2180 }
2181 }
2182 if (ImGui::IsItemHovered())
2183 {
2184 ImGui::BeginTooltip();
2185 ImGui::TextUnformatted(synchronizationControlSyncInEdge.second);
2186 ImGui::EndTooltip();
2187 }
2188
2189 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2190 {
2191 ImGui::SetItemDefaultFocus();
2192 }
2193 }
2194 ImGui::EndCombo();
2195 }
2196 ImGui::SameLine();
2197 gui::widgets::HelpMarker("The SyncInEdge register controls the type of edge the signal is set to trigger on.\n"
2198 "The factory default state is to trigger on a rising edge.");
2199
2200 int syncInSkipFactor = _synchronizationControlRegister.syncInSkipFactor;
2201 if (ImGui::InputInt(fmt::format("SyncIn Skip Factor##{}", size_t(id)).c_str(), &syncInSkipFactor))
2202 {
2203 if (syncInSkipFactor < 0)
2204 {
2205 syncInSkipFactor = 0;
2206 }
2207 else if (syncInSkipFactor > std::numeric_limits<uint16_t>::max())
2208 {
2209 syncInSkipFactor = std::numeric_limits<uint16_t>::max();
2210 }
2211 _synchronizationControlRegister.syncInSkipFactor = static_cast<uint16_t>(syncInSkipFactor);
2212 LOG_DEBUG("{}: synchronizationControlRegister.syncInSkipFactor changed to {}", nameId(), _synchronizationControlRegister.syncInSkipFactor);
2213 flow::ApplyChanges();
2214 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2215 {
2216 try
2217 {
2218 _vs.writeSynchronizationControl(_synchronizationControlRegister);
2219 }
2220 catch (const std::exception& e)
2221 {
2222 LOG_ERROR("{}: Could not configure the synchronizationControlRegister: {}", nameId(), e.what());
2223 doDeinitialize();
2224 }
2225 }
2226 else
2227 {
2228 doDeinitialize();
2229 }
2230 }
2231 ImGui::SameLine();
2232 gui::widgets::HelpMarker("The SyncInSkipFactor defines how many times trigger edges defined by SyncInEdge should occur prior to "
2233 "triggering a SyncIn event. The action performed on a SyncIn event is determined by the SyncIn mode. As an "
2234 "example if the SyncInSkipFactor was set to 4 and a 1 kHz signal was attached to the SyncIn pin, then the "
2235 "SyncIn event would only occur at 200 Hz.");
2236
2237 static constexpr std::array<std::pair<vn::protocol::uart::SyncOutMode, const char*>, 5> synchronizationControlSyncOutModes = {
2238 { { vn::protocol::uart::SyncOutMode::SYNCOUTMODE_NONE, "None" },
2239 { vn::protocol::uart::SyncOutMode::SYNCOUTMODE_IMUSTART, "Trigger at start of IMU sampling" },
2240 { vn::protocol::uart::SyncOutMode::SYNCOUTMODE_IMUREADY, "Trigger when IMU measurements are available" },
2241 { vn::protocol::uart::SyncOutMode::SYNCOUTMODE_INS, "Trigger when attitude measurements are available" },
2242 { vn::protocol::uart::SyncOutMode::SYNCOUTMODE_GPSPPS, "Trigger on a GPS PPS event (1 Hz) when a 3D fix is valid." } }
2243 };
2244 if (ImGui::BeginCombo(fmt::format("SyncOut Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_synchronizationControlRegister.syncOutMode).c_str()))
2245 {
2246 for (const auto& synchronizationControlSyncOutMode : synchronizationControlSyncOutModes)
2247 {
2248 const bool isSelected = (_synchronizationControlRegister.syncOutMode == synchronizationControlSyncOutMode.first);
2249 if (ImGui::Selectable(vn::protocol::uart::str(synchronizationControlSyncOutMode.first).c_str(), isSelected))
2250 {
2251 _synchronizationControlRegister.syncOutMode = synchronizationControlSyncOutMode.first;
2252 LOG_DEBUG("{}: synchronizationControlRegister.syncOutMode changed to {}", nameId(), vn::protocol::uart::str(_synchronizationControlRegister.syncOutMode));
2253
2254 if (_synchronizationControlRegister.syncOutMode == vn::protocol::uart::SyncOutMode::SYNCOUTMODE_GPSPPS
2255 && outputPins.size() <= 4)
2256 {
2257 nm::CreateOutputPin(this, "SyncOut", Pin::Type::Object, { "TimeSync" }, &_timeSyncOut);
2258 }
2259 else if (_synchronizationControlRegister.syncOutMode != vn::protocol::uart::SyncOutMode::SYNCOUTMODE_GPSPPS
2260 && outputPins.size() == 5)
2261 {
2262 nm::DeleteOutputPin(outputPins.at(4));
2263 }
2264
2265 flow::ApplyChanges();
2266 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2267 {
2268 try
2269 {
2270 _vs.writeSynchronizationControl(_synchronizationControlRegister);
2271 }
2272 catch (const std::exception& e)
2273 {
2274 LOG_ERROR("{}: Could not configure the synchronizationControlRegister: {}", nameId(), e.what());
2275 doDeinitialize();
2276 }
2277 }
2278 else
2279 {
2280 doDeinitialize();
2281 }
2282 }
2283 if (ImGui::IsItemHovered())
2284 {
2285 ImGui::BeginTooltip();
2286 ImGui::TextUnformatted(synchronizationControlSyncOutMode.second);
2287 ImGui::EndTooltip();
2288 }
2289
2290 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2291 {
2292 ImGui::SetItemDefaultFocus();
2293 }
2294 }
2295 ImGui::EndCombo();
2296 }
2297 ImGui::SameLine();
2298 gui::widgets::HelpMarker("The SyncOutMode register controls the behavior of the SyncOut pin. If this is set to IMU then the SyncOut "
2299 "will start the pulse when the internal IMU sample loop starts. This mode is used to make a sensor the Master "
2300 "in a multi-sensor network array. If this is set to IMU_READY mode then the pulse will start when IMU "
2301 "measurements become available. If this is set to INS mode then the pulse will start when attitude "
2302 "measurements are made available. Changes to this register take effect immediately.");
2303
2304 static constexpr std::array<std::pair<vn::protocol::uart::SyncOutPolarity, const char*>, 2> synchronizationControlSyncOutPolarities = {
2305 { { vn::protocol::uart::SyncOutPolarity::SYNCOUTPOLARITY_NEGATIVE, "Negative Pulse" },
2306 { vn::protocol::uart::SyncOutPolarity::SYNCOUTPOLARITY_POSITIVE, "Positive Pulse" } }
2307 };
2308 if (ImGui::BeginCombo(fmt::format("SyncOut Polarity##{}", size_t(id)).c_str(), vn::protocol::uart::str(_synchronizationControlRegister.syncOutPolarity).c_str()))
2309 {
2310 for (const auto& synchronizationControlSyncOutPolarity : synchronizationControlSyncOutPolarities)
2311 {
2312 const bool isSelected = (_synchronizationControlRegister.syncOutPolarity == synchronizationControlSyncOutPolarity.first);
2313 if (ImGui::Selectable(vn::protocol::uart::str(synchronizationControlSyncOutPolarity.first).c_str(), isSelected))
2314 {
2315 _synchronizationControlRegister.syncOutPolarity = synchronizationControlSyncOutPolarity.first;
2316 LOG_DEBUG("{}: synchronizationControlRegister.syncOutPolarity changed to {}", nameId(), vn::protocol::uart::str(_synchronizationControlRegister.syncOutPolarity));
2317 flow::ApplyChanges();
2318 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2319 {
2320 try
2321 {
2322 _vs.writeSynchronizationControl(_synchronizationControlRegister);
2323 }
2324 catch (const std::exception& e)
2325 {
2326 LOG_ERROR("{}: Could not configure the synchronizationControlRegister: {}", nameId(), e.what());
2327 doDeinitialize();
2328 }
2329 }
2330 else
2331 {
2332 doDeinitialize();
2333 }
2334 }
2335 if (ImGui::IsItemHovered())
2336 {
2337 ImGui::BeginTooltip();
2338 ImGui::TextUnformatted(synchronizationControlSyncOutPolarity.second);
2339 ImGui::EndTooltip();
2340 }
2341
2342 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2343 {
2344 ImGui::SetItemDefaultFocus();
2345 }
2346 }
2347 ImGui::EndCombo();
2348 }
2349 ImGui::SameLine();
2350 gui::widgets::HelpMarker("The SyncOutPolarity register controls the polarity of the output pulse on the SyncOut pin.\n"
2351 "Changes to this register take effect immediately.");
2352
2353 int syncOutSkipFactor = _synchronizationControlRegister.syncOutSkipFactor;
2354 if (ImGui::InputInt(fmt::format("SyncOut Skip Factor##{}", size_t(id)).c_str(), &syncOutSkipFactor))
2355 {
2356 if (syncOutSkipFactor < 0)
2357 {
2358 syncOutSkipFactor = 0;
2359 }
2360 else if (syncOutSkipFactor > std::numeric_limits<uint16_t>::max())
2361 {
2362 syncOutSkipFactor = std::numeric_limits<uint16_t>::max();
2363 }
2364 _synchronizationControlRegister.syncOutSkipFactor = static_cast<uint16_t>(syncOutSkipFactor);
2365 LOG_DEBUG("{}: synchronizationControlRegister.syncOutSkipFactor changed to {}", nameId(), _synchronizationControlRegister.syncOutSkipFactor);
2366 flow::ApplyChanges();
2367 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2368 {
2369 try
2370 {
2371 _vs.writeSynchronizationControl(_synchronizationControlRegister);
2372 }
2373 catch (const std::exception& e)
2374 {
2375 LOG_ERROR("{}: Could not configure the synchronizationControlRegister: {}", nameId(), e.what());
2376 doDeinitialize();
2377 }
2378 }
2379 else
2380 {
2381 doDeinitialize();
2382 }
2383 }
2384 ImGui::SameLine();
2385 gui::widgets::HelpMarker("The SyncOutSkipFactor defines how many times the sync out event should be skipped before actually triggering the SyncOut pin.");
2386
2387 int syncOutPulseWidth = static_cast<int>(_synchronizationControlRegister.syncOutPulseWidth);
2388 if (ImGui::InputInt(fmt::format("SyncOut Pulse Width##{}", size_t(id)).c_str(), &syncOutPulseWidth))
2389 {
2390 syncOutPulseWidth = std::max(syncOutPulseWidth, 0);
2391 _synchronizationControlRegister.syncOutPulseWidth = static_cast<uint32_t>(syncOutPulseWidth);
2392 LOG_DEBUG("{}: synchronizationControlRegister.syncOutPulseWidth changed to {}", nameId(), _synchronizationControlRegister.syncOutPulseWidth);
2393 flow::ApplyChanges();
2394 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2395 {
2396 try
2397 {
2398 _vs.writeSynchronizationControl(_synchronizationControlRegister);
2399 }
2400 catch (const std::exception& e)
2401 {
2402 LOG_ERROR("{}: Could not configure the synchronizationControlRegister: {}", nameId(), e.what());
2403 doDeinitialize();
2404 }
2405 }
2406 else
2407 {
2408 doDeinitialize();
2409 }
2410 }
2411 ImGui::SameLine();
2412 gui::widgets::HelpMarker("The SyncOutPulseWidth field controls the desired width of the SyncOut pulse.\n"
2413 "The default value is 100,000,000 (100 ms).");
2414
2415 ImGui::TreePop();
2416 }
2417
2418 if (ImGui::TreeNode(fmt::format("Communication Protocol Control##{}", size_t(id)).c_str()))
2419 {
2420 ImGui::TextUnformatted("Contains parameters that controls the communication protocol used by the sensor.");
2421
2422 static constexpr std::array<std::pair<vn::protocol::uart::CountMode, const char*>, 5> communicationProtocolControlSerialCounts = {
2423 { { vn::protocol::uart::CountMode::COUNTMODE_NONE, "OFF" },
2424 { vn::protocol::uart::CountMode::COUNTMODE_SYNCINCOUNT, "SyncIn Counter" },
2425 { vn::protocol::uart::CountMode::COUNTMODE_SYNCINTIME, "SyncIn Time" },
2426 { vn::protocol::uart::CountMode::COUNTMODE_SYNCOUTCOUNTER, "SyncOut Counter" },
2427 { vn::protocol::uart::CountMode::COUNTMODE_GPSPPS, "Gps Pps Time" } }
2428 };
2429 if (ImGui::BeginCombo(fmt::format("Serial Count Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_communicationProtocolControlRegister.serialCount).c_str()))
2430 {
2431 for (const auto& communicationProtocolControlSerialCount : communicationProtocolControlSerialCounts)
2432 {
2433 const bool isSelected = (_communicationProtocolControlRegister.serialCount == communicationProtocolControlSerialCount.first);
2434 if (ImGui::Selectable(vn::protocol::uart::str(communicationProtocolControlSerialCount.first).c_str(), isSelected))
2435 {
2436 _communicationProtocolControlRegister.serialCount = communicationProtocolControlSerialCount.first;
2437 LOG_DEBUG("{}: communicationProtocolControlRegister.serialCount changed to {}", nameId(), vn::protocol::uart::str(_communicationProtocolControlRegister.serialCount));
2438 flow::ApplyChanges();
2439 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2440 {
2441 try
2442 {
2443 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
2444 }
2445 catch (const std::exception& e)
2446 {
2447 LOG_ERROR("{}: Could not configure the communicationProtocolControlRegister: {}", nameId(), e.what());
2448 doDeinitialize();
2449 }
2450 }
2451 else
2452 {
2453 doDeinitialize();
2454 }
2455 }
2456 if (ImGui::IsItemHovered())
2457 {
2458 ImGui::BeginTooltip();
2459 ImGui::TextUnformatted(communicationProtocolControlSerialCount.second);
2460 ImGui::EndTooltip();
2461 }
2462
2463 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2464 {
2465 ImGui::SetItemDefaultFocus();
2466 }
2467 }
2468 ImGui::EndCombo();
2469 }
2470 ImGui::SameLine();
2471 gui::widgets::HelpMarker("The SerialCount field provides a means of appending a time or counter to the end of all asynchronous "
2472 "communication messages transmitted on the serial interface. The values for each of these counters come "
2473 "directly from the Synchronization Status Register in the System subsystem.\n\n"
2474 "With the SerialCount field set to OFF a typical serial asynchronous message would appear as the following:\n"
2475 "$VNYPR,+010.071,+000.278,-002.026*60\n\n"
2476 "With the SerialCount field set to one of the non-zero values the same asynchronous message would appear "
2477 "instead as:\n"
2478 "$VNYPR,+010.071,+000.278,-002.026,T1162704*2F\n\n"
2479 "When the SerialCount field is enabled the counter will always be appended to the end of the message just "
2480 "prior to the checksum. The counter will be preceded by the T character to distinguish it from the status field.");
2481
2482 static constexpr std::array<std::pair<vn::protocol::uart::StatusMode, const char*>, 3> communicationProtocolControlSerialStatuses = {
2483 { { vn::protocol::uart::StatusMode::STATUSMODE_OFF, "OFF" },
2484 { vn::protocol::uart::StatusMode::STATUSMODE_VPESTATUS, "VPE Status" },
2485 { vn::protocol::uart::StatusMode::STATUSMODE_INSSTATUS, "INS Status" } }
2486 };
2487 if (ImGui::BeginCombo(fmt::format("Serial Status##{}", size_t(id)).c_str(), vn::protocol::uart::str(_communicationProtocolControlRegister.serialStatus).c_str()))
2488 {
2489 for (const auto& communicationProtocolControlSerialStatus : communicationProtocolControlSerialStatuses)
2490 {
2491 const bool isSelected = (_communicationProtocolControlRegister.serialStatus == communicationProtocolControlSerialStatus.first);
2492 if (ImGui::Selectable(vn::protocol::uart::str(communicationProtocolControlSerialStatus.first).c_str(), isSelected))
2493 {
2494 _communicationProtocolControlRegister.serialStatus = communicationProtocolControlSerialStatus.first;
2495 LOG_DEBUG("{}: communicationProtocolControlRegister.serialStatus changed to {}", nameId(), vn::protocol::uart::str(_communicationProtocolControlRegister.serialStatus));
2496 flow::ApplyChanges();
2497 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2498 {
2499 try
2500 {
2501 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
2502 }
2503 catch (const std::exception& e)
2504 {
2505 LOG_ERROR("{}: Could not configure the communicationProtocolControlRegister: {}", nameId(), e.what());
2506 doDeinitialize();
2507 }
2508 }
2509 else
2510 {
2511 doDeinitialize();
2512 }
2513 }
2514 if (ImGui::IsItemHovered())
2515 {
2516 ImGui::BeginTooltip();
2517 ImGui::TextUnformatted(communicationProtocolControlSerialStatus.second);
2518 ImGui::EndTooltip();
2519 }
2520
2521 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2522 {
2523 ImGui::SetItemDefaultFocus();
2524 }
2525 }
2526 ImGui::EndCombo();
2527 }
2528 ImGui::SameLine();
2529 gui::widgets::HelpMarker("The SerialStatus field provides a means of tracking real-time status information pertain to the overall state "
2530 "of the sensor measurements and onboard filtering algorithm. As with the SerialCount, a typical serial "
2531 "asynchronous message would appear as the following:\n"
2532 "$VNYPR,+010.071,+000.278,-002.026*60\n\n"
2533 "With the SerialStatus field set to one of the non-zero values, the same asynchronous message would appear "
2534 "instead as:\n"
2535 "$VNYPR,+010.071,+000.278,-002.026,S0000*1F\n\n"
2536 "When the SerialStatus field is enabled the status will always be appended to the end of the message just "
2537 "prior to the checksum. If both the SerialCount and SerialStatus are enabled then the SerialStatus will be "
2538 "displayed first. The counter will be preceded by the S character to distinguish it from the counter field. The "
2539 "status consists of 4 hexadecimal characters.");
2540
2541 if (_sensorModel == VectorNavModel::VN100_VN110)
2542 {
2543 if (ImGui::BeginCombo(fmt::format("SPI Count##{}", size_t(id)).c_str(), vn::protocol::uart::str(_communicationProtocolControlRegister.spiCount).c_str()))
2544 {
2545 for (const auto& communicationProtocolControlSpiCount : communicationProtocolControlSerialCounts)
2546 {
2547 const bool isSelected = (_communicationProtocolControlRegister.spiCount == communicationProtocolControlSpiCount.first);
2548 if (ImGui::Selectable(vn::protocol::uart::str(communicationProtocolControlSpiCount.first).c_str(), isSelected))
2549 {
2550 _communicationProtocolControlRegister.spiCount = communicationProtocolControlSpiCount.first;
2551 LOG_DEBUG("{}: communicationProtocolControlRegister.spiCount changed to {}", nameId(), vn::protocol::uart::str(_communicationProtocolControlRegister.spiCount));
2552 flow::ApplyChanges();
2553 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2554 {
2555 try
2556 {
2557 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
2558 }
2559 catch (const std::exception& e)
2560 {
2561 LOG_ERROR("{}: Could not configure the communicationProtocolControlRegister: {}", nameId(), e.what());
2562 doDeinitialize();
2563 }
2564 }
2565 else
2566 {
2567 doDeinitialize();
2568 }
2569 }
2570 if (ImGui::IsItemHovered())
2571 {
2572 ImGui::BeginTooltip();
2573 ImGui::TextUnformatted(communicationProtocolControlSpiCount.second);
2574 ImGui::EndTooltip();
2575 }
2576
2577 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2578 {
2579 ImGui::SetItemDefaultFocus();
2580 }
2581 }
2582 ImGui::EndCombo();
2583 }
2584 ImGui::SameLine();
2585 gui::widgets::HelpMarker("The SPICount field provides a means of appending a time or counter to the end of all SPI packets. The "
2586 "values for each of these counters come directly from the Synchronization Status Register.");
2587 }
2588
2589 if (_sensorModel == VectorNavModel::VN100_VN110)
2590 {
2591 if (ImGui::BeginCombo(fmt::format("SPI Status##{}", size_t(id)).c_str(), vn::protocol::uart::str(_communicationProtocolControlRegister.spiStatus).c_str()))
2592 {
2593 for (const auto& communicationProtocolControlSpiStatus : communicationProtocolControlSerialStatuses)
2594 {
2595 const bool isSelected = (_communicationProtocolControlRegister.spiStatus == communicationProtocolControlSpiStatus.first);
2596 if (ImGui::Selectable(vn::protocol::uart::str(communicationProtocolControlSpiStatus.first).c_str(), isSelected))
2597 {
2598 _communicationProtocolControlRegister.spiStatus = communicationProtocolControlSpiStatus.first;
2599 LOG_DEBUG("{}: communicationProtocolControlRegister.spiStatus changed to {}", nameId(), vn::protocol::uart::str(_communicationProtocolControlRegister.spiStatus));
2600 flow::ApplyChanges();
2601 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2602 {
2603 try
2604 {
2605 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
2606 }
2607 catch (const std::exception& e)
2608 {
2609 LOG_ERROR("{}: Could not configure the communicationProtocolControlRegister: {}", nameId(), e.what());
2610 doDeinitialize();
2611 }
2612 }
2613 else
2614 {
2615 doDeinitialize();
2616 }
2617 }
2618 if (ImGui::IsItemHovered())
2619 {
2620 ImGui::BeginTooltip();
2621 ImGui::TextUnformatted(communicationProtocolControlSpiStatus.second);
2622 ImGui::EndTooltip();
2623 }
2624
2625 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2626 {
2627 ImGui::SetItemDefaultFocus();
2628 }
2629 }
2630 ImGui::EndCombo();
2631 }
2632 ImGui::SameLine();
2633 gui::widgets::HelpMarker("The AsyncStatus field provides a means of tracking real-time status information pertaining to the overall "
2634 "state of the sensor measurements and onboard filtering algorithm. This information is very useful in "
2635 "situations where action must be taken when certain crucial events happen such as the detection of gyro "
2636 "saturation or magnetic interference.");
2637 }
2638
2639 static constexpr std::array<std::pair<vn::protocol::uart::ChecksumMode, const char*>, 2> communicationProtocolControlSerialChecksums = {
2640 { { vn::protocol::uart::ChecksumMode::CHECKSUMMODE_CHECKSUM, "8-Bit Checksum" },
2641 { vn::protocol::uart::ChecksumMode::CHECKSUMMODE_CRC, "16-Bit CRC" } }
2642 };
2643 if (ImGui::BeginCombo(fmt::format("Serial Checksum##{}", size_t(id)).c_str(), vn::protocol::uart::str(_communicationProtocolControlRegister.serialChecksum).c_str()))
2644 {
2645 for (const auto& communicationProtocolControlSerialChecksum : communicationProtocolControlSerialChecksums)
2646 {
2647 const bool isSelected = (_communicationProtocolControlRegister.serialChecksum == communicationProtocolControlSerialChecksum.first);
2648 if (ImGui::Selectable(vn::protocol::uart::str(communicationProtocolControlSerialChecksum.first).c_str(), isSelected))
2649 {
2650 _communicationProtocolControlRegister.serialChecksum = communicationProtocolControlSerialChecksum.first;
2651 LOG_DEBUG("{}: communicationProtocolControlRegister.serialChecksum changed to {}", nameId(), vn::protocol::uart::str(_communicationProtocolControlRegister.serialChecksum));
2652 flow::ApplyChanges();
2653 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2654 {
2655 try
2656 {
2657 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
2658 }
2659 catch (const std::exception& e)
2660 {
2661 LOG_ERROR("{}: Could not configure the communicationProtocolControlRegister: {}", nameId(), e.what());
2662 doDeinitialize();
2663 }
2664 }
2665 else
2666 {
2667 doDeinitialize();
2668 }
2669 }
2670 if (ImGui::IsItemHovered())
2671 {
2672 ImGui::BeginTooltip();
2673 ImGui::TextUnformatted(communicationProtocolControlSerialChecksum.second);
2674 ImGui::EndTooltip();
2675 }
2676
2677 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2678 {
2679 ImGui::SetItemDefaultFocus();
2680 }
2681 }
2682 ImGui::EndCombo();
2683 }
2684 ImGui::SameLine();
2685 gui::widgets::HelpMarker("This field controls the type of checksum used for the serial communications. Normally the VN-310E uses an "
2686 "8-bit checksum identical to the type used for normal GPS NMEA packets. This form of checksum however "
2687 "offers only a limited means of error checking. As an alternative a full 16-bit CRC (CRC16-CCITT with "
2688 "polynomial = 0x07) is also offered. The 2-byte CRC value is printed using 4 hexadecimal digits.");
2689
2690 if (_sensorModel == VectorNavModel::VN100_VN110)
2691 {
2692 static constexpr std::array<std::pair<vn::protocol::uart::ChecksumMode, const char*>, 3> communicationProtocolControlSpiChecksums = {
2693 { { vn::protocol::uart::ChecksumMode::CHECKSUMMODE_OFF, "OFF" },
2694 { vn::protocol::uart::ChecksumMode::CHECKSUMMODE_CHECKSUM, "8-Bit Checksum" },
2695 { vn::protocol::uart::ChecksumMode::CHECKSUMMODE_CRC, "16-Bit CRC" } }
2696 };
2697 if (ImGui::BeginCombo(fmt::format("SPI Checksum##{}", size_t(id)).c_str(), vn::protocol::uart::str(_communicationProtocolControlRegister.spiChecksum).c_str()))
2698 {
2699 for (const auto& communicationProtocolControlSpiChecksum : communicationProtocolControlSpiChecksums)
2700 {
2701 const bool isSelected = (_communicationProtocolControlRegister.spiChecksum == communicationProtocolControlSpiChecksum.first);
2702 if (ImGui::Selectable(vn::protocol::uart::str(communicationProtocolControlSpiChecksum.first).c_str(), isSelected))
2703 {
2704 _communicationProtocolControlRegister.spiChecksum = communicationProtocolControlSpiChecksum.first;
2705 LOG_DEBUG("{}: communicationProtocolControlRegister.spiChecksum changed to {}", nameId(), vn::protocol::uart::str(_communicationProtocolControlRegister.spiChecksum));
2706 flow::ApplyChanges();
2707 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2708 {
2709 try
2710 {
2711 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
2712 }
2713 catch (const std::exception& e)
2714 {
2715 LOG_ERROR("{}: Could not configure the communicationProtocolControlRegister: {}", nameId(), e.what());
2716 doDeinitialize();
2717 }
2718 }
2719 else
2720 {
2721 doDeinitialize();
2722 }
2723 }
2724 if (ImGui::IsItemHovered())
2725 {
2726 ImGui::BeginTooltip();
2727 ImGui::TextUnformatted(communicationProtocolControlSpiChecksum.second);
2728 ImGui::EndTooltip();
2729 }
2730
2731 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2732 {
2733 ImGui::SetItemDefaultFocus();
2734 }
2735 }
2736 ImGui::EndCombo();
2737 }
2738 ImGui::SameLine();
2739 gui::widgets::HelpMarker("This field controls the type of checksum used for the SPI communications. The checksum is appended to "
2740 "the end of the binary data packet. The 16-bit CRC is identical to the one described above for the "
2741 "SerialChecksum.");
2742 }
2743
2744 static constexpr std::array<std::pair<vn::protocol::uart::ErrorMode, const char*>, 3> communicationProtocolControlErrorModes = {
2745 { { vn::protocol::uart::ErrorMode::ERRORMODE_IGNORE, "Ignore Error" },
2746 { vn::protocol::uart::ErrorMode::ERRORMODE_SEND, "Send Error" },
2747 { vn::protocol::uart::ErrorMode::ERRORMODE_SENDANDOFF, "Send Error and set ADOR register to OFF" } }
2748 };
2749 if (ImGui::BeginCombo(fmt::format("Error Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_communicationProtocolControlRegister.errorMode).c_str()))
2750 {
2751 for (const auto& communicationProtocolControlErrorMode : communicationProtocolControlErrorModes)
2752 {
2753 const bool isSelected = (_communicationProtocolControlRegister.errorMode == communicationProtocolControlErrorMode.first);
2754 if (ImGui::Selectable(vn::protocol::uart::str(communicationProtocolControlErrorMode.first).c_str(), isSelected))
2755 {
2756 _communicationProtocolControlRegister.errorMode = communicationProtocolControlErrorMode.first;
2757 LOG_DEBUG("{}: communicationProtocolControlRegister.errorMode changed to {}", nameId(), vn::protocol::uart::str(_communicationProtocolControlRegister.errorMode));
2758 flow::ApplyChanges();
2759 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2760 {
2761 try
2762 {
2763 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
2764 }
2765 catch (const std::exception& e)
2766 {
2767 LOG_ERROR("{}: Could not configure the communicationProtocolControlRegister: {}", nameId(), e.what());
2768 doDeinitialize();
2769 }
2770 }
2771 else
2772 {
2773 doDeinitialize();
2774 }
2775 }
2776 if (ImGui::IsItemHovered())
2777 {
2778 ImGui::BeginTooltip();
2779 ImGui::TextUnformatted(communicationProtocolControlErrorMode.second);
2780 ImGui::EndTooltip();
2781 }
2782
2783 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2784 {
2785 ImGui::SetItemDefaultFocus();
2786 }
2787 }
2788 ImGui::EndCombo();
2789 }
2790 ImGui::SameLine();
2791 gui::widgets::HelpMarker("This field controls the type of action taken by the VectorNav when an error event occurs. If the send error "
2792 "mode is enabled then a message similar to the one shown below will be sent on the serial bus when an error "
2793 "event occurs.\n\n"
2794 "$VNERR,03*72\n\n"
2795 "Regardless of the state of the ErrorMode, the number of error events is always recorded and is made available "
2796 "in the SysErrors field of the Communication Protocol Status Register in the System subsystem.");
2797
2798 if (ImGui::TreeNode(fmt::format("Example Async Messages##{}", size_t(id)).c_str()))
2799 {
2800 ImGui::TextUnformatted("The following table shows example asynchronous messages with the\nAsyncCount and the AsyncStatus values appended to the end.");
2801
2802 if (ImGui::BeginTable("Example Async Messages Table", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
2803 {
2804 ImGui::TableSetupColumn("Example Type");
2805 ImGui::TableSetupColumn("Message");
2806 ImGui::TableHeadersRow();
2807
2808 ImGui::TableNextColumn();
2809 ImGui::TextUnformatted("Async Message with\nAsyncCount Enabled");
2810 ImGui::TableNextColumn();
2811 ImGui::TextUnformatted("$VNYPR,+010.071,+000.278,-002.026,T1162704*2F");
2812
2813 ImGui::TableNextColumn();
2814 ImGui::TextUnformatted("Async Message with\nAsyncStatus Enabled");
2815 ImGui::TableNextColumn();
2816 ImGui::TextUnformatted("$VNYPR,+010.071,+000.278,-002.026,S0000*1F");
2817
2818 ImGui::TableNextColumn();
2819 ImGui::TextUnformatted("Async Message with\nAsyncCount and\nAsyncStatus Enabled");
2820 ImGui::TableNextColumn();
2821 ImGui::TextUnformatted("$VNYPR,+010.071,+000.278,-002.026,T1162704,S0000*50");
2822
2823 ImGui::EndTable();
2824 }
2825
2826 ImGui::TreePop();
2827 }
2828
2829 ImGui::TreePop();
2830 }
2831
2832 for (size_t b = 0; b < _binaryOutputRegister.size(); b++)
2833 {
2834 if (ImGui::TreeNode(fmt::format("Binary Output {}##{}", b + 1, size_t(id)).c_str()))
2835 {
2836 static constexpr std::array<std::pair<vn::protocol::uart::AsyncMode, const char*>, 4> asyncModes = {
2837 { { vn::protocol::uart::AsyncMode::ASYNCMODE_NONE, " User message is not automatically sent out either serial port" },
2838 { vn::protocol::uart::AsyncMode::ASYNCMODE_PORT1, "Message is sent out serial port 1 at a fixed rate" },
2839 { vn::protocol::uart::AsyncMode::ASYNCMODE_PORT2, "Message is sent out serial port 2 at a fixed rate" },
2840 { vn::protocol::uart::AsyncMode::ASYNCMODE_BOTH, "Message is sent out both serial ports at a fixed rate" } }
2841 };
2842 if ((_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output2 && b == 1)
2843 || (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output3 && b == 2)
2844 || (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output2_Output3 && b == 2))
2845 {
2846 ImGui::BeginDisabled();
2847 }
2848
2849 if (ImGui::BeginCombo(fmt::format("Async Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_binaryOutputRegister.at(b).asyncMode).c_str()))
2850 {
2851 for (const auto& asyncMode : asyncModes)
2852 {
2853 const bool isSelected = (_binaryOutputRegister.at(b).asyncMode == asyncMode.first);
2854 if (ImGui::Selectable(vn::protocol::uart::str(asyncMode.first).c_str(), isSelected))
2855 {
2856 _binaryOutputRegister.at(b).asyncMode = asyncMode.first;
2857 LOG_DEBUG("{}: binaryOutputRegister.at({}).asyncMode changed to {}", nameId(), b, vn::protocol::uart::str(_binaryOutputRegister.at(b).asyncMode));
2858
2859 std::vector<size_t> binaryOutputRegistersToUpdate;
2860 binaryOutputRegistersToUpdate.push_back(b);
2861 if (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output2 && b == 0)
2862 {
2863 _binaryOutputRegister.at(1).asyncMode = asyncMode.first;
2864 LOG_DEBUG("{}: binaryOutputRegister.at({}).asyncMode changed to {}", nameId(), 1, vn::protocol::uart::str(_binaryOutputRegister.at(1).asyncMode));
2865 binaryOutputRegistersToUpdate.push_back(1);
2866 }
2867 else if ((_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output3 && b == 0)
2868 || (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output2_Output3 && b == 1))
2869 {
2870 _binaryOutputRegister.at(2).asyncMode = asyncMode.first;
2871 LOG_DEBUG("{}: binaryOutputRegister.at({}).asyncMode changed to {}", nameId(), 2, vn::protocol::uart::str(_binaryOutputRegister.at(2).asyncMode));
2872 binaryOutputRegistersToUpdate.push_back(2);
2873 }
2874 flow::ApplyChanges();
2875
2876 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2877 {
2878 for (const auto& binUpdate : binaryOutputRegistersToUpdate)
2879 {
2880 try
2881 {
2882 switch (binUpdate)
2883 {
2884 case 0:
2885 _vs.writeBinaryOutput1(_binaryOutputRegister.at(binUpdate));
2886 break;
2887 case 1:
2888 _vs.writeBinaryOutput2(_binaryOutputRegister.at(binUpdate));
2889 break;
2890 case 2:
2891 _vs.writeBinaryOutput3(_binaryOutputRegister.at(binUpdate));
2892 break;
2893 default:
2894 break;
2895 }
2896 }
2897 catch (const std::exception& e)
2898 {
2899 LOG_ERROR("{}: Could not configure the binaryOutputRegister {}: {}", nameId(), binUpdate + 1, e.what());
2900 doDeinitialize();
2901 }
2902 }
2903 }
2904 else
2905 {
2906 doDeinitialize();
2907 }
2908 }
2909 if (ImGui::IsItemHovered())
2910 {
2911 ImGui::BeginTooltip();
2912 ImGui::TextUnformatted(asyncMode.second);
2913 ImGui::EndTooltip();
2914 }
2915
2916 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
2917 {
2918 ImGui::SetItemDefaultFocus();
2919 }
2920 }
2921 ImGui::EndCombo();
2922 }
2923 ImGui::SameLine();
2924 gui::widgets::HelpMarker("Selects whether the output message should be sent "
2925 "out on the serial port(s) at a fixed rate.");
2926
2927 const char* frequencyText = _binaryOutputSelectedFrequency.at(b) < _dividerFrequency.first.size()
2928 ? _dividerFrequency.second.at(_binaryOutputSelectedFrequency.at(b)).c_str()
2929 : "Unknown";
2930 if (auto freq = static_cast<int>(_binaryOutputSelectedFrequency.at(b));
2931 ImGui::SliderInt(fmt::format("Frequency##{} {}", size_t(id), b).c_str(),
2932 &freq,
2933 0, static_cast<int>(_dividerFrequency.second.size()) - 1,
2934 frequencyText))
2935 {
2936 _binaryOutputSelectedFrequency.at(b) = static_cast<size_t>(freq);
2937 _binaryOutputRegister.at(b).rateDivisor = _dividerFrequency.first.at(_binaryOutputSelectedFrequency.at(b));
2938 LOG_DEBUG("{}: Frequency of Binary Group {} changed to {}", nameId(), b + 1, _dividerFrequency.second.at(_binaryOutputSelectedFrequency.at(b)));
2939 if (_coupleImuRateOutput)
2940 {
2941 if (_binaryOutputRegister.at(b).imuField & (vn::protocol::uart::IMUGROUP_ACCEL | vn::protocol::uart::IMUGROUP_UNCOMPACCEL))
2942 {
2943 _imuFilteringConfigurationRegister.accelWindowSize = _binaryOutputRegister.at(b).rateDivisor;
2944 }
2945 if (_binaryOutputRegister.at(b).imuField & (vn::protocol::uart::IMUGROUP_ANGULARRATE | vn::protocol::uart::IMUGROUP_UNCOMPGYRO))
2946 {
2947 _imuFilteringConfigurationRegister.gyroWindowSize = _binaryOutputRegister.at(b).rateDivisor;
2948 }
2949 if (_binaryOutputRegister.at(b).imuField & (vn::protocol::uart::IMUGROUP_MAG | vn::protocol::uart::IMUGROUP_UNCOMPMAG))
2950 {
2951 _imuFilteringConfigurationRegister.magWindowSize = _binaryOutputRegister.at(b).rateDivisor;
2952 }
2953 if (_binaryOutputRegister.at(b).imuField & (vn::protocol::uart::IMUGROUP_MAG | vn::protocol::uart::IMUGROUP_UNCOMPMAG))
2954 {
2955 _imuFilteringConfigurationRegister.magWindowSize = _binaryOutputRegister.at(b).rateDivisor;
2956 }
2957 if (_binaryOutputRegister.at(b).imuField & vn::protocol::uart::IMUGROUP_TEMP)
2958 {
2959 _imuFilteringConfigurationRegister.tempWindowSize = _binaryOutputRegister.at(b).rateDivisor;
2960 }
2961 if (_binaryOutputRegister.at(b).imuField & vn::protocol::uart::IMUGROUP_PRES)
2962 {
2963 _imuFilteringConfigurationRegister.presWindowSize = _binaryOutputRegister.at(b).rateDivisor;
2964 }
2965 LOG_DATA("{}: Frequency of ImuFilter matches output frequency (coupled 'rateDivisor' and 'windowSize' of moving average filter.)", nameId());
2966
2967 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
2968 {
2969 try
2970 {
2971 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
2972 }
2973 catch (const std::exception& e)
2974 {
2975 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
2976 doDeinitialize();
2977 }
2978 }
2979 else
2980 {
2981 doDeinitialize();
2982 }
2983 }
2984
2985 std::vector<size_t> binaryOutputRegistersToUpdate;
2986 binaryOutputRegistersToUpdate.push_back(b);
2987 if (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output2 && b == 0)
2988 {
2989 _binaryOutputSelectedFrequency.at(1) = _binaryOutputSelectedFrequency.at(b);
2990 _binaryOutputRegister.at(1).rateDivisor = _dividerFrequency.first.at(_binaryOutputSelectedFrequency.at(1));
2991 LOG_DEBUG("{}: Frequency of Binary Group {} changed to {}", nameId(), 1 + 1, _dividerFrequency.second.at(_binaryOutputSelectedFrequency.at(1)));
2992 binaryOutputRegistersToUpdate.push_back(1);
2993 }
2994 else if ((_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output3 && b == 0)
2995 || (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output2_Output3 && b == 1))
2996 {
2997 _binaryOutputSelectedFrequency.at(2) = _binaryOutputSelectedFrequency.at(b);
2998 _binaryOutputRegister.at(2).rateDivisor = _dividerFrequency.first.at(_binaryOutputSelectedFrequency.at(2));
2999 LOG_DEBUG("{}: Frequency of Binary Group {} changed to {}", nameId(), 2 + 1, _dividerFrequency.second.at(_binaryOutputSelectedFrequency.at(2)));
3000 binaryOutputRegistersToUpdate.push_back(2);
3001 }
3002
3003 flow::ApplyChanges();
3004 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3005 {
3006 for (const auto& binUpdate : binaryOutputRegistersToUpdate)
3007 {
3008 try
3009 {
3010 switch (binUpdate)
3011 {
3012 case 0:
3013 _vs.writeBinaryOutput1(_binaryOutputRegister.at(binUpdate));
3014 break;
3015 case 1:
3016 _vs.writeBinaryOutput2(_binaryOutputRegister.at(binUpdate));
3017 break;
3018 case 2:
3019 _vs.writeBinaryOutput3(_binaryOutputRegister.at(binUpdate));
3020 break;
3021 default:
3022 break;
3023 }
3024 }
3025 catch (const std::exception& e)
3026 {
3027 LOG_ERROR("{}: Could not configure the binaryOutputRegister {}: {}", nameId(), binUpdate + 1, e.what());
3028 doDeinitialize();
3029 }
3030 }
3031 }
3032 else
3033 {
3034 doDeinitialize();
3035 }
3036 }
3037 if ((_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output2 && b == 1)
3038 || (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output3 && b == 2)
3039 || (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output2_Output3 && b == 2))
3040 {
3041 ImGui::EndDisabled();
3042 }
3043
3044 if (ImGui::BeginTable(fmt::format("##VectorNavSensorConfig ({})", size_t(id)).c_str(), 6,
3045 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
3046 {
3047 // ImGui::TableSetupColumn("Common", ImGuiTableColumnFlags_WidthFixed);
3048 ImGui::TableSetupColumn("Time", ImGuiTableColumnFlags_WidthFixed);
3049 ImGui::TableSetupColumn("IMU", ImGuiTableColumnFlags_WidthFixed);
3050 ImGui::TableSetupColumn("GNSS1", ImGuiTableColumnFlags_WidthFixed);
3051 ImGui::TableSetupColumn("Attitude", ImGuiTableColumnFlags_WidthFixed);
3052 ImGui::TableSetupColumn("INS", ImGuiTableColumnFlags_WidthFixed);
3053 ImGui::TableSetupColumn("GNSS2", ImGuiTableColumnFlags_WidthFixed);
3054 ImGui::TableHeadersRow();
3055
3056 auto CheckboxFlags = [&, this](int index, const char* label, int* flags, int flags_value, bool enabled, void (*toggleFields)(VectorNavSensor*, vn::sensors::BinaryOutputRegister&, uint32_t&)) {
3057 ImGui::TableSetColumnIndex(index);
3058
3059 if (!enabled)
3060 {
3061 ImGui::BeginDisabled();
3062 }
3063
3064 if (ImGui::CheckboxFlags(label, flags, flags_value))
3065 {
3066 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");
3067 if (toggleFields)
3068 {
3069 toggleFields(this, _binaryOutputRegister.at(b), *reinterpret_cast<uint32_t*>(flags));
3070 }
3071 flow::ApplyChanges();
3072 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3073 {
3074 try
3075 {
3076 if (b == 0)
3077 {
3078 _vs.writeBinaryOutput1(_binaryOutputRegister.at(b));
3079 }
3080 else if (b == 1)
3081 {
3082 _vs.writeBinaryOutput2(_binaryOutputRegister.at(b));
3083 }
3084 else if (b == 2)
3085 {
3086 _vs.writeBinaryOutput3(_binaryOutputRegister.at(b));
3087 }
3088
3089 for (auto& link : outputPins.at(b + 2).links)
3090 {
3091 if (auto* connectedPin = link.getConnectedPin())
3092 {
3093 outputPins.at(b + 2).recreateLink(*connectedPin);
3094 }
3095 }
3096 }
3097 catch (const std::exception& e)
3098 {
3099 LOG_ERROR("{}: Could not configure the binaryOutputRegister {}: {}", nameId(), b + 1, e.what());
3100 doDeinitialize();
3101 }
3102 }
3103 else
3104 {
3105 doDeinitialize();
3106 }
3107 }
3108
3109 if (!enabled)
3110 {
3111 ImGui::EndDisabled();
3112 }
3113 };
3114
3115 for (size_t i = 0; i < 16; i++)
3116 {
3117 if (i < std::max({ /* _binaryGroupCommon.size(), */ _binaryGroupTime.size(), _binaryGroupIMU.size(),
3118 _binaryGroupGNSS.size(), _binaryGroupAttitude.size(), _binaryGroupINS.size() }))
3119 {
3120 ImGui::TableNextRow();
3121 }
3122 // if (i < _binaryGroupCommon.size())
3123 // {
3124 // const auto& binaryGroupItem = _binaryGroupCommon.at(i);
3125 // CheckboxFlags(0, fmt::format("{}##Common {} {}", binaryGroupItem.name, size_t(id), b).c_str(),
3126 // reinterpret_cast<int*>(&_binaryOutputRegister.at(b).commonField),
3127 // binaryGroupItem.flagsValue,
3128 // binaryGroupItem.isEnabled(_sensorModel, _binaryOutputRegister.at(b), static_cast<uint32_t>(_binaryOutputRegister.at(b).commonField)),
3129 // binaryGroupItem.toggleFields);
3130 // if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && binaryGroupItem.tooltip != nullptr)
3131 // {
3132 // ImGui::BeginTooltip();
3133 // binaryGroupItem.tooltip();
3134 // ImGui::EndTooltip();
3135 // }
3136 // }
3137 if (i < _binaryGroupTime.size())
3138 {
3139 const auto& binaryGroupItem = _binaryGroupTime.at(i);
3140 CheckboxFlags(0, fmt::format("{}##Time {} {}", binaryGroupItem.name, size_t(id), b).c_str(),
3141 reinterpret_cast<int*>(&_binaryOutputRegister.at(b).timeField),
3142 binaryGroupItem.flagsValue,
3143 binaryGroupItem.isEnabled(_sensorModel, _binaryOutputRegister.at(b), static_cast<uint32_t>(_binaryOutputRegister.at(b).timeField)),
3144 binaryGroupItem.toggleFields);
3145 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && binaryGroupItem.tooltip != nullptr)
3146 {
3147 ImGui::BeginTooltip();
3148 binaryGroupItem.tooltip();
3149 ImGui::EndTooltip();
3150 }
3151 }
3152 if (i < _binaryGroupIMU.size())
3153 {
3154 const auto& binaryGroupItem = _binaryGroupIMU.at(i);
3155 CheckboxFlags(1, fmt::format("{}##IMU {} {}", binaryGroupItem.name, size_t(id), b).c_str(),
3156 reinterpret_cast<int*>(&_binaryOutputRegister.at(b).imuField),
3157 binaryGroupItem.flagsValue,
3158 binaryGroupItem.isEnabled(_sensorModel, _binaryOutputRegister.at(b), static_cast<uint32_t>(_binaryOutputRegister.at(b).imuField)),
3159 binaryGroupItem.toggleFields);
3160 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && binaryGroupItem.tooltip != nullptr)
3161 {
3162 ImGui::BeginTooltip();
3163 binaryGroupItem.tooltip();
3164 ImGui::EndTooltip();
3165 }
3166 }
3167 if (i < _binaryGroupGNSS.size())
3168 {
3169 const auto& binaryGroupItem = _binaryGroupGNSS.at(i);
3170 CheckboxFlags(2, fmt::format("{}##GNSS1 {} {}", binaryGroupItem.name, size_t(id), b).c_str(),
3171 reinterpret_cast<int*>(&_binaryOutputRegister.at(b).gpsField),
3172 binaryGroupItem.flagsValue,
3173 binaryGroupItem.isEnabled(_sensorModel, _binaryOutputRegister.at(b), static_cast<uint32_t>(_binaryOutputRegister.at(b).gpsField)),
3174 binaryGroupItem.toggleFields);
3175 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && binaryGroupItem.tooltip != nullptr)
3176 {
3177 ImGui::BeginTooltip();
3178 binaryGroupItem.tooltip();
3179 ImGui::EndTooltip();
3180 }
3181 }
3182 if (i < _binaryGroupAttitude.size())
3183 {
3184 const auto& binaryGroupItem = _binaryGroupAttitude.at(i);
3185 CheckboxFlags(3, fmt::format("{}##Attitude {} {}", binaryGroupItem.name, size_t(id), b).c_str(),
3186 reinterpret_cast<int*>(&_binaryOutputRegister.at(b).attitudeField),
3187 binaryGroupItem.flagsValue,
3188 binaryGroupItem.isEnabled(_sensorModel, _binaryOutputRegister.at(b), static_cast<uint32_t>(_binaryOutputRegister.at(b).attitudeField)),
3189 binaryGroupItem.toggleFields);
3190 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && binaryGroupItem.tooltip != nullptr)
3191 {
3192 ImGui::BeginTooltip();
3193 binaryGroupItem.tooltip();
3194 ImGui::EndTooltip();
3195 }
3196 }
3197 if (i < _binaryGroupINS.size())
3198 {
3199 const auto& binaryGroupItem = _binaryGroupINS.at(i);
3200 CheckboxFlags(4, fmt::format("{}##INS {} {}", binaryGroupItem.name, size_t(id), b).c_str(),
3201 reinterpret_cast<int*>(&_binaryOutputRegister.at(b).insField),
3202 binaryGroupItem.flagsValue,
3203 binaryGroupItem.isEnabled(_sensorModel, _binaryOutputRegister.at(b), static_cast<uint32_t>(_binaryOutputRegister.at(b).insField)),
3204 binaryGroupItem.toggleFields);
3205 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && binaryGroupItem.tooltip != nullptr)
3206 {
3207 ImGui::BeginTooltip();
3208 binaryGroupItem.tooltip();
3209 ImGui::EndTooltip();
3210 }
3211 }
3212 if (i < _binaryGroupGNSS.size())
3213 {
3214 const auto& binaryGroupItem = _binaryGroupGNSS.at(i);
3215 CheckboxFlags(5, fmt::format("{}##GNSS2 {} {}", binaryGroupItem.name, size_t(id), b).c_str(),
3216 reinterpret_cast<int*>(&_binaryOutputRegister.at(b).gps2Field),
3217 binaryGroupItem.flagsValue,
3218 binaryGroupItem.isEnabled(_sensorModel, _binaryOutputRegister.at(b), static_cast<uint32_t>(_binaryOutputRegister.at(b).gps2Field)),
3219 binaryGroupItem.toggleFields);
3220 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && binaryGroupItem.tooltip != nullptr)
3221 {
3222 ImGui::BeginTooltip();
3223 binaryGroupItem.tooltip();
3224 ImGui::EndTooltip();
3225 }
3226 }
3227 }
3228
3229 ImGui::EndTable();
3230 }
3231
3232 ImGui::TreePop();
3233 }
3234 }
3235
3236 if (auto binaryOutputRegisterMerge = static_cast<int>(_binaryOutputRegisterMerge);
3237 ImGui::Combo(fmt::format("Merge Binary outputs").c_str(), &binaryOutputRegisterMerge, "None\0"
3238 "1 <-> 2\0"
3239 "1 <-> 3\0"
3240 "2 <-> 3\0\0"))
3241 {
3242 _binaryOutputRegisterMerge = static_cast<decltype(_binaryOutputRegisterMerge)>(binaryOutputRegisterMerge);
3243 flow::ApplyChanges();
3244 switch (_binaryOutputRegisterMerge)
3245 {
3246 case BinaryRegisterMerge::Output1_Output2:
3247 _binaryOutputRegister.at(1).rateDivisor = _binaryOutputRegister.at(0).rateDivisor;
3248 _binaryOutputSelectedFrequency.at(1) = _binaryOutputSelectedFrequency.at(0);
3249 _binaryOutputRegister.at(1).asyncMode = _binaryOutputRegister.at(0).asyncMode;
3250 outputPins.at(2).deleteLinks();
3251 outputPins.at(1).type = Pin::Type::Flow;
3252 outputPins.at(2).type = Pin::Type::None;
3253 outputPins.at(3).type = Pin::Type::Flow;
3254 break;
3255 case BinaryRegisterMerge::Output1_Output3:
3256 _binaryOutputRegister.at(2).rateDivisor = _binaryOutputRegister.at(0).rateDivisor;
3257 _binaryOutputSelectedFrequency.at(2) = _binaryOutputSelectedFrequency.at(0);
3258 _binaryOutputRegister.at(2).asyncMode = _binaryOutputRegister.at(0).asyncMode;
3259 outputPins.at(3).deleteLinks();
3260 outputPins.at(1).type = Pin::Type::Flow;
3261 outputPins.at(2).type = Pin::Type::Flow;
3262 outputPins.at(3).type = Pin::Type::None;
3263 break;
3264 case BinaryRegisterMerge::Output2_Output3:
3265 _binaryOutputRegister.at(2).rateDivisor = _binaryOutputRegister.at(1).rateDivisor;
3266 _binaryOutputSelectedFrequency.at(2) = _binaryOutputSelectedFrequency.at(1);
3267 _binaryOutputRegister.at(2).asyncMode = _binaryOutputRegister.at(1).asyncMode;
3268 outputPins.at(3).deleteLinks();
3269 outputPins.at(1).type = Pin::Type::Flow;
3270 outputPins.at(2).type = Pin::Type::Flow;
3271 outputPins.at(3).type = Pin::Type::None;
3272 break;
3273 default:
3274 break;
3275 }
3276 }
3277 ImGui::SameLine();
3278 NAV::gui::widgets::HelpMarker("VectorNav sensors have a buffer overflow if packages get too big (SatInfo/RawMeas selected and a lot of satellites). "
3279 "A workaround is, to split the data into different Binary Outputs. "
3280 "This option merges the outputs into a single observation.");
3281
3282 // TODO: Add Gui Config for NMEA output - User manual VN-310 - 8.2.14 (p 103)
3283 }
3284
3285 // ###########################################################################################################
3286 // IMU SUBSYSTEM
3287 // ###########################################################################################################
3288
3289 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
3290 if (ImGui::CollapsingHeader(fmt::format("IMU Subsystem##{}", size_t(id)).c_str()))
3291 {
3292 if (ImGui::TreeNode(fmt::format("Reference Frame Rotation##{}", size_t(id)).c_str()))
3293 {
3294 ImGui::TextUnformatted("Allows the measurements of the VN-310E to be rotated into a different reference frame.");
3295 ImGui::SameLine();
3296 gui::widgets::HelpMarker("This register contains a transformation matrix that allows for the transformation of measured acceleration, "
3297 "magnetic, and angular rates from the body frame of the VN-310E to any other arbitrary frame of reference. "
3298 "The use of this register allows for the sensor to be placed in any arbitrary orientation with respect to the "
3299 "user's desired body coordinate frame. This register can also be used to correct for any orientation errors due "
3300 "to mounting the VN-310E on the user's vehicle or platform.\n\n"
3301 "(X Y Z)_U = C * (X Y Z)_B\n\n"
3302 "The variables (X Y Z)_B are a measured parameter such as acceleration in the body reference frame with "
3303 "respect to the VectorNav. The variables (X Y Z)_U are a measured parameter such as acceleration in the user's "
3304 "frame of reference. The reference frame rotation register Thus needs to be loaded with the transformation "
3305 "matrix that will transform measurements from the body reference frame of the VectorNav to the desired user "
3306 "frame of reference.");
3307 if (_sensorModel == VectorNavModel::VN310)
3308 {
3309 ImGui::SameLine();
3310 gui::widgets::HelpMarker("The reference frame rotation is performed on all vector measurements prior to entering the INS "
3311 "filter. As such, changing this register while the attitude filter is running will lead to unexpected "
3312 "behavior in the INS output. To prevent this, the register is cached on startup and changes will "
3313 "not take effect during runtime. After setting the reference frame rotation register to its new value, "
3314 "send a write settings command and then reset the VN-310E. This will allow the INS filter to "
3315 "startup with the newly set reference frame rotation.",
3316 "(!)");
3317 }
3318 ImGui::SameLine();
3319 gui::widgets::HelpMarker("The matrix C in the Reference Frame Rotation Register must be an orthonormal, right-handed"
3320 " matrix. The sensor will output an error if the tolerance is not within 1e-5. The sensor will also"
3321 " report an error if any of the parameters are greater than 1 or less than -1.",
3322 "(!)");
3323
3324 ImGui::TextUnformatted("Rotation Angles [deg]");
3325 ImGui::SameLine();
3326 gui::widgets::HelpMarker("The rotation happens in ZYX Order");
3327 ImGui::Indent();
3328
3329 Eigen::Matrix3d dcm;
3330 dcm << _referenceFrameRotationMatrix.e00, _referenceFrameRotationMatrix.e01, _referenceFrameRotationMatrix.e02,
3331 _referenceFrameRotationMatrix.e10, _referenceFrameRotationMatrix.e11, _referenceFrameRotationMatrix.e12,
3332 _referenceFrameRotationMatrix.e20, _referenceFrameRotationMatrix.e21, _referenceFrameRotationMatrix.e22;
3333 auto b_Quat_p = Eigen::Quaterniond(dcm);
3334
3335 Eigen::Vector3d eulerRot = rad2deg(trafo::quat2eulerZYX(b_Quat_p.conjugate()));
3336 std::array<float, 3> imuRot = { static_cast<float>(eulerRot.x()), static_cast<float>(eulerRot.y()), static_cast<float>(eulerRot.z()) };
3337 if (ImGui::InputFloat3(fmt::format("##rotationAngles{}", size_t(id)).c_str(), imuRot.data()))
3338 {
3339 // (-180:180] x (-90:90] x (-180:180]
3340 imuRot.at(0) = std::max(imuRot.at(0), -179.9999F);
3341 imuRot.at(0) = std::min(imuRot.at(0), 180.0F);
3342 imuRot.at(1) = std::max(imuRot.at(1), -89.9999F);
3343 imuRot.at(1) = std::min(imuRot.at(1), 90.0F);
3344 imuRot.at(2) = std::max(imuRot.at(2), -179.9999F);
3345 imuRot.at(2) = std::min(imuRot.at(2), 180.0F);
3346
3347 Eigen::Matrix3f dcmf = trafo::b_Quat_p(deg2rad(imuRot.at(0)), deg2rad(imuRot.at(1)), deg2rad(imuRot.at(2))).toRotationMatrix().cast<float>();
3348 _referenceFrameRotationMatrix = vn::math::mat3f(dcmf(0, 0), dcmf(0, 1), dcmf(0, 2),
3349 dcmf(1, 0), dcmf(1, 1), dcmf(1, 2),
3350 dcmf(2, 0), dcmf(2, 1), dcmf(2, 2));
3351 LOG_DEBUG("{}: referenceFrameRotationMatrix changed to {}", nameId(), _referenceFrameRotationMatrix);
3352 flow::ApplyChanges();
3353 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3354 {
3355 try
3356 {
3357 _vs.writeReferenceFrameRotation(_referenceFrameRotationMatrix);
3358 }
3359 catch (const std::exception& e)
3360 {
3361 LOG_ERROR("{}: Could not configure the referenceFrameRotationMatrix: {}", nameId(), e.what());
3362 doDeinitialize();
3363 }
3364 }
3365 else
3366 {
3367 doDeinitialize();
3368 }
3369 }
3370
3371 ImGui::Unindent();
3372 ImGui::TextUnformatted("Rotation Matrix C");
3373 ImGui::Indent();
3374
3375 std::array<float, 3> row = { _referenceFrameRotationMatrix.e00, _referenceFrameRotationMatrix.e01, _referenceFrameRotationMatrix.e02 };
3376 if (ImGui::InputFloat3(fmt::format("##referenceFrameRotationMatrix row 0 {}", size_t(id)).c_str(), row.data(), "%.2f"))
3377 {
3378 _referenceFrameRotationMatrix.e00 = row.at(0);
3379 _referenceFrameRotationMatrix.e01 = row.at(1);
3380 _referenceFrameRotationMatrix.e02 = row.at(2);
3381 LOG_DEBUG("{}: referenceFrameRotationMatrix changed to {}", nameId(), _referenceFrameRotationMatrix);
3382 flow::ApplyChanges();
3383 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3384 {
3385 try
3386 {
3387 _vs.writeReferenceFrameRotation(_referenceFrameRotationMatrix);
3388 }
3389 catch (const std::exception& e)
3390 {
3391 LOG_ERROR("{}: Could not configure the referenceFrameRotationMatrix: {}", nameId(), e.what());
3392 doDeinitialize();
3393 }
3394 }
3395 else
3396 {
3397 doDeinitialize();
3398 }
3399 }
3400 row = { _referenceFrameRotationMatrix.e10, _referenceFrameRotationMatrix.e11, _referenceFrameRotationMatrix.e12 };
3401 if (ImGui::InputFloat3(fmt::format("##referenceFrameRotationMatrix row 1 {}", size_t(id)).c_str(), row.data(), "%.2f"))
3402 {
3403 _referenceFrameRotationMatrix.e10 = row.at(0);
3404 _referenceFrameRotationMatrix.e11 = row.at(1);
3405 _referenceFrameRotationMatrix.e12 = row.at(2);
3406 LOG_DEBUG("{}: referenceFrameRotationMatrix changed to {}", nameId(), _referenceFrameRotationMatrix);
3407 flow::ApplyChanges();
3408 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3409 {
3410 try
3411 {
3412 _vs.writeReferenceFrameRotation(_referenceFrameRotationMatrix);
3413 }
3414 catch (const std::exception& e)
3415 {
3416 LOG_ERROR("{}: Could not configure the referenceFrameRotationMatrix: {}", nameId(), e.what());
3417 doDeinitialize();
3418 }
3419 }
3420 else
3421 {
3422 doDeinitialize();
3423 }
3424 }
3425 row = { _referenceFrameRotationMatrix.e20, _referenceFrameRotationMatrix.e21, _referenceFrameRotationMatrix.e22 };
3426 if (ImGui::InputFloat3(fmt::format("##referenceFrameRotationMatrix row 2 {}", size_t(id)).c_str(), row.data(), "%.2f"))
3427 {
3428 _referenceFrameRotationMatrix.e20 = row.at(0);
3429 _referenceFrameRotationMatrix.e21 = row.at(1);
3430 _referenceFrameRotationMatrix.e22 = row.at(2);
3431 LOG_DEBUG("{}: referenceFrameRotationMatrix changed to {}", nameId(), _referenceFrameRotationMatrix);
3432 flow::ApplyChanges();
3433 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3434 {
3435 try
3436 {
3437 _vs.writeReferenceFrameRotation(_referenceFrameRotationMatrix);
3438 }
3439 catch (const std::exception& e)
3440 {
3441 LOG_ERROR("{}: Could not configure the referenceFrameRotationMatrix: {}", nameId(), e.what());
3442 doDeinitialize();
3443 }
3444 }
3445 else
3446 {
3447 doDeinitialize();
3448 }
3449 }
3450
3451 ImGui::Unindent();
3452 ImGui::TreePop();
3453 }
3454
3455 if (ImGui::TreeNode(fmt::format("IMU Filtering Configuration##{}", size_t(id)).c_str()))
3456 {
3457 ImGui::TextUnformatted("This register allows the user to configure the FIR filtering which is applied to the IMU measurements.\n"
3458 "The filter is a uniformly-weighted moving window (boxcar) filter, also known as a 'moving-average' filter.\n"
3459 "Its window size can be adjusted with respect to the internal IMU frequency (800 Hz).");
3460
3461 if (ImGui::Checkbox(fmt::format("Couple the Imu-Filter's rate to the output rate##{}", size_t(id)).c_str(), &_coupleImuRateOutput))
3462 {
3463 LOG_DEBUG("{}: coupleImuRateOutput changed to {}", nameId(), _coupleImuRateOutput);
3464 flow::ApplyChanges();
3465 }
3466 ImGui::SameLine();
3467 gui::widgets::HelpMarker("If the window-size of the IMU's moving-average filter is smaller than the output rate "
3468 "divisor, some samples are lost. This results in a low output rate that still contains "
3469 "noise due to high frequencies. Therefore, it is recommended to couple the ImuFilter's "
3470 "rate to the output rate.");
3471
3472 int magWindowSize = _imuFilteringConfigurationRegister.magWindowSize;
3473 if (_coupleImuRateOutput)
3474 {
3475 ImGui::BeginDisabled();
3476 }
3477 if (ImGui::InputInt(fmt::format("Mag Window Size##{}", size_t(id)).c_str(), &magWindowSize))
3478 {
3479 if (magWindowSize < 0)
3480 {
3481 magWindowSize = 0;
3482 }
3483 else if (magWindowSize > std::numeric_limits<uint16_t>::max())
3484 {
3485 magWindowSize = std::numeric_limits<uint16_t>::max();
3486 }
3487 _imuFilteringConfigurationRegister.magWindowSize = static_cast<uint16_t>(magWindowSize);
3488 LOG_DEBUG("{}: imuFilteringConfigurationRegister.magWindowSize changed to {}", nameId(), _imuFilteringConfigurationRegister.magWindowSize);
3489 flow::ApplyChanges();
3490 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3491 {
3492 try
3493 {
3494 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3495 }
3496 catch (const std::exception& e)
3497 {
3498 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3499 doDeinitialize();
3500 }
3501 }
3502 else
3503 {
3504 doDeinitialize();
3505 }
3506 }
3507 ImGui::SameLine();
3508 gui::widgets::HelpMarker("The WindowSize parameters for each sensor define the number of samples at the IMU rate (default 800Hz) "
3509 "which will be averaged for each output measurement.");
3510
3511 int accelWindowSize = _imuFilteringConfigurationRegister.accelWindowSize;
3512 if (ImGui::InputInt(fmt::format("Accel Window Size##{}", size_t(id)).c_str(), &accelWindowSize))
3513 {
3514 if (accelWindowSize < 0)
3515 {
3516 accelWindowSize = 0;
3517 }
3518 else if (accelWindowSize > std::numeric_limits<uint16_t>::max())
3519 {
3520 accelWindowSize = std::numeric_limits<uint16_t>::max();
3521 }
3522 _imuFilteringConfigurationRegister.accelWindowSize = static_cast<uint16_t>(accelWindowSize);
3523 LOG_DEBUG("{}: imuFilteringConfigurationRegister.accelWindowSize changed to {}", nameId(), _imuFilteringConfigurationRegister.accelWindowSize);
3524 flow::ApplyChanges();
3525 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3526 {
3527 try
3528 {
3529 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3530 }
3531 catch (const std::exception& e)
3532 {
3533 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3534 doDeinitialize();
3535 }
3536 }
3537 else
3538 {
3539 doDeinitialize();
3540 }
3541 }
3542 ImGui::SameLine();
3543 gui::widgets::HelpMarker("The WindowSize parameters for each sensor define the number of samples at the IMU rate (default 800Hz) "
3544 "which will be averaged for each output measurement.");
3545
3546 int gyroWindowSize = _imuFilteringConfigurationRegister.gyroWindowSize;
3547 if (ImGui::InputInt(fmt::format("Gyro Window Size##{}", size_t(id)).c_str(), &gyroWindowSize))
3548 {
3549 if (gyroWindowSize < 0)
3550 {
3551 gyroWindowSize = 0;
3552 }
3553 else if (gyroWindowSize > std::numeric_limits<uint16_t>::max())
3554 {
3555 gyroWindowSize = std::numeric_limits<uint16_t>::max();
3556 }
3557 _imuFilteringConfigurationRegister.gyroWindowSize = static_cast<uint16_t>(gyroWindowSize);
3558 LOG_DEBUG("{}: imuFilteringConfigurationRegister.gyroWindowSize changed to {}", nameId(), _imuFilteringConfigurationRegister.gyroWindowSize);
3559 flow::ApplyChanges();
3560 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3561 {
3562 try
3563 {
3564 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3565 }
3566 catch (const std::exception& e)
3567 {
3568 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3569 doDeinitialize();
3570 }
3571 }
3572 else
3573 {
3574 doDeinitialize();
3575 }
3576 }
3577 ImGui::SameLine();
3578 gui::widgets::HelpMarker("The WindowSize parameters for each sensor define the number of samples at the IMU rate (default 800Hz) "
3579 "which will be averaged for each output measurement.");
3580
3581 int tempWindowSize = _imuFilteringConfigurationRegister.tempWindowSize;
3582 if (ImGui::InputInt(fmt::format("Temp Window Size##{}", size_t(id)).c_str(), &tempWindowSize))
3583 {
3584 if (tempWindowSize < 0)
3585 {
3586 tempWindowSize = 0;
3587 }
3588 else if (tempWindowSize > std::numeric_limits<uint16_t>::max())
3589 {
3590 tempWindowSize = std::numeric_limits<uint16_t>::max();
3591 }
3592 _imuFilteringConfigurationRegister.tempWindowSize = static_cast<uint16_t>(tempWindowSize);
3593 LOG_DEBUG("{}: imuFilteringConfigurationRegister.tempWindowSize changed to {}", nameId(), _imuFilteringConfigurationRegister.tempWindowSize);
3594 flow::ApplyChanges();
3595 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3596 {
3597 try
3598 {
3599 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3600 }
3601 catch (const std::exception& e)
3602 {
3603 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3604 doDeinitialize();
3605 }
3606 }
3607 else
3608 {
3609 doDeinitialize();
3610 }
3611 }
3612 ImGui::SameLine();
3613 gui::widgets::HelpMarker("The WindowSize parameters for each sensor define the number of samples at the IMU rate (default 800Hz) "
3614 "which will be averaged for each output measurement.");
3615
3616 int presWindowSize = _imuFilteringConfigurationRegister.presWindowSize;
3617 if (ImGui::InputInt(fmt::format("Pres Window Size##{}", size_t(id)).c_str(), &presWindowSize))
3618 {
3619 if (presWindowSize < 0)
3620 {
3621 presWindowSize = 0;
3622 }
3623 else if (presWindowSize > std::numeric_limits<uint16_t>::max())
3624 {
3625 presWindowSize = std::numeric_limits<uint16_t>::max();
3626 }
3627 _imuFilteringConfigurationRegister.presWindowSize = static_cast<uint16_t>(presWindowSize);
3628 LOG_DEBUG("{}: imuFilteringConfigurationRegister.presWindowSize changed to {}", nameId(), _imuFilteringConfigurationRegister.presWindowSize);
3629 flow::ApplyChanges();
3630 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3631 {
3632 try
3633 {
3634 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3635 }
3636 catch (const std::exception& e)
3637 {
3638 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3639 doDeinitialize();
3640 }
3641 }
3642 else
3643 {
3644 doDeinitialize();
3645 }
3646 }
3647 ImGui::SameLine();
3648 gui::widgets::HelpMarker("The WindowSize parameters for each sensor define the number of samples at the IMU rate (default 800Hz) "
3649 "which will be averaged for each output measurement.");
3650 if (_coupleImuRateOutput)
3651 {
3652 ImGui::EndDisabled();
3653 }
3654
3655 static constexpr std::array<std::pair<vn::protocol::uart::FilterMode, const char*>, 4> imuFilteringConfigurationFilterModes = {
3656 { { vn::protocol::uart::FilterMode::FILTERMODE_NOFILTERING, "No Filtering" },
3657 { vn::protocol::uart::FilterMode::FILTERMODE_ONLYRAW, "Filtering performed only on raw uncompensated IMU measurements." },
3658 { vn::protocol::uart::FilterMode::FILTERMODE_ONLYCOMPENSATED, "Filtering performed only on compensated IMU measurements." },
3659 { vn::protocol::uart::FilterMode::FILTERMODE_BOTH, "Filtering performed on both uncompensated and compensated IMU measurements." } }
3660 };
3661 if (ImGui::BeginCombo(fmt::format("Mag Filter Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.magFilterMode).c_str()))
3662 {
3663 for (const auto& imuFilteringConfigurationFilterMode : imuFilteringConfigurationFilterModes)
3664 {
3665 const bool isSelected = (_imuFilteringConfigurationRegister.magFilterMode == imuFilteringConfigurationFilterMode.first);
3666 if (ImGui::Selectable(vn::protocol::uart::str(imuFilteringConfigurationFilterMode.first).c_str(), isSelected))
3667 {
3668 _imuFilteringConfigurationRegister.magFilterMode = imuFilteringConfigurationFilterMode.first;
3669 LOG_DEBUG("{}: imuFilteringConfigurationRegister.magFilterMode changed to {}", nameId(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.magFilterMode));
3670 flow::ApplyChanges();
3671 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3672 {
3673 try
3674 {
3675 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3676 }
3677 catch (const std::exception& e)
3678 {
3679 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3680 doDeinitialize();
3681 }
3682 }
3683 else
3684 {
3685 doDeinitialize();
3686 }
3687 }
3688 if (ImGui::IsItemHovered())
3689 {
3690 ImGui::BeginTooltip();
3691 ImGui::TextUnformatted(imuFilteringConfigurationFilterMode.second);
3692 ImGui::EndTooltip();
3693 }
3694
3695 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
3696 {
3697 ImGui::SetItemDefaultFocus();
3698 }
3699 }
3700 ImGui::EndCombo();
3701 }
3702 ImGui::SameLine();
3703 gui::widgets::HelpMarker("The FilterMode parameters for each sensor select which output quantities the filtering should be applied to. "
3704 "Filtering can be applied to either the uncompensated IMU measurements, compensated (HSI and biases "
3705 "compensated by onboard filters, if applicable), or both.");
3706
3707 if (ImGui::BeginCombo(fmt::format("Accel Filter Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.accelFilterMode).c_str()))
3708 {
3709 for (const auto& imuFilteringConfigurationFilterMode : imuFilteringConfigurationFilterModes)
3710 {
3711 const bool isSelected = (_imuFilteringConfigurationRegister.accelFilterMode == imuFilteringConfigurationFilterMode.first);
3712 if (ImGui::Selectable(vn::protocol::uart::str(imuFilteringConfigurationFilterMode.first).c_str(), isSelected))
3713 {
3714 _imuFilteringConfigurationRegister.accelFilterMode = imuFilteringConfigurationFilterMode.first;
3715 LOG_DEBUG("{}: imuFilteringConfigurationRegister.accelFilterMode changed to {}", nameId(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.accelFilterMode));
3716 flow::ApplyChanges();
3717 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3718 {
3719 try
3720 {
3721 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3722 }
3723 catch (const std::exception& e)
3724 {
3725 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3726 doDeinitialize();
3727 }
3728 }
3729 else
3730 {
3731 doDeinitialize();
3732 }
3733 }
3734 if (ImGui::IsItemHovered())
3735 {
3736 ImGui::BeginTooltip();
3737 ImGui::TextUnformatted(imuFilteringConfigurationFilterMode.second);
3738 ImGui::EndTooltip();
3739 }
3740
3741 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
3742 {
3743 ImGui::SetItemDefaultFocus();
3744 }
3745 }
3746 ImGui::EndCombo();
3747 }
3748 ImGui::SameLine();
3749 gui::widgets::HelpMarker("The FilterMode parameters for each sensor select which output quantities the filtering should be applied to. "
3750 "Filtering can be applied to either the uncompensated IMU measurements, compensated (HSI and biases "
3751 "compensated by onboard filters, if applicable), or both.");
3752
3753 if (ImGui::BeginCombo(fmt::format("Gyro Filter Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.gyroFilterMode).c_str()))
3754 {
3755 for (const auto& imuFilteringConfigurationFilterMode : imuFilteringConfigurationFilterModes)
3756 {
3757 const bool isSelected = (_imuFilteringConfigurationRegister.gyroFilterMode == imuFilteringConfigurationFilterMode.first);
3758 if (ImGui::Selectable(vn::protocol::uart::str(imuFilteringConfigurationFilterMode.first).c_str(), isSelected))
3759 {
3760 _imuFilteringConfigurationRegister.gyroFilterMode = imuFilteringConfigurationFilterMode.first;
3761 LOG_DEBUG("{}: imuFilteringConfigurationRegister.gyroFilterMode changed to {}", nameId(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.gyroFilterMode));
3762 flow::ApplyChanges();
3763 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3764 {
3765 try
3766 {
3767 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3768 }
3769 catch (const std::exception& e)
3770 {
3771 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3772 doDeinitialize();
3773 }
3774 }
3775 else
3776 {
3777 doDeinitialize();
3778 }
3779 }
3780 if (ImGui::IsItemHovered())
3781 {
3782 ImGui::BeginTooltip();
3783 ImGui::TextUnformatted(imuFilteringConfigurationFilterMode.second);
3784 ImGui::EndTooltip();
3785 }
3786
3787 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
3788 {
3789 ImGui::SetItemDefaultFocus();
3790 }
3791 }
3792 ImGui::EndCombo();
3793 }
3794 ImGui::SameLine();
3795 gui::widgets::HelpMarker("The FilterMode parameters for each sensor select which output quantities the filtering should be applied to. "
3796 "Filtering can be applied to either the uncompensated IMU measurements, compensated (HSI and biases "
3797 "compensated by onboard filters, if applicable), or both.");
3798
3799 if (ImGui::BeginCombo(fmt::format("Temp Filter Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.tempFilterMode).c_str()))
3800 {
3801 for (const auto& imuFilteringConfigurationFilterMode : imuFilteringConfigurationFilterModes)
3802 {
3803 const bool isSelected = (_imuFilteringConfigurationRegister.tempFilterMode == imuFilteringConfigurationFilterMode.first);
3804 if (ImGui::Selectable(vn::protocol::uart::str(imuFilteringConfigurationFilterMode.first).c_str(), isSelected))
3805 {
3806 _imuFilteringConfigurationRegister.tempFilterMode = imuFilteringConfigurationFilterMode.first;
3807 LOG_DEBUG("{}: imuFilteringConfigurationRegister.tempFilterMode changed to {}", nameId(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.tempFilterMode));
3808 flow::ApplyChanges();
3809 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3810 {
3811 try
3812 {
3813 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3814 }
3815 catch (const std::exception& e)
3816 {
3817 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3818 doDeinitialize();
3819 }
3820 }
3821 else
3822 {
3823 doDeinitialize();
3824 }
3825 }
3826 if (ImGui::IsItemHovered())
3827 {
3828 ImGui::BeginTooltip();
3829 ImGui::TextUnformatted(imuFilteringConfigurationFilterMode.second);
3830 ImGui::EndTooltip();
3831 }
3832
3833 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
3834 {
3835 ImGui::SetItemDefaultFocus();
3836 }
3837 }
3838 ImGui::EndCombo();
3839 }
3840 ImGui::SameLine();
3841 gui::widgets::HelpMarker("The FilterMode parameters for each sensor select which output quantities the filtering should be applied to. "
3842 "Filtering can be applied to either the uncompensated IMU measurements, compensated (HSI and biases "
3843 "compensated by onboard filters, if applicable), or both.");
3844
3845 if (ImGui::BeginCombo(fmt::format("Pres Filter Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.presFilterMode).c_str()))
3846 {
3847 for (const auto& imuFilteringConfigurationFilterMode : imuFilteringConfigurationFilterModes)
3848 {
3849 const bool isSelected = (_imuFilteringConfigurationRegister.presFilterMode == imuFilteringConfigurationFilterMode.first);
3850 if (ImGui::Selectable(vn::protocol::uart::str(imuFilteringConfigurationFilterMode.first).c_str(), isSelected))
3851 {
3852 _imuFilteringConfigurationRegister.presFilterMode = imuFilteringConfigurationFilterMode.first;
3853 LOG_DEBUG("{}: imuFilteringConfigurationRegister.presFilterMode changed to {}", nameId(), vn::protocol::uart::str(_imuFilteringConfigurationRegister.presFilterMode));
3854 flow::ApplyChanges();
3855 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3856 {
3857 try
3858 {
3859 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
3860 }
3861 catch (const std::exception& e)
3862 {
3863 LOG_ERROR("{}: Could not configure the imuFilteringConfigurationRegister: {}", nameId(), e.what());
3864 doDeinitialize();
3865 }
3866 }
3867 else
3868 {
3869 doDeinitialize();
3870 }
3871 }
3872 if (ImGui::IsItemHovered())
3873 {
3874 ImGui::BeginTooltip();
3875 ImGui::TextUnformatted(imuFilteringConfigurationFilterMode.second);
3876 ImGui::EndTooltip();
3877 }
3878
3879 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
3880 {
3881 ImGui::SetItemDefaultFocus();
3882 }
3883 }
3884 ImGui::EndCombo();
3885 }
3886 ImGui::SameLine();
3887 gui::widgets::HelpMarker("The FilterMode parameters for each sensor select which output quantities the filtering should be applied to. "
3888 "Filtering can be applied to either the uncompensated IMU measurements, compensated (HSI and biases "
3889 "compensated by onboard filters, if applicable), or both.");
3890
3891 ImGui::TreePop();
3892 }
3893
3894 if (ImGui::TreeNode(fmt::format("Delta Theta and Delta Velocity Configuration##{}", size_t(id)).c_str()))
3895 {
3896 ImGui::TextUnformatted("The Delta Theta and Delta Velocity Configuration register allows configuration of the onboard coning and\n"
3897 "sculling used to generate integrated motion values from the angular rate and acceleration IMU quantities.\n"
3898 "The fully-coupled coning and sculling integrals are computed at the IMU sample rate (nominal 400 Hz).");
3899
3900 static constexpr std::array<std::pair<vn::protocol::uart::IntegrationFrame, const char*>, 2> deltaThetaAndDeltaVelocityConfigurationIntegrationFrames = {
3901 { { vn::protocol::uart::IntegrationFrame::INTEGRATIONFRAME_BODY, "Body frame" },
3902 { vn::protocol::uart::IntegrationFrame::INTEGRATIONFRAME_NED, "NED frame" } }
3903 };
3904 if (ImGui::BeginCombo(fmt::format("Integration Frame##{}", size_t(id)).c_str(), vn::protocol::uart::str(_deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame).c_str()))
3905 {
3906 for (const auto& deltaThetaAndDeltaVelocityConfigurationIntegrationFrame : deltaThetaAndDeltaVelocityConfigurationIntegrationFrames)
3907 {
3908 const bool isSelected = (_deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame == deltaThetaAndDeltaVelocityConfigurationIntegrationFrame.first);
3909 if (ImGui::Selectable(vn::protocol::uart::str(deltaThetaAndDeltaVelocityConfigurationIntegrationFrame.first).c_str(), isSelected))
3910 {
3911 _deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame = deltaThetaAndDeltaVelocityConfigurationIntegrationFrame.first;
3912 LOG_DEBUG("{}: deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame changed to {}", nameId(), vn::protocol::uart::str(_deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame));
3913 flow::ApplyChanges();
3914 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3915 {
3916 try
3917 {
3918 _vs.writeDeltaThetaAndDeltaVelocityConfiguration(_deltaThetaAndDeltaVelocityConfigurationRegister);
3919 }
3920 catch (const std::exception& e)
3921 {
3922 LOG_ERROR("{}: Could not configure the deltaThetaAndDeltaVelocityConfigurationRegister: {}", nameId(), e.what());
3923 doDeinitialize();
3924 }
3925 }
3926 else
3927 {
3928 doDeinitialize();
3929 }
3930 }
3931 if (ImGui::IsItemHovered())
3932 {
3933 ImGui::BeginTooltip();
3934 ImGui::TextUnformatted(deltaThetaAndDeltaVelocityConfigurationIntegrationFrame.second);
3935 ImGui::EndTooltip();
3936 }
3937
3938 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
3939 {
3940 ImGui::SetItemDefaultFocus();
3941 }
3942 }
3943 ImGui::EndCombo();
3944 }
3945 ImGui::SameLine();
3946 gui::widgets::HelpMarker("The IntegrationFrame register setting selects the reference frame used for coning and sculling. Note that "
3947 "using any frame other than the body frame will rely on the onboard Kalman filter's attitude estimate. The "
3948 "factory default state is to integrate in the sensor body frame.");
3949
3950 static constexpr std::array<std::pair<vn::protocol::uart::CompensationMode, const char*>, 2> deltaThetaAndDeltaVelocityConfigurationGyroCompensationModes = {
3951 { { vn::protocol::uart::CompensationMode::COMPENSATIONMODE_NONE, "None" },
3952 { vn::protocol::uart::CompensationMode::COMPENSATIONMODE_BIAS, "Bias" } }
3953 };
3954 if (ImGui::BeginCombo(fmt::format("Gyro Compensation##{}", size_t(id)).c_str(), vn::protocol::uart::str(_deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation).c_str()))
3955 {
3956 for (const auto& deltaThetaAndDeltaVelocityConfigurationGyroCompensationMode : deltaThetaAndDeltaVelocityConfigurationGyroCompensationModes)
3957 {
3958 const bool isSelected = (_deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation == deltaThetaAndDeltaVelocityConfigurationGyroCompensationMode.first);
3959 if (ImGui::Selectable(vn::protocol::uart::str(deltaThetaAndDeltaVelocityConfigurationGyroCompensationMode.first).c_str(), isSelected))
3960 {
3961 _deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation = deltaThetaAndDeltaVelocityConfigurationGyroCompensationMode.first;
3962 LOG_DEBUG("{}: deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation changed to {}", nameId(), vn::protocol::uart::str(_deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation));
3963 flow::ApplyChanges();
3964 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
3965 {
3966 try
3967 {
3968 _vs.writeDeltaThetaAndDeltaVelocityConfiguration(_deltaThetaAndDeltaVelocityConfigurationRegister);
3969 }
3970 catch (const std::exception& e)
3971 {
3972 LOG_ERROR("{}: Could not configure the deltaThetaAndDeltaVelocityConfigurationRegister: {}", nameId(), e.what());
3973 doDeinitialize();
3974 }
3975 }
3976 else
3977 {
3978 doDeinitialize();
3979 }
3980 }
3981 if (ImGui::IsItemHovered())
3982 {
3983 ImGui::BeginTooltip();
3984 ImGui::TextUnformatted(deltaThetaAndDeltaVelocityConfigurationGyroCompensationMode.second);
3985 ImGui::EndTooltip();
3986 }
3987
3988 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
3989 {
3990 ImGui::SetItemDefaultFocus();
3991 }
3992 }
3993 ImGui::EndCombo();
3994 }
3995 ImGui::SameLine();
3996 gui::widgets::HelpMarker("The GyroCompensation register setting selects the compensation to be applied to the angular rate "
3997 "measurements before integration. If bias compensation is selected, the onboard Kalman filter’s real-time "
3998 "estimate of the gyro biases will be used to compensate the IMU measurements before integration. The "
3999 "factory default state is to integrate the uncompensated angular rates from the IMU.");
4000
4001 static constexpr std::array<std::pair<vn::protocol::uart::AccCompensationMode, const char*>, 2> deltaThetaAndDeltaVelocityConfigurationAccelCompensationModes = {
4002 { { vn::protocol::uart::AccCompensationMode::ACCCOMPENSATIONMODE_NONE, "None" },
4003 { vn::protocol::uart::AccCompensationMode::ACCCOMPENSATIONMODE_BIAS, "Bias" } }
4004 };
4005 if (ImGui::BeginCombo(fmt::format("Accel Compensation##{}", size_t(id)).c_str(), vn::protocol::uart::str(_deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation).c_str()))
4006 {
4007 for (const auto& deltaThetaAndDeltaVelocityConfigurationAccelCompensationMode : deltaThetaAndDeltaVelocityConfigurationAccelCompensationModes)
4008 {
4009 const bool isSelected = (_deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation == deltaThetaAndDeltaVelocityConfigurationAccelCompensationMode.first);
4010 if (ImGui::Selectable(vn::protocol::uart::str(deltaThetaAndDeltaVelocityConfigurationAccelCompensationMode.first).c_str(), isSelected))
4011 {
4012 _deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation = deltaThetaAndDeltaVelocityConfigurationAccelCompensationMode.first;
4013 LOG_DEBUG("{}: deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation changed to {}", nameId(), vn::protocol::uart::str(_deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation));
4014 flow::ApplyChanges();
4015 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4016 {
4017 try
4018 {
4019 _vs.writeDeltaThetaAndDeltaVelocityConfiguration(_deltaThetaAndDeltaVelocityConfigurationRegister);
4020 }
4021 catch (const std::exception& e)
4022 {
4023 LOG_ERROR("{}: Could not configure the deltaThetaAndDeltaVelocityConfigurationRegister: {}", nameId(), e.what());
4024 doDeinitialize();
4025 }
4026 }
4027 else
4028 {
4029 doDeinitialize();
4030 }
4031 }
4032 if (ImGui::IsItemHovered())
4033 {
4034 ImGui::BeginTooltip();
4035 ImGui::TextUnformatted(deltaThetaAndDeltaVelocityConfigurationAccelCompensationMode.second);
4036 ImGui::EndTooltip();
4037 }
4038
4039 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4040 {
4041 ImGui::SetItemDefaultFocus();
4042 }
4043 }
4044 ImGui::EndCombo();
4045 }
4046 ImGui::SameLine();
4047 gui::widgets::HelpMarker("The AccelCompensation register setting selects the compensation to be applied to the acceleration "
4048 "measurements before integration. If bias compensation is selected, the onboard Kalman filter’s real-time "
4049 "estimate of the accel biases will be used to compensate the IMU measurements before integration. The "
4050 "factory default state is to integrate the uncompensated acceleration from the IMU.");
4051
4052 ImGui::TreePop();
4053 }
4054 }
4055
4056 // ###########################################################################################################
4057 // GNSS SUBSYSTEM
4058 // ###########################################################################################################
4059
4060 if (_sensorModel == VectorNavModel::VN310)
4061 {
4062 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
4063 if (ImGui::CollapsingHeader(fmt::format("GNSS Subsystem##{}", size_t(id)).c_str()))
4064 {
4065 if (ImGui::TreeNode(fmt::format("GNSS Configuration##{}", size_t(id)).c_str()))
4066 {
4067 static constexpr std::array<std::pair<vn::protocol::uart::GpsMode, const char*>, 3> gpsConfigurationModes = {
4068 { { vn::protocol::uart::GpsMode::GPSMODE_ONBOARDGPS, "Use onboard GNSS" },
4069 { vn::protocol::uart::GpsMode::GPSMODE_EXTERNALGPS, "Use external GNSS" },
4070 { vn::protocol::uart::GpsMode::GPSMODE_EXTERNALVN200GPS, "Use external VectorNav sensor as the GNSS" } }
4071 };
4072 if (ImGui::BeginCombo(fmt::format("Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_gpsConfigurationRegister.mode).c_str()))
4073 {
4074 for (const auto& gpsConfigurationMode : gpsConfigurationModes)
4075 {
4076 const bool isSelected = (_gpsConfigurationRegister.mode == gpsConfigurationMode.first);
4077 if (ImGui::Selectable(vn::protocol::uart::str(gpsConfigurationMode.first).c_str(), isSelected))
4078 {
4079 _gpsConfigurationRegister.mode = gpsConfigurationMode.first;
4080 LOG_DEBUG("{}: gpsConfigurationRegister.mode changed to {}", nameId(), vn::protocol::uart::str(_gpsConfigurationRegister.mode));
4081 flow::ApplyChanges();
4082 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4083 {
4084 try
4085 {
4086 _vs.writeGpsConfiguration(_gpsConfigurationRegister);
4087 }
4088 catch (const std::exception& e)
4089 {
4090 LOG_ERROR("{}: Could not configure the gpsConfigurationRegister: {}", nameId(), e.what());
4091 doDeinitialize();
4092 }
4093 }
4094 else
4095 {
4096 doDeinitialize();
4097 }
4098 }
4099 if (ImGui::IsItemHovered())
4100 {
4101 ImGui::BeginTooltip();
4102 ImGui::TextUnformatted(gpsConfigurationMode.second);
4103 ImGui::EndTooltip();
4104 }
4105
4106 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4107 {
4108 ImGui::SetItemDefaultFocus();
4109 }
4110 }
4111 ImGui::EndCombo();
4112 }
4113
4114 static constexpr std::array<std::pair<vn::protocol::uart::PpsSource, const char*>, 4> gpsConfigurationPpsSources = {
4115 { { 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." },
4116 { 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" },
4117 { vn::protocol::uart::PpsSource::PPSSOURCE_SYNCINRISING, "GNSS PPS signal is present on the SyncIn pin (pin 22) and should trigger on a rising edge" },
4118 { vn::protocol::uart::PpsSource::PPSSOURCE_SYNCINFALLING, "GNSS PPS signal is present on the SyncIn pin (pin 22) and should trigger on a falling edge" } }
4119 };
4120 if (ImGui::BeginCombo(fmt::format("PPS Source##{}", size_t(id)).c_str(), vn::protocol::uart::str(_gpsConfigurationRegister.ppsSource).c_str()))
4121 {
4122 for (const auto& gpsConfigurationPpsSource : gpsConfigurationPpsSources)
4123 {
4124 const bool isSelected = (_gpsConfigurationRegister.ppsSource == gpsConfigurationPpsSource.first);
4125 if (ImGui::Selectable(vn::protocol::uart::str(gpsConfigurationPpsSource.first).c_str(), isSelected))
4126 {
4127 _gpsConfigurationRegister.ppsSource = gpsConfigurationPpsSource.first;
4128 LOG_DEBUG("{}: gpsConfigurationRegister.ppsSource changed to {}", nameId(), vn::protocol::uart::str(_gpsConfigurationRegister.ppsSource));
4129 flow::ApplyChanges();
4130 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4131 {
4132 try
4133 {
4134 _vs.writeGpsConfiguration(_gpsConfigurationRegister);
4135 }
4136 catch (const std::exception& e)
4137 {
4138 LOG_ERROR("{}: Could not configure the gpsConfigurationRegister: {}", nameId(), e.what());
4139 doDeinitialize();
4140 }
4141 }
4142 else
4143 {
4144 doDeinitialize();
4145 }
4146 }
4147 if (ImGui::IsItemHovered())
4148 {
4149 ImGui::BeginTooltip();
4150 ImGui::TextUnformatted(gpsConfigurationPpsSource.second);
4151 ImGui::EndTooltip();
4152 }
4153
4154 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4155 {
4156 ImGui::SetItemDefaultFocus();
4157 }
4158 }
4159 ImGui::EndCombo();
4160 }
4161
4162 static constexpr std::array<vn::protocol::uart::GpsRate, 1> gpsConfigurationRates = {
4163 { /* vn::protocol::uart::GpsRate::GPSRATE_1HZ, */
4164 vn::protocol::uart::GpsRate::GPSRATE_5HZ }
4165 };
4166 if (ImGui::BeginCombo(fmt::format("Rate##{}", size_t(id)).c_str(), vn::protocol::uart::str(_gpsConfigurationRegister.rate).c_str()))
4167 {
4168 for (const auto& gpsConfigurationRate : gpsConfigurationRates)
4169 {
4170 const bool isSelected = (_gpsConfigurationRegister.rate == gpsConfigurationRate);
4171 if (ImGui::Selectable(vn::protocol::uart::str(gpsConfigurationRate).c_str(), isSelected))
4172 {
4173 _gpsConfigurationRegister.rate = gpsConfigurationRate;
4174 LOG_DEBUG("{}: gpsConfigurationRegister.rate changed to {}", nameId(), vn::protocol::uart::str(_gpsConfigurationRegister.rate));
4175 flow::ApplyChanges();
4176 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4177 {
4178 try
4179 {
4180 _vs.writeGpsConfiguration(_gpsConfigurationRegister);
4181 }
4182 catch (const std::exception& e)
4183 {
4184 LOG_ERROR("{}: Could not configure the gpsConfigurationRegister: {}", nameId(), e.what());
4185 doDeinitialize();
4186 }
4187 }
4188 else
4189 {
4190 doDeinitialize();
4191 }
4192 }
4193
4194 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4195 {
4196 ImGui::SetItemDefaultFocus();
4197 }
4198 }
4199 ImGui::EndCombo();
4200 }
4201 ImGui::SameLine();
4202 gui::widgets::HelpMarker("GNSS navigation rate. Value must be set to 5.");
4203
4204 static constexpr std::array<std::pair<vn::protocol::uart::AntPower, const char*>, 3> gpsConfigurationAntPowers = {
4205 { { vn::protocol::uart::AntPower::ANTPOWER_OFFRESV, "Disable antenna power supply." },
4206 { vn::protocol::uart::AntPower::ANTPOWER_INTERNAL, "Use internal antenna power supply (3V, 50mA combined)." },
4207 { vn::protocol::uart::AntPower::ANTPOWER_EXTERNAL, "Use external antenna power supply (VANT pin, up to 5V and 100mA combined)" } }
4208 };
4209 if (ImGui::BeginCombo(fmt::format("Ant Power##{}", size_t(id)).c_str(), vn::protocol::uart::str(_gpsConfigurationRegister.antPow).c_str()))
4210 {
4211 for (const auto& gpsConfigurationAntPower : gpsConfigurationAntPowers)
4212 {
4213 const bool isSelected = (_gpsConfigurationRegister.antPow == gpsConfigurationAntPower.first);
4214 if (ImGui::Selectable(vn::protocol::uart::str(gpsConfigurationAntPower.first).c_str(), isSelected))
4215 {
4216 _gpsConfigurationRegister.antPow = gpsConfigurationAntPower.first;
4217 LOG_DEBUG("{}: gpsConfigurationRegister.antPow changed to {}", nameId(), vn::protocol::uart::str(_gpsConfigurationRegister.antPow));
4218 flow::ApplyChanges();
4219 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4220 {
4221 try
4222 {
4223 _vs.writeGpsConfiguration(_gpsConfigurationRegister);
4224 }
4225 catch (const std::exception& e)
4226 {
4227 LOG_ERROR("{}: Could not configure the gpsConfigurationRegister: {}", nameId(), e.what());
4228 doDeinitialize();
4229 }
4230 }
4231 else
4232 {
4233 doDeinitialize();
4234 }
4235 }
4236 if (ImGui::IsItemHovered())
4237 {
4238 ImGui::BeginTooltip();
4239 ImGui::TextUnformatted(gpsConfigurationAntPower.second);
4240 ImGui::EndTooltip();
4241 }
4242
4243 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4244 {
4245 ImGui::SetItemDefaultFocus();
4246 }
4247 }
4248 ImGui::EndCombo();
4249 }
4250 ImGui::SameLine();
4251 gui::widgets::HelpMarker("GNSS navigation rate. Value must be set to 5.");
4252
4253 ImGui::TreePop();
4254 }
4255
4256 if (ImGui::TreeNode(fmt::format("GNSS Antenna A Offset##{}", size_t(id)).c_str()))
4257 {
4258 ImGui::TextUnformatted("The position of the GNSS antenna A relative to the sensor in the vehicle coordinate frame\n"
4259 "also referred to as the GNSS antenna lever arm.");
4260
4261 if (ImGui::InputFloat3(fmt::format("##gpsAntennaOffset {}", size_t(id)).c_str(), _gpsAntennaOffset.c, "%.6f"))
4262 {
4263 LOG_DEBUG("{}: gpsAntennaOffset changed to {}", nameId(), _gpsAntennaOffset);
4264 flow::ApplyChanges();
4265 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4266 {
4267 try
4268 {
4269 _vs.writeGpsAntennaOffset(_gpsAntennaOffset);
4270 }
4271 catch (const std::exception& e)
4272 {
4273 LOG_ERROR("{}: Could not configure the gpsAntennaOffset: {}", nameId(), e.what());
4274 doDeinitialize();
4275 }
4276 }
4277 else
4278 {
4279 doDeinitialize();
4280 }
4281 }
4282
4283 ImGui::TreePop();
4284 }
4285
4286 if (ImGui::TreeNode(fmt::format("GNSS Compass Baseline##{}", size_t(id)).c_str()))
4287 {
4288 ImGui::TextUnformatted("Configures the position offset and measurement uncertainty of the second GNSS\n"
4289 "antenna relative to the first GNSS antenna in the vehicle reference frame.");
4290
4291 if (ImGui::InputFloat3(fmt::format("Position [m]##{}", size_t(id)).c_str(), _gpsCompassBaselineRegister.position.c, "%.6f"))
4292 {
4293 LOG_DEBUG("{}: gpsCompassBaselineRegister.position changed to {}", nameId(), _gpsCompassBaselineRegister.position);
4294 flow::ApplyChanges();
4295 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4296 {
4297 try
4298 {
4299 _vs.writeGpsCompassBaseline(_gpsCompassBaselineRegister);
4300 }
4301 catch (const std::exception& e)
4302 {
4303 LOG_ERROR("{}: Could not configure the gpsCompassBaselineRegister: {}", nameId(), e.what());
4304 doDeinitialize();
4305 }
4306 }
4307 else
4308 {
4309 doDeinitialize();
4310 }
4311 }
4312 ImGui::SameLine();
4313 gui::widgets::HelpMarker("HEADING ACCURACY\n\n"
4314 "The accuracy of the estimated heading is dependent upon the accuracy of the measured baseline "
4315 "between the two GNSS antennas. The factory default baseline is {1.0m, 0.0m, 0.0m}. If any other "
4316 "baseline is used, it is extremely important that the user acurately measures this baseline to ensure "
4317 "accurate heading estimates.\n"
4318 "The heading accuracy is linearly proportional to the measurement accuracy of the position of "
4319 "GNSS antenna B with respect to GNSS antenna A, and inversely proportional to the baseline "
4320 "length.\n\n"
4321 "Heading Error [deg] ~= 0.57 * (Baseline Error [cm]) / (Baseline Length [m])\n\n"
4322 "On a 1 meter baseline, a 1 cm measurement error equates to heading error of 0.6 degrees.",
4323 "(!)");
4324
4325 if (ImGui::InputFloat3(fmt::format("Uncertainty [m]##{}", size_t(id)).c_str(), _gpsCompassBaselineRegister.uncertainty.c, "%.3f"))
4326 {
4327 LOG_DEBUG("{}: gpsCompassBaselineRegister.uncertainty changed to {}", nameId(), _gpsCompassBaselineRegister.uncertainty);
4328 flow::ApplyChanges();
4329 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4330 {
4331 try
4332 {
4333 _vs.writeGpsCompassBaseline(_gpsCompassBaselineRegister);
4334 }
4335 catch (const std::exception& e)
4336 {
4337 LOG_ERROR("{}: Could not configure the gpsCompassBaselineRegister: {}", nameId(), e.what());
4338 doDeinitialize();
4339 }
4340 }
4341 else
4342 {
4343 doDeinitialize();
4344 }
4345 }
4346 ImGui::SameLine();
4347 gui::widgets::HelpMarker("MEASUREMENT UNCERTAINTY\n\n"
4348 "For the VN-310E to function properly it is very important that the user supplies a reasonable "
4349 "measurement uncertainty that is greater than the actual uncertainty in the baseline measurement. "
4350 "The VN-310E uses the uncertainty supplied by the user to validate measurements that it receives "
4351 "from the GNSS receivers. If the user inputs an uncertainty that is lower than the actual error in "
4352 "the baseline measurement between the two antennas, the VN-310E will no longer be able to derive "
4353 "heading estimates from the GNSS.\n\n"
4354 "It is recommended that you set the uncertainty equal to twice what you expect the worst case "
4355 "error to be in your baseline measurements. In many applications it is easier to measure more "
4356 "accurately in one direction than another. It is recommended that you set each of the X, Y, & Z "
4357 "uncertainties seperately to reflect this, as opposed to using a single large value.",
4358 "(!)");
4359
4360 ImGui::TreePop();
4361 }
4362 }
4363 }
4364
4365 // ###########################################################################################################
4366 // ATTITUDE SUBSYSTEM
4367 // ###########################################################################################################
4368
4369 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
4370 if (ImGui::CollapsingHeader(fmt::format("Attitude Subsystem##{}", size_t(id)).c_str()))
4371 {
4372 if (ImGui::TreeNode(fmt::format("VPE Basic Control##{}", size_t(id)).c_str()))
4373 {
4374 ImGui::TextUnformatted("Provides control over various features relating to the onboard attitude filtering algorithm.");
4375
4376 static constexpr std::array<vn::protocol::uart::VpeEnable, 2> vpeBasicControlEnables = {
4377 { vn::protocol::uart::VpeEnable::VPEENABLE_DISABLE,
4378 vn::protocol::uart::VpeEnable::VPEENABLE_ENABLE }
4379 };
4380 if (ImGui::BeginCombo(fmt::format("Enable##{}", size_t(id)).c_str(), vn::protocol::uart::str(_vpeBasicControlRegister.enable).c_str()))
4381 {
4382 for (const auto& vpeBasicControlEnable : vpeBasicControlEnables)
4383 {
4384 const bool isSelected = (_vpeBasicControlRegister.enable == vpeBasicControlEnable);
4385 if (ImGui::Selectable(vn::protocol::uart::str(vpeBasicControlEnable).c_str(), isSelected))
4386 {
4387 _vpeBasicControlRegister.enable = vpeBasicControlEnable;
4388 LOG_DEBUG("{}: vpeBasicControlRegister.enable changed to {}", nameId(), vn::protocol::uart::str(_vpeBasicControlRegister.enable));
4389 flow::ApplyChanges();
4390 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4391 {
4392 try
4393 {
4394 _vs.writeVpeBasicControl(_vpeBasicControlRegister);
4395 }
4396 catch (const std::exception& e)
4397 {
4398 LOG_ERROR("{}: Could not configure the vpeBasicControlRegister: {}", nameId(), e.what());
4399 doDeinitialize();
4400 }
4401 }
4402 else
4403 {
4404 doDeinitialize();
4405 }
4406 }
4407
4408 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4409 {
4410 ImGui::SetItemDefaultFocus();
4411 }
4412 }
4413 ImGui::EndCombo();
4414 }
4415
4416 static constexpr std::array<vn::protocol::uart::HeadingMode, 3> vpeBasicControlHeadingModes = {
4417 { vn::protocol::uart::HeadingMode::HEADINGMODE_ABSOLUTE,
4418 vn::protocol::uart::HeadingMode::HEADINGMODE_RELATIVE,
4419 vn::protocol::uart::HeadingMode::HEADINGMODE_INDOOR }
4420 };
4421 if (ImGui::BeginCombo(fmt::format("Heading Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_vpeBasicControlRegister.headingMode).c_str()))
4422 {
4423 for (const auto& vpeBasicControlHeadingMode : vpeBasicControlHeadingModes)
4424 {
4425 const bool isSelected = (_vpeBasicControlRegister.headingMode == vpeBasicControlHeadingMode);
4426 if (ImGui::Selectable(vn::protocol::uart::str(vpeBasicControlHeadingMode).c_str(), isSelected))
4427 {
4428 _vpeBasicControlRegister.headingMode = vpeBasicControlHeadingMode;
4429 LOG_DEBUG("{}: vpeBasicControlRegister.headingMode changed to {}", nameId(), vn::protocol::uart::str(_vpeBasicControlRegister.headingMode));
4430 flow::ApplyChanges();
4431 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4432 {
4433 try
4434 {
4435 _vs.writeVpeBasicControl(_vpeBasicControlRegister);
4436 }
4437 catch (const std::exception& e)
4438 {
4439 LOG_ERROR("{}: Could not configure the vpeBasicControlRegister: {}", nameId(), e.what());
4440 doDeinitialize();
4441 }
4442 }
4443 else
4444 {
4445 doDeinitialize();
4446 }
4447 }
4448
4449 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4450 {
4451 ImGui::SetItemDefaultFocus();
4452 }
4453 }
4454 ImGui::EndCombo();
4455 }
4456
4457 static constexpr std::array<vn::protocol::uart::VpeMode, 2> vpeBasicControlModes = {
4458 { vn::protocol::uart::VpeMode::VPEMODE_OFF,
4459 vn::protocol::uart::VpeMode::VPEMODE_MODE1 }
4460 };
4461 if (ImGui::BeginCombo(fmt::format("Filtering Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_vpeBasicControlRegister.filteringMode).c_str()))
4462 {
4463 for (const auto& vpeBasicControlMode : vpeBasicControlModes)
4464 {
4465 const bool isSelected = (_vpeBasicControlRegister.filteringMode == vpeBasicControlMode);
4466 if (ImGui::Selectable(vn::protocol::uart::str(vpeBasicControlMode).c_str(), isSelected))
4467 {
4468 _vpeBasicControlRegister.filteringMode = vpeBasicControlMode;
4469 LOG_DEBUG("{}: vpeBasicControlRegister.filteringMode changed to {}", nameId(), vn::protocol::uart::str(_vpeBasicControlRegister.filteringMode));
4470 flow::ApplyChanges();
4471 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4472 {
4473 try
4474 {
4475 _vs.writeVpeBasicControl(_vpeBasicControlRegister);
4476 }
4477 catch (const std::exception& e)
4478 {
4479 LOG_ERROR("{}: Could not configure the vpeBasicControlRegister: {}", nameId(), e.what());
4480 doDeinitialize();
4481 }
4482 }
4483 else
4484 {
4485 doDeinitialize();
4486 }
4487 }
4488
4489 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4490 {
4491 ImGui::SetItemDefaultFocus();
4492 }
4493 }
4494 ImGui::EndCombo();
4495 }
4496
4497 if (ImGui::BeginCombo(fmt::format("Tuning Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_vpeBasicControlRegister.tuningMode).c_str()))
4498 {
4499 for (const auto& vpeBasicControlMode : vpeBasicControlModes)
4500 {
4501 const bool isSelected = (_vpeBasicControlRegister.tuningMode == vpeBasicControlMode);
4502 if (ImGui::Selectable(vn::protocol::uart::str(vpeBasicControlMode).c_str(), isSelected))
4503 {
4504 _vpeBasicControlRegister.tuningMode = vpeBasicControlMode;
4505 LOG_DEBUG("{}: vpeBasicControlRegister.tuningMode changed to {}", nameId(), vn::protocol::uart::str(_vpeBasicControlRegister.tuningMode));
4506 flow::ApplyChanges();
4507 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4508 {
4509 try
4510 {
4511 _vs.writeVpeBasicControl(_vpeBasicControlRegister);
4512 }
4513 catch (const std::exception& e)
4514 {
4515 LOG_ERROR("{}: Could not configure the vpeBasicControlRegister: {}", nameId(), e.what());
4516 doDeinitialize();
4517 }
4518 }
4519 else
4520 {
4521 doDeinitialize();
4522 }
4523 }
4524
4525 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4526 {
4527 ImGui::SetItemDefaultFocus();
4528 }
4529 }
4530 ImGui::EndCombo();
4531 }
4532
4533 ImGui::TreePop();
4534 }
4535
4536 if (_sensorModel == VectorNavModel::VN100_VN110)
4537 {
4538 if (ImGui::TreeNode(fmt::format("VPE Magnetometer Basic Tuning##{}", size_t(id)).c_str()))
4539 {
4540 ImGui::TextUnformatted("Provides basic control of the adaptive filtering and tuning for the magnetometer.");
4541
4542 if (ImGui::DragFloat3(fmt::format("Base Tuning Level##{}", size_t(id)).c_str(), _vpeMagnetometerBasicTuningRegister.baseTuning.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4543 {
4544 LOG_DEBUG("{}: vpeMagnetometerBasicTuningRegister.baseTuning changed to {}", nameId(), _vpeMagnetometerBasicTuningRegister.baseTuning);
4545 flow::ApplyChanges();
4546 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4547 {
4548 try
4549 {
4550 _vs.writeVpeMagnetometerBasicTuning(_vpeMagnetometerBasicTuningRegister);
4551 }
4552 catch (const std::exception& e)
4553 {
4554 LOG_ERROR("{}: Could not configure the vpeMagnetometerBasicTuningRegister: {}", nameId(), e.what());
4555 doDeinitialize();
4556 }
4557 }
4558 else
4559 {
4560 doDeinitialize();
4561 }
4562 }
4563 ImGui::SameLine();
4564 gui::widgets::HelpMarker("This sets the level of confidence placed in the magnetometer when no disturbances are present. "
4565 "A larger number provides better heading accuracy, but with more sensitivity to magnetic interference.");
4566
4567 if (ImGui::DragFloat3(fmt::format("Adaptive Tuning Level##{}", size_t(id)).c_str(), _vpeMagnetometerBasicTuningRegister.adaptiveTuning.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4568 {
4569 LOG_DEBUG("{}: vpeMagnetometerBasicTuningRegister.adaptiveTuning changed to {}", nameId(), _vpeMagnetometerBasicTuningRegister.adaptiveTuning);
4570 flow::ApplyChanges();
4571 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4572 {
4573 try
4574 {
4575 _vs.writeVpeMagnetometerBasicTuning(_vpeMagnetometerBasicTuningRegister);
4576 }
4577 catch (const std::exception& e)
4578 {
4579 LOG_ERROR("{}: Could not configure the vpeMagnetometerBasicTuningRegister: {}", nameId(), e.what());
4580 doDeinitialize();
4581 }
4582 }
4583 else
4584 {
4585 doDeinitialize();
4586 }
4587 }
4588
4589 if (ImGui::DragFloat3(fmt::format("Adaptive Filtering Level##{}", size_t(id)).c_str(), _vpeMagnetometerBasicTuningRegister.adaptiveFiltering.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4590 {
4591 LOG_DEBUG("{}: vpeMagnetometerBasicTuningRegister.adaptiveFiltering changed to {}", nameId(), _vpeMagnetometerBasicTuningRegister.adaptiveFiltering);
4592 flow::ApplyChanges();
4593 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4594 {
4595 try
4596 {
4597 _vs.writeVpeMagnetometerBasicTuning(_vpeMagnetometerBasicTuningRegister);
4598 }
4599 catch (const std::exception& e)
4600 {
4601 LOG_ERROR("{}: Could not configure the vpeMagnetometerBasicTuningRegister: {}", nameId(), e.what());
4602 doDeinitialize();
4603 }
4604 }
4605 else
4606 {
4607 doDeinitialize();
4608 }
4609 }
4610
4611 ImGui::TreePop();
4612 }
4613
4614 if (ImGui::TreeNode(fmt::format("VPE Accelerometer Basic Tuning##{}", size_t(id)).c_str()))
4615 {
4616 ImGui::TextUnformatted("Provides basic control of the adaptive filtering and tuning for the accelerometer.");
4617
4618 if (ImGui::DragFloat3(fmt::format("Base Tuning Level##{}", size_t(id)).c_str(), _vpeAccelerometerBasicTuningRegister.baseTuning.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4619 {
4620 LOG_DEBUG("{}: vpeAccelerometerBasicTuningRegister.baseTuning changed to {}", nameId(), _vpeAccelerometerBasicTuningRegister.baseTuning);
4621 flow::ApplyChanges();
4622 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4623 {
4624 try
4625 {
4626 _vs.writeVpeAccelerometerBasicTuning(_vpeAccelerometerBasicTuningRegister);
4627 }
4628 catch (const std::exception& e)
4629 {
4630 LOG_ERROR("{}: Could not configure the vpeAccelerometerBasicTuningRegister: {}", nameId(), e.what());
4631 doDeinitialize();
4632 }
4633 }
4634 else
4635 {
4636 doDeinitialize();
4637 }
4638 }
4639 ImGui::SameLine();
4640 gui::widgets::HelpMarker("This sets the level of confidence placed in the accelerometer when no disturbances are present. "
4641 "A larger number provides better pitch/roll heading accuracy, but with more sensitivity to acceleration interference.");
4642
4643 if (ImGui::DragFloat3(fmt::format("Adaptive Tuning Level##{}", size_t(id)).c_str(), _vpeAccelerometerBasicTuningRegister.adaptiveTuning.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4644 {
4645 LOG_DEBUG("{}: vpeAccelerometerBasicTuningRegister.adaptiveTuning changed to {}", nameId(), _vpeAccelerometerBasicTuningRegister.adaptiveTuning);
4646 flow::ApplyChanges();
4647 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4648 {
4649 try
4650 {
4651 _vs.writeVpeAccelerometerBasicTuning(_vpeAccelerometerBasicTuningRegister);
4652 }
4653 catch (const std::exception& e)
4654 {
4655 LOG_ERROR("{}: Could not configure the vpeAccelerometerBasicTuningRegister: {}", nameId(), e.what());
4656 doDeinitialize();
4657 }
4658 }
4659 else
4660 {
4661 doDeinitialize();
4662 }
4663 }
4664
4665 if (ImGui::DragFloat3(fmt::format("Adaptive Filtering Level##{}", size_t(id)).c_str(), _vpeAccelerometerBasicTuningRegister.adaptiveFiltering.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4666 {
4667 LOG_DEBUG("{}: vpeAccelerometerBasicTuningRegister.adaptiveFiltering changed to {}", nameId(), _vpeAccelerometerBasicTuningRegister.adaptiveFiltering);
4668 flow::ApplyChanges();
4669 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4670 {
4671 try
4672 {
4673 _vs.writeVpeAccelerometerBasicTuning(_vpeAccelerometerBasicTuningRegister);
4674 }
4675 catch (const std::exception& e)
4676 {
4677 LOG_ERROR("{}: Could not configure the vpeAccelerometerBasicTuningRegister: {}", nameId(), e.what());
4678 doDeinitialize();
4679 }
4680 }
4681 else
4682 {
4683 doDeinitialize();
4684 }
4685 }
4686
4687 ImGui::TreePop();
4688 }
4689
4690 if (ImGui::TreeNode(fmt::format("VPE Gyro Basic Tuning##{}", size_t(id)).c_str()))
4691 {
4692 ImGui::TextUnformatted("Provides basic control of the adaptive filtering and tuning for the gyroscope.");
4693
4694 if (ImGui::DragFloat3(fmt::format("Base Tuning Level##{}", size_t(id)).c_str(), _vpeGyroBasicTuningRegister.baseTuning.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4695 {
4696 LOG_DEBUG("{}: vpeGyroBasicTuningRegister.baseTuning changed to {}", nameId(), _vpeGyroBasicTuningRegister.baseTuning);
4697 flow::ApplyChanges();
4698 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4699 {
4700 try
4701 {
4702 _vs.writeVpeGyroBasicTuning(_vpeGyroBasicTuningRegister);
4703 }
4704 catch (const std::exception& e)
4705 {
4706 LOG_ERROR("{}: Could not configure the vpeGyroBasicTuningRegister: {}", nameId(), e.what());
4707 doDeinitialize();
4708 }
4709 }
4710 else
4711 {
4712 doDeinitialize();
4713 }
4714 }
4715 ImGui::SameLine();
4716 gui::widgets::HelpMarker("This sets the level of confidence placed in the gyro axes.");
4717
4718 if (ImGui::DragFloat3(fmt::format("Adaptive Tuning Level##{}", size_t(id)).c_str(), _vpeGyroBasicTuningRegister.adaptiveTuning.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4719 {
4720 LOG_DEBUG("{}: vpeGyroBasicTuningRegister.adaptiveTuning changed to {}", nameId(), _vpeGyroBasicTuningRegister.adaptiveTuning);
4721 flow::ApplyChanges();
4722 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4723 {
4724 try
4725 {
4726 _vs.writeVpeGyroBasicTuning(_vpeGyroBasicTuningRegister);
4727 }
4728 catch (const std::exception& e)
4729 {
4730 LOG_ERROR("{}: Could not configure the vpeGyroBasicTuningRegister: {}", nameId(), e.what());
4731 doDeinitialize();
4732 }
4733 }
4734 else
4735 {
4736 doDeinitialize();
4737 }
4738 }
4739
4740 if (ImGui::DragFloat3(fmt::format("Variance - Angular Walk##{}", size_t(id)).c_str(), _vpeGyroBasicTuningRegister.angularWalkVariance.c, 0.1F, 0.0F, 10.0F, "%.1f"))
4741 {
4742 LOG_DEBUG("{}: vpeGyroBasicTuningRegister.angularWalkVariance changed to {}", nameId(), _vpeGyroBasicTuningRegister.angularWalkVariance);
4743 flow::ApplyChanges();
4744 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4745 {
4746 try
4747 {
4748 _vs.writeVpeGyroBasicTuning(_vpeGyroBasicTuningRegister);
4749 }
4750 catch (const std::exception& e)
4751 {
4752 LOG_ERROR("{}: Could not configure the vpeGyroBasicTuningRegister: {}", nameId(), e.what());
4753 doDeinitialize();
4754 }
4755 }
4756 else
4757 {
4758 doDeinitialize();
4759 }
4760 }
4761
4762 ImGui::TreePop();
4763 }
4764
4765 if (ImGui::TreeNode(fmt::format("Filter Startup Gyro Bias##{}", size_t(id)).c_str()))
4766 {
4767 ImGui::TextUnformatted("The filter gyro bias estimate used at startup [rad/s].");
4768
4769 if (ImGui::InputFloat3(fmt::format("## FilterStartupGyroBias {}", size_t(id)).c_str(), _filterStartupGyroBias.c, "%.1f"))
4770 {
4771 LOG_DEBUG("{}: filterStartupGyroBias changed to {}", nameId(), _filterStartupGyroBias);
4772 flow::ApplyChanges();
4773 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4774 {
4775 try
4776 {
4777 _vs.writeFilterStartupGyroBias(_filterStartupGyroBias);
4778 }
4779 catch (const std::exception& e)
4780 {
4781 LOG_ERROR("{}: Could not configure the filterStartupGyroBias: {}", nameId(), e.what());
4782 doDeinitialize();
4783 }
4784 }
4785 else
4786 {
4787 doDeinitialize();
4788 }
4789 }
4790
4791 ImGui::TreePop();
4792 }
4793 }
4794 }
4795
4796 // ###########################################################################################################
4797 // INS SUBSYSTEM
4798 // ###########################################################################################################
4799
4800 if (_sensorModel == VectorNavModel::VN310)
4801 {
4802 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
4803 if (ImGui::CollapsingHeader(fmt::format("INS Subsystem##{}", size_t(id)).c_str()))
4804 {
4805 if (ImGui::TreeNode(fmt::format("INS Basic Configuration##{}", size_t(id)).c_str()))
4806 {
4807 static constexpr std::array<std::pair<vn::protocol::uart::Scenario, const char*>, 3> insBasicConfigurationRegisterVn300Scenarios = {
4808 { { vn::protocol::uart::Scenario::SCENARIO_INSWITHPRESSURE, "General purpose INS with barometric pressure sensor" },
4809 { vn::protocol::uart::Scenario::SCENARIO_INSWITHOUTPRESSURE, "General purpose INS without barometric pressure sensor" },
4810 { vn::protocol::uart::Scenario::SCENARIO_GPSMOVINGBASELINEDYNAMIC, "GNSS moving baseline for dynamic applications" } }
4811 };
4812 if (ImGui::BeginCombo(fmt::format("Scenario##{}", size_t(id)).c_str(), vn::protocol::uart::str(_insBasicConfigurationRegisterVn300.scenario).c_str()))
4813 {
4814 for (const auto& insBasicConfigurationRegisterVn300Scenario : insBasicConfigurationRegisterVn300Scenarios)
4815 {
4816 const bool isSelected = (_insBasicConfigurationRegisterVn300.scenario == insBasicConfigurationRegisterVn300Scenario.first);
4817 if (ImGui::Selectable(vn::protocol::uart::str(insBasicConfigurationRegisterVn300Scenario.first).c_str(), isSelected))
4818 {
4819 _insBasicConfigurationRegisterVn300.scenario = insBasicConfigurationRegisterVn300Scenario.first;
4820 LOG_DEBUG("{}: insBasicConfigurationRegisterVn300.scenario changed to {}", nameId(), vn::protocol::uart::str(_insBasicConfigurationRegisterVn300.scenario));
4821 flow::ApplyChanges();
4822 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4823 {
4824 try
4825 {
4826 _vs.writeInsBasicConfigurationVn300(_insBasicConfigurationRegisterVn300);
4827 }
4828 catch (const std::exception& e)
4829 {
4830 LOG_ERROR("{}: Could not configure the insBasicConfigurationRegisterVn300: {}", nameId(), e.what());
4831 doDeinitialize();
4832 }
4833 }
4834 else
4835 {
4836 doDeinitialize();
4837 }
4838 }
4839 if (ImGui::IsItemHovered())
4840 {
4841 ImGui::BeginTooltip();
4842 ImGui::TextUnformatted(insBasicConfigurationRegisterVn300Scenario.second);
4843 ImGui::EndTooltip();
4844 }
4845
4846 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
4847 {
4848 ImGui::SetItemDefaultFocus();
4849 }
4850 }
4851 ImGui::EndCombo();
4852 }
4853
4854 if (ImGui::Checkbox(fmt::format("Ahrs Aiding##{}", size_t(id)).c_str(), &_insBasicConfigurationRegisterVn300.ahrsAiding))
4855 {
4856 LOG_DEBUG("{}: insBasicConfigurationRegisterVn300.ahrsAiding changed to {}", nameId(), _insBasicConfigurationRegisterVn300.ahrsAiding);
4857 flow::ApplyChanges();
4858 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4859 {
4860 try
4861 {
4862 _vs.writeInsBasicConfigurationVn300(_insBasicConfigurationRegisterVn300);
4863 }
4864 catch (const std::exception& e)
4865 {
4866 LOG_ERROR("{}: Could not configure the insBasicConfigurationRegisterVn300: {}", nameId(), e.what());
4867 doDeinitialize();
4868 }
4869 }
4870 else
4871 {
4872 doDeinitialize();
4873 }
4874 }
4875 ImGui::SameLine();
4876 gui::widgets::HelpMarker("Enables AHRS attitude aiding. AHRS aiding provides "
4877 "the ability to switch to using the magnetometer to "
4878 "stabilize heading during times when the device is "
4879 "stationary and the GNSS compass is not available. "
4880 "AHRS aiding also helps to eliminate large updates in "
4881 "the attitude solution during times when heading is "
4882 "weakly observable, such as at startup.");
4883
4884 if (ImGui::Checkbox(fmt::format("Estimate Baseline##{}", size_t(id)).c_str(), &_insBasicConfigurationRegisterVn300.estBaseline))
4885 {
4886 LOG_DEBUG("{}: insBasicConfigurationRegisterVn300.estBaseline changed to {}", nameId(), _insBasicConfigurationRegisterVn300.estBaseline);
4887 flow::ApplyChanges();
4888 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4889 {
4890 try
4891 {
4892 _vs.writeInsBasicConfigurationVn300(_insBasicConfigurationRegisterVn300);
4893 }
4894 catch (const std::exception& e)
4895 {
4896 LOG_ERROR("{}: Could not configure the insBasicConfigurationRegisterVn300: {}", nameId(), e.what());
4897 doDeinitialize();
4898 }
4899 }
4900 else
4901 {
4902 doDeinitialize();
4903 }
4904 }
4905 ImGui::SameLine();
4906 gui::widgets::HelpMarker("Enables GNSS compass baseline estimation by INS");
4907
4908 ImGui::TreePop();
4909 }
4910
4911 if (ImGui::TreeNode(fmt::format("Startup Filter Bias Estimate##{}", size_t(id)).c_str()))
4912 {
4913 ImGui::TextUnformatted("Sets the initial estimate for the filter bias states");
4914
4915 if (ImGui::InputFloat3(fmt::format("Gyro Bias [rad/s]##{}", size_t(id)).c_str(), _startupFilterBiasEstimateRegister.gyroBias.c, "%.1f"))
4916 {
4917 LOG_DEBUG("{}: startupFilterBiasEstimateRegister.gyroBias changed to {}", nameId(), _startupFilterBiasEstimateRegister.gyroBias);
4918 flow::ApplyChanges();
4919 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4920 {
4921 try
4922 {
4923 _vs.writeStartupFilterBiasEstimate(_startupFilterBiasEstimateRegister);
4924 }
4925 catch (const std::exception& e)
4926 {
4927 LOG_ERROR("{}: Could not configure the startupFilterBiasEstimateRegister: {}", nameId(), e.what());
4928 doDeinitialize();
4929 }
4930 }
4931 else
4932 {
4933 doDeinitialize();
4934 }
4935 }
4936
4937 if (ImGui::InputFloat3(fmt::format("Accel Bias [m/s^2]##{}", size_t(id)).c_str(), _startupFilterBiasEstimateRegister.accelBias.c, "%.1f"))
4938 {
4939 LOG_DEBUG("{}: startupFilterBiasEstimateRegister.accelBias changed to {}", nameId(), _startupFilterBiasEstimateRegister.accelBias);
4940 flow::ApplyChanges();
4941 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4942 {
4943 try
4944 {
4945 _vs.writeStartupFilterBiasEstimate(_startupFilterBiasEstimateRegister);
4946 }
4947 catch (const std::exception& e)
4948 {
4949 LOG_ERROR("{}: Could not configure the startupFilterBiasEstimateRegister: {}", nameId(), e.what());
4950 doDeinitialize();
4951 }
4952 }
4953 else
4954 {
4955 doDeinitialize();
4956 }
4957 }
4958
4959 if (ImGui::InputFloat(fmt::format("Pressure Bias [m]##{}", size_t(id)).c_str(), &_startupFilterBiasEstimateRegister.pressureBias))
4960 {
4961 LOG_DEBUG("{}: startupFilterBiasEstimateRegister.pressureBias changed to {}", nameId(), _startupFilterBiasEstimateRegister.pressureBias);
4962 flow::ApplyChanges();
4963 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
4964 {
4965 try
4966 {
4967 _vs.writeStartupFilterBiasEstimate(_startupFilterBiasEstimateRegister);
4968 }
4969 catch (const std::exception& e)
4970 {
4971 LOG_ERROR("{}: Could not configure the startupFilterBiasEstimateRegister: {}", nameId(), e.what());
4972 doDeinitialize();
4973 }
4974 }
4975 else
4976 {
4977 doDeinitialize();
4978 }
4979 }
4980
4981 ImGui::TreePop();
4982 }
4983 }
4984 }
4985
4986 // ###########################################################################################################
4987 // HARD/SOFT IRON ESTIMATOR SUBSYSTEM
4988 // ###########################################################################################################
4989
4990 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
4991 if (ImGui::CollapsingHeader(fmt::format("Hard/Soft Iron Estimator Subsystem##{}", size_t(id)).c_str()))
4992 {
4993 if (ImGui::TreeNode(fmt::format("Magnetometer Calibration Control##{}", size_t(id)).c_str()))
4994 {
4995 ImGui::TextUnformatted("Controls the magnetometer real-time calibration algorithm.");
4996 if (_sensorModel == VectorNavModel::VN310)
4997 {
4998 ImGui::SameLine();
4999 gui::widgets::HelpMarker("On the PRODUCT the magnetometer is only used to provide a coarse heading estimate at startup "
5000 "and is completely tuned out of the INS filter during normal operation. A Hard/Soft Iron calibration "
5001 "may be performed to try and improve the startup magnetic heading, but is not required for nominal "
5002 "operaiton.",
5003 "(!)");
5004 }
5005
5006 static constexpr std::array<std::pair<vn::protocol::uart::HsiMode, const char*>, 3> magnetometerCalibrationControlHsiModes = {
5007 { { vn::protocol::uart::HsiMode::HSIMODE_OFF, "Real-time hard/soft iron calibration algorithm is turned off" },
5008 { vn::protocol::uart::HsiMode::HSIMODE_RUN, "Runs the real-time hard/soft iron calibration. The algorithm will continue using its existing solution.\n"
5009 "The algorithm can be started and stopped at any time by switching between the HSI_OFF and HSI_RUN state." },
5010 { vn::protocol::uart::HsiMode::HSIMODE_RESET, "Resets the real-time hard/soft iron solution" } }
5011 };
5012 if (ImGui::BeginCombo(fmt::format("HSI Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_magnetometerCalibrationControlRegister.hsiMode).c_str()))
5013 {
5014 for (const auto& magnetometerCalibrationControlHsiMode : magnetometerCalibrationControlHsiModes)
5015 {
5016 const bool isSelected = (_magnetometerCalibrationControlRegister.hsiMode == magnetometerCalibrationControlHsiMode.first);
5017 if (ImGui::Selectable(vn::protocol::uart::str(magnetometerCalibrationControlHsiMode.first).c_str(), isSelected))
5018 {
5019 _magnetometerCalibrationControlRegister.hsiMode = magnetometerCalibrationControlHsiMode.first;
5020 LOG_DEBUG("{}: magnetometerCalibrationControlRegister.hsiMode changed to {}", nameId(), vn::protocol::uart::str(_magnetometerCalibrationControlRegister.hsiMode));
5021 flow::ApplyChanges();
5022 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5023 {
5024 try
5025 {
5026 _vs.writeMagnetometerCalibrationControl(_magnetometerCalibrationControlRegister);
5027 }
5028 catch (const std::exception& e)
5029 {
5030 LOG_ERROR("{}: Could not configure the magnetometerCalibrationControlRegister: {}", nameId(), e.what());
5031 doDeinitialize();
5032 }
5033 }
5034 else
5035 {
5036 doDeinitialize();
5037 }
5038 }
5039 if (ImGui::IsItemHovered())
5040 {
5041 ImGui::BeginTooltip();
5042 ImGui::TextUnformatted(magnetometerCalibrationControlHsiMode.second);
5043 ImGui::EndTooltip();
5044 }
5045
5046 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
5047 {
5048 ImGui::SetItemDefaultFocus();
5049 }
5050 }
5051 ImGui::EndCombo();
5052 }
5053 ImGui::SameLine();
5054 gui::widgets::HelpMarker("Controls the mode of operation for the onboard real-time magnetometer hard/soft iron compensation algorithm.");
5055
5056 static constexpr std::array<std::pair<vn::protocol::uart::HsiOutput, const char*>, 2> magnetometerCalibrationControlHsiOutputs = {
5057 { { vn::protocol::uart::HsiOutput::HSIOUTPUT_NOONBOARD, "Onboard HSI is not applied to the magnetic measurements" },
5058 { vn::protocol::uart::HsiOutput::HSIOUTPUT_USEONBOARD, "Onboard HSI is applied to the magnetic measurements" } }
5059 };
5060 if (ImGui::BeginCombo(fmt::format("HSI Output##{}", size_t(id)).c_str(), vn::protocol::uart::str(_magnetometerCalibrationControlRegister.hsiOutput).c_str()))
5061 {
5062 for (const auto& [magnetometerCalibrationControlHsiOutputMode, magnetometerCalibrationControlHsiOutputDescription] : magnetometerCalibrationControlHsiOutputs)
5063 {
5064 const bool isSelected = (_magnetometerCalibrationControlRegister.hsiOutput == magnetometerCalibrationControlHsiOutputMode);
5065 if (ImGui::Selectable(vn::protocol::uart::str(magnetometerCalibrationControlHsiOutputMode).c_str(), isSelected))
5066 {
5067 _magnetometerCalibrationControlRegister.hsiOutput = magnetometerCalibrationControlHsiOutputMode;
5068 LOG_DEBUG("{}: magnetometerCalibrationControlRegister.hsiOutput changed to {}", nameId(), vn::protocol::uart::str(_magnetometerCalibrationControlRegister.hsiOutput));
5069 flow::ApplyChanges();
5070 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5071 {
5072 try
5073 {
5074 _vs.writeMagnetometerCalibrationControl(_magnetometerCalibrationControlRegister);
5075 }
5076 catch (const std::exception& e)
5077 {
5078 LOG_ERROR("{}: Could not configure the magnetometerCalibrationControlRegister: {}", nameId(), e.what());
5079 doDeinitialize();
5080 }
5081 }
5082 else
5083 {
5084 doDeinitialize();
5085 }
5086 }
5087 if (ImGui::IsItemHovered())
5088 {
5089 ImGui::BeginTooltip();
5090 ImGui::TextUnformatted(magnetometerCalibrationControlHsiOutputDescription);
5091 ImGui::EndTooltip();
5092 }
5093
5094 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
5095 {
5096 ImGui::SetItemDefaultFocus();
5097 }
5098 }
5099 ImGui::EndCombo();
5100 }
5101 ImGui::SameLine();
5102 gui::widgets::HelpMarker("Controls the type of measurements that are provided as "
5103 "outputs from the magnetometer sensor and also "
5104 "subsequently used in the attitude filter.");
5105
5106 int convergeRate = _magnetometerCalibrationControlRegister.convergeRate;
5107 if (ImGui::SliderInt(fmt::format("Converge Rate##{}", size_t(id)).c_str(), &convergeRate, 0, 5))
5108 {
5109 _magnetometerCalibrationControlRegister.convergeRate = static_cast<uint8_t>(convergeRate);
5110 LOG_DEBUG("{}: magnetometerCalibrationControlRegister.convergeRate changed to {}", nameId(), _magnetometerCalibrationControlRegister.convergeRate);
5111 flow::ApplyChanges();
5112 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5113 {
5114 try
5115 {
5116 _vs.writeMagnetometerCalibrationControl(_magnetometerCalibrationControlRegister);
5117 }
5118 catch (const std::exception& e)
5119 {
5120 LOG_ERROR("{}: Could not configure the magnetometerCalibrationControlRegister: {}", nameId(), e.what());
5121 doDeinitialize();
5122 }
5123 }
5124 else
5125 {
5126 doDeinitialize();
5127 }
5128 }
5129 ImGui::SameLine();
5130 gui::widgets::HelpMarker("Controls how quickly the hard/soft iron solution is allowed "
5131 "to converge onto a new solution. The slower the "
5132 "convergence the more accurate the estimate of the "
5133 "hard/soft iron solution. A quicker convergence will provide "
5134 "a less accurate estimate of the hard/soft iron parameters, "
5135 "but for applications where the hard/soft iron changes "
5136 "rapidly may provide a more accurate attitude estimate.\n\n"
5137 "Range: 1 to 5\n"
5138 "1 = Solution converges slowly over approximately 60-90 seconds.\n"
5139 "5 = Solution converges rapidly over approximately 15-20 seconds.");
5140
5141 ImGui::TreePop();
5142 }
5143 }
5144
5145 // ###########################################################################################################
5146 // WORLD MAGNETIC & GRAVITY MODULE
5147 // ###########################################################################################################
5148
5149 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
5150 if (ImGui::CollapsingHeader(fmt::format("World Magnetic & Gravity Module##{}", size_t(id)).c_str()))
5151 {
5152 if (ImGui::TreeNode(fmt::format("Magnetic and Gravity Reference Vectors##{}", size_t(id)).c_str()))
5153 {
5154 ImGui::TextUnformatted("Magnetic and gravity reference vectors");
5155 ImGui::SameLine();
5156 gui::widgets::HelpMarker("This register contains the reference vectors for the magnetic and gravitational fields as used by the "
5157 "onboard filter. The values map to either the user-set values or the results of calculations of the onboard "
5158 "reference models (see the Reference Vector Configuration Register in the IMU subsystem). When the "
5159 "reference values come from the onboard model(s), those values are read-only. When the reference models "
5160 "are disabled, the values reflect the user reference vectors and will be writable. For example, if the onboard "
5161 "World Magnetic Model is enabled and the onboard Gravitational Model is disabled, only the gravity "
5162 "reference values will be modified on a register write. Note that the user reference vectors will not be "
5163 "overwritten by the onboard models, but will retain their previous values for when the onboard models are "
5164 "disabled.");
5165
5166 if (ImGui::InputFloat3(fmt::format("Magnetic Reference [Gauss]##{}", size_t(id)).c_str(), _magneticAndGravityReferenceVectorsRegister.magRef.c, "%.3f"))
5167 {
5168 LOG_DEBUG("{}: magneticAndGravityReferenceVectorsRegister.magRef changed to {}", nameId(), _magneticAndGravityReferenceVectorsRegister.magRef);
5169 flow::ApplyChanges();
5170 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5171 {
5172 try
5173 {
5174 _vs.writeMagneticAndGravityReferenceVectors(_magneticAndGravityReferenceVectorsRegister);
5175 }
5176 catch (const std::exception& e)
5177 {
5178 LOG_ERROR("{}: Could not configure the magneticAndGravityReferenceVectorsRegister: {}", nameId(), e.what());
5179 doDeinitialize();
5180 }
5181 }
5182 else
5183 {
5184 doDeinitialize();
5185 }
5186 }
5187
5188 if (ImGui::InputFloat3(fmt::format("Gravity Reference [m/s^2]##{}", size_t(id)).c_str(), _magneticAndGravityReferenceVectorsRegister.accRef.c, "%.6f"))
5189 {
5190 LOG_DEBUG("{}: magneticAndGravityReferenceVectorsRegister.accRef changed to {}", nameId(), _magneticAndGravityReferenceVectorsRegister.accRef);
5191 flow::ApplyChanges();
5192 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5193 {
5194 try
5195 {
5196 _vs.writeMagneticAndGravityReferenceVectors(_magneticAndGravityReferenceVectorsRegister);
5197 }
5198 catch (const std::exception& e)
5199 {
5200 LOG_ERROR("{}: Could not configure the magneticAndGravityReferenceVectorsRegister: {}", nameId(), e.what());
5201 doDeinitialize();
5202 }
5203 }
5204 else
5205 {
5206 doDeinitialize();
5207 }
5208 }
5209
5210 ImGui::TreePop();
5211 }
5212
5213 if (ImGui::TreeNode(fmt::format("Reference Vector Configuration##{}", size_t(id)).c_str()))
5214 {
5215 ImGui::TextUnformatted("Control register for both the onboard world magnetic and gravity model corrections");
5216 ImGui::SameLine();
5217 gui::widgets::HelpMarker("This register allows configuration of the onboard spherical harmonic models used to calculate the local "
5218 "magnetic and gravitational reference values. Having accurate magnetic reference values improves the "
5219 "accuracy of heading when using the magnetometer and accounts for magnetic declination. Having accurate "
5220 "gravitational reference values improves accuracy by allowing the INS filter to more accurately estimate the "
5221 "accelerometer biases. The VectorNav currently includes the EGM96 gravitational model and the WMM2010 "
5222 "magnetic model. The models are upgradable to allow updating to future models when available.\n\n"
5223 "The magnetic and gravity models can be individually enabled or disabled using the UseMagModel and "
5224 "UseGravityModel parameters, respectively. When disabled, the corresponding values set by the user in the "
5225 "Reference Vector Register in the IMU subsystem will be used instead of values calculated by the onboard "
5226 "model.\n\n"
5227 "The VectorNav starts up with the user configured reference vector values. Shortly after startup (and if the "
5228 "models are enabled), the location and time set in this register will be used to update the reference vectors. "
5229 "When a 3D GNSS fix is available, the location and time reported by the GNSS will be used to update the model. "
5230 "If GNSS is lost, the reference vectors will hold their last valid values. The model values will be recalculated "
5231 "whenever the current position has changed by the RecaclThreshold or the date has changed by more than "
5232 "approximately 8 hours, whichever comes first.");
5233
5234 if (ImGui::Checkbox(fmt::format("Use Mag Model##{}", size_t(id)).c_str(), &_referenceVectorConfigurationRegister.useMagModel))
5235 {
5236 LOG_DEBUG("{}: referenceVectorConfigurationRegister.useMagModel changed to {}", nameId(), _referenceVectorConfigurationRegister.useMagModel);
5237 flow::ApplyChanges();
5238 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5239 {
5240 try
5241 {
5242 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
5243 }
5244 catch (const std::exception& e)
5245 {
5246 LOG_ERROR("{}: Could not configure the referenceVectorConfigurationRegister: {}", nameId(), e.what());
5247 doDeinitialize();
5248 }
5249 }
5250 else
5251 {
5252 doDeinitialize();
5253 }
5254 }
5255
5256 if (ImGui::Checkbox(fmt::format("Use Gravity Model##{}", size_t(id)).c_str(), &_referenceVectorConfigurationRegister.useGravityModel))
5257 {
5258 LOG_DEBUG("{}: referenceVectorConfigurationRegister.useGravityModel changed to {}", nameId(), _referenceVectorConfigurationRegister.useGravityModel);
5259 flow::ApplyChanges();
5260 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5261 {
5262 try
5263 {
5264 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
5265 }
5266 catch (const std::exception& e)
5267 {
5268 LOG_ERROR("{}: Could not configure the referenceVectorConfigurationRegister: {}", nameId(), e.what());
5269 doDeinitialize();
5270 }
5271 }
5272 else
5273 {
5274 doDeinitialize();
5275 }
5276 }
5277
5278 auto recalcThreshold = static_cast<int>(_referenceVectorConfigurationRegister.recalcThreshold);
5279 if (ImGui::InputInt(fmt::format("Recalc Threshold [m]##{}", size_t(id)).c_str(), &recalcThreshold))
5280 {
5281 _referenceVectorConfigurationRegister.recalcThreshold = static_cast<uint32_t>(recalcThreshold);
5282 LOG_DEBUG("{}: referenceVectorConfigurationRegister.recalcThreshold changed to {}", nameId(), _referenceVectorConfigurationRegister.recalcThreshold);
5283 flow::ApplyChanges();
5284 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5285 {
5286 try
5287 {
5288 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
5289 }
5290 catch (const std::exception& e)
5291 {
5292 LOG_ERROR("{}: Could not configure the referenceVectorConfigurationRegister: {}", nameId(), e.what());
5293 doDeinitialize();
5294 }
5295 }
5296 else
5297 {
5298 doDeinitialize();
5299 }
5300 }
5301 ImGui::SameLine();
5302 gui::widgets::HelpMarker("Maximum distance traveled before magnetic and gravity models are recalculated for the new position.");
5303
5304 if (ImGui::InputFloat(fmt::format("Year##{}", size_t(id)).c_str(), &_referenceVectorConfigurationRegister.year))
5305 {
5306 LOG_DEBUG("{}: referenceVectorConfigurationRegister.year changed to {}", nameId(), _referenceVectorConfigurationRegister.year);
5307 flow::ApplyChanges();
5308 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5309 {
5310 try
5311 {
5312 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
5313 }
5314 catch (const std::exception& e)
5315 {
5316 LOG_ERROR("{}: Could not configure the referenceVectorConfigurationRegister: {}", nameId(), e.what());
5317 doDeinitialize();
5318 }
5319 }
5320 else
5321 {
5322 doDeinitialize();
5323 }
5324 }
5325 ImGui::SameLine();
5326 gui::widgets::HelpMarker("The reference date expressed as a decimal year. Used for both the magnetic and gravity models.");
5327
5328 if (ImGui::InputDouble(fmt::format("Latitude [deg]##{}", size_t(id)).c_str(), &_referenceVectorConfigurationRegister.position[0]))
5329 {
5330 LOG_DEBUG("{}: referenceVectorConfigurationRegister.position.latitude changed to {}", nameId(), _referenceVectorConfigurationRegister.position[0]);
5331 flow::ApplyChanges();
5332 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5333 {
5334 try
5335 {
5336 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
5337 }
5338 catch (const std::exception& e)
5339 {
5340 LOG_ERROR("{}: Could not configure the referenceVectorConfigurationRegister: {}", nameId(), e.what());
5341 doDeinitialize();
5342 }
5343 }
5344 else
5345 {
5346 doDeinitialize();
5347 }
5348 }
5349 ImGui::SameLine();
5350 gui::widgets::HelpMarker("The reference latitude position in degrees.");
5351
5352 if (ImGui::InputDouble(fmt::format("Longitude [deg]##{}", size_t(id)).c_str(), &_referenceVectorConfigurationRegister.position[1]))
5353 {
5354 LOG_DEBUG("{}: referenceVectorConfigurationRegister.position.longitude changed to {}", nameId(), _referenceVectorConfigurationRegister.position[1]);
5355 flow::ApplyChanges();
5356 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5357 {
5358 try
5359 {
5360 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
5361 }
5362 catch (const std::exception& e)
5363 {
5364 LOG_ERROR("{}: Could not configure the referenceVectorConfigurationRegister: {}", nameId(), e.what());
5365 doDeinitialize();
5366 }
5367 }
5368 else
5369 {
5370 doDeinitialize();
5371 }
5372 }
5373 ImGui::SameLine();
5374 gui::widgets::HelpMarker("The reference longitude position in degrees.");
5375
5376 if (ImGui::InputDouble(fmt::format("Altitude [m]##{}", size_t(id)).c_str(), &_referenceVectorConfigurationRegister.position[2]))
5377 {
5378 LOG_DEBUG("{}: referenceVectorConfigurationRegister.position.altitude changed to {}", nameId(), _referenceVectorConfigurationRegister.position[2]);
5379 flow::ApplyChanges();
5380 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5381 {
5382 try
5383 {
5384 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
5385 }
5386 catch (const std::exception& e)
5387 {
5388 LOG_ERROR("{}: Could not configure the referenceVectorConfigurationRegister: {}", nameId(), e.what());
5389 doDeinitialize();
5390 }
5391 }
5392 else
5393 {
5394 doDeinitialize();
5395 }
5396 }
5397 ImGui::SameLine();
5398 gui::widgets::HelpMarker("The reference altitude above the reference ellipsoid in meters.");
5399
5400 ImGui::TreePop();
5401 }
5402 }
5403
5404 // ###########################################################################################################
5405 // VELOCITY AIDING
5406 // ###########################################################################################################
5407
5408 if (_sensorModel == VectorNavModel::VN100_VN110)
5409 {
5410 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
5411 if (ImGui::CollapsingHeader(fmt::format("Velocity Aiding##{}", size_t(id)).c_str()))
5412 {
5413 if (ImGui::TreeNode(fmt::format("Velocity Compensation Control##{}", size_t(id)).c_str()))
5414 {
5415 ImGui::TextUnformatted("Provides control over the velocity compensation feature for the attitude filter.");
5416
5417 static constexpr std::array<vn::protocol::uart::VelocityCompensationMode, 2> velocityCompensationControlModes = {
5418 { vn::protocol::uart::VelocityCompensationMode::VELOCITYCOMPENSATIONMODE_DISABLED,
5419 vn::protocol::uart::VelocityCompensationMode::VELOCITYCOMPENSATIONMODE_BODYMEASUREMENT }
5420 };
5421 if (ImGui::BeginCombo(fmt::format("Mode##{}", size_t(id)).c_str(), vn::protocol::uart::str(_velocityCompensationControlRegister.mode).c_str()))
5422 {
5423 for (const auto& velocityCompensationControlMode : velocityCompensationControlModes)
5424 {
5425 const bool isSelected = (_velocityCompensationControlRegister.mode == velocityCompensationControlMode);
5426 if (ImGui::Selectable(vn::protocol::uart::str(velocityCompensationControlMode).c_str(), isSelected))
5427 {
5428 _velocityCompensationControlRegister.mode = velocityCompensationControlMode;
5429 LOG_DEBUG("{}: velocityCompensationControlRegister.mode changed to {}", nameId(), vn::protocol::uart::str(_velocityCompensationControlRegister.mode));
5430 flow::ApplyChanges();
5431 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5432 {
5433 try
5434 {
5435 _vs.writeVelocityCompensationControl(_velocityCompensationControlRegister);
5436 }
5437 catch (const std::exception& e)
5438 {
5439 LOG_ERROR("{}: Could not configure the velocityCompensationControlRegister: {}", nameId(), e.what());
5440 doDeinitialize();
5441 }
5442 }
5443 else
5444 {
5445 doDeinitialize();
5446 }
5447 }
5448
5449 if (isSelected) // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
5450 {
5451 ImGui::SetItemDefaultFocus();
5452 }
5453 }
5454 ImGui::EndCombo();
5455 }
5456 ImGui::SameLine();
5457 gui::widgets::HelpMarker("Selects the type of velocity compensation performed by the VPE");
5458
5459 if (ImGui::InputFloat(fmt::format("Velocity Tuning##{}", size_t(id)).c_str(), &_velocityCompensationControlRegister.velocityTuning))
5460 {
5461 LOG_DEBUG("{}: velocityCompensationControlRegister.velocityTuning changed to {}", nameId(), _velocityCompensationControlRegister.velocityTuning);
5462 flow::ApplyChanges();
5463 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5464 {
5465 try
5466 {
5467 _vs.writeVelocityCompensationControl(_velocityCompensationControlRegister);
5468 }
5469 catch (const std::exception& e)
5470 {
5471 LOG_ERROR("{}: Could not configure the velocityCompensationControlRegister: {}", nameId(), e.what());
5472 doDeinitialize();
5473 }
5474 }
5475 else
5476 {
5477 doDeinitialize();
5478 }
5479 }
5480 ImGui::SameLine();
5481 gui::widgets::HelpMarker("Tuning parameter for the velocity measurement");
5482
5483 if (ImGui::InputFloat(fmt::format("Rate Tuning##{}", size_t(id)).c_str(), &_velocityCompensationControlRegister.rateTuning))
5484 {
5485 LOG_DEBUG("{}: velocityCompensationControlRegister.rateTuning changed to {}", nameId(), _velocityCompensationControlRegister.rateTuning);
5486 flow::ApplyChanges();
5487 if (isInitialized() && _vs.isConnected() && _vs.verifySensorConnectivity())
5488 {
5489 try
5490 {
5491 _vs.writeVelocityCompensationControl(_velocityCompensationControlRegister);
5492 }
5493 catch (const std::exception& e)
5494 {
5495 LOG_ERROR("{}: Could not configure the velocityCompensationControlRegister: {}", nameId(), e.what());
5496 doDeinitialize();
5497 }
5498 }
5499 else
5500 {
5501 doDeinitialize();
5502 }
5503 }
5504 ImGui::SameLine();
5505 gui::widgets::HelpMarker("Tuning parameter for the angular rate measurement");
5506
5507 ImGui::TreePop();
5508 }
5509 }
5510 }
5511 }
5512
5513 [[nodiscard]] json NAV::VectorNavSensor::save() const
5514 {
5515 LOG_TRACE("{}: called", nameId());
5516
5517 json j;
5518
5519 j["UartSensor"] = UartSensor::save();
5520 j["sensorModel"] = _sensorModel;
5521
5522 // ###########################################################################################################
5523 // SYSTEM MODULE
5524 // ###########################################################################################################
5525
5526 j["asyncDataOutputType"] = _asyncDataOutputType;
5527 j["asyncDataOutputFrequency"] = _asyncDataOutputFrequency;
5528 j["asciiOutputBufferSize"] = _asciiOutputBufferSize;
5529 j["syncInPin"] = _syncInPin;
5530 j["synchronizationControlRegister"] = _synchronizationControlRegister;
5531 j["communicationProtocolControlRegister"] = _communicationProtocolControlRegister;
5532 j["binaryOutputRegisterMerge"] = _binaryOutputRegisterMerge;
5533 for (size_t b = 0; b < 3; b++)
5534 {
5535 j[fmt::format("binaryOutputRegister{}", b + 1)] = _binaryOutputRegister.at(b);
5536 j[fmt::format("binaryOutputRegister{}-Frequency", b + 1)] = _dividerFrequency.second.at(static_cast<size_t>(_binaryOutputSelectedFrequency.at(b)));
5537 }
5538
5539 // ###########################################################################################################
5540 // IMU SUBSYSTEM
5541 // ###########################################################################################################
5542
5543 j["referenceFrameRotationMatrix"] = _referenceFrameRotationMatrix;
5544 j["imuFilteringConfigurationRegister"] = _imuFilteringConfigurationRegister;
5545 j["deltaThetaAndDeltaVelocityConfigurationRegister"] = _deltaThetaAndDeltaVelocityConfigurationRegister;
5546 j["coupleImuRateOutput"] = _coupleImuRateOutput;
5547
5548 // ###########################################################################################################
5549 // GNSS SUBSYSTEM
5550 // ###########################################################################################################
5551
5552 j["gpsConfigurationRegister"] = _gpsConfigurationRegister;
5553 j["gpsAntennaOffset"] = _gpsAntennaOffset;
5554 j["gpsCompassBaselineRegister"] = _gpsCompassBaselineRegister;
5555
5556 // ###########################################################################################################
5557 // ATTITUDE SUBSYSTEM
5558 // ###########################################################################################################
5559
5560 j["vpeBasicControlRegister"] = _vpeBasicControlRegister;
5561 j["vpeMagnetometerBasicTuningRegister"] = _vpeMagnetometerBasicTuningRegister;
5562 j["vpeAccelerometerBasicTuningRegister"] = _vpeAccelerometerBasicTuningRegister;
5563 j["vpeGyroBasicTuningRegister"] = _vpeGyroBasicTuningRegister;
5564 j["filterStartupGyroBias"] = _filterStartupGyroBias;
5565
5566 // ###########################################################################################################
5567 // INS SUBSYSTEM
5568 // ###########################################################################################################
5569
5570 j["insBasicConfigurationRegisterVn300"] = _insBasicConfigurationRegisterVn300;
5571 j["startupFilterBiasEstimateRegister"] = _startupFilterBiasEstimateRegister;
5572
5573 // ###########################################################################################################
5574 // HARD/SOFT IRON ESTIMATOR SUBSYSTEM
5575 // ###########################################################################################################
5576
5577 j["magnetometerCalibrationControlRegister"] = _magnetometerCalibrationControlRegister;
5578
5579 // ###########################################################################################################
5580 // WORLD MAGNETIC & GRAVITY MODULE
5581 // ###########################################################################################################
5582
5583 j["magneticAndGravityReferenceVectorsRegister"] = _magneticAndGravityReferenceVectorsRegister;
5584 j["referenceVectorConfigurationRegister"] = _referenceVectorConfigurationRegister;
5585
5586 // ###########################################################################################################
5587 // VELOCITY AIDING
5588 // ###########################################################################################################
5589
5590 j["velocityCompensationControlRegister"] = _velocityCompensationControlRegister;
5591
5592 return j;
5593 }
5594
5595 void NAV::VectorNavSensor::restore(json const& j)
5596 {
5597 LOG_TRACE("{}: called", nameId());
5598
5599 if (j.contains("UartSensor"))
5600 {
5601 UartSensor::restore(j.at("UartSensor"));
5602 }
5603 if (j.contains("sensorModel"))
5604 {
5605 j.at("sensorModel").get_to(_sensorModel);
5606 }
5607
5608 // ###########################################################################################################
5609 // SYSTEM MODULE
5610 // ###########################################################################################################
5611
5612 if (j.contains("asyncDataOutputType"))
5613 {
5614 j.at("asyncDataOutputType").get_to(_asyncDataOutputType);
5615 }
5616 if (j.contains("asyncDataOutputFrequency"))
5617 {
5618 j.at("asyncDataOutputFrequency").get_to(_asyncDataOutputFrequency);
5619 _asyncDataOutputFrequencySelected = static_cast<int>(std::ranges::find(_possibleAsyncDataOutputFrequency, static_cast<int>(_asyncDataOutputFrequency))
5620 - _possibleAsyncDataOutputFrequency.begin());
5621 }
5622 if (j.contains("asciiOutputBufferSize"))
5623 {
5624 j.at("asciiOutputBufferSize").get_to(_asciiOutputBufferSize);
5625 _asciiOutputBuffer.resize(static_cast<size_t>(_asciiOutputBufferSize));
5626 }
5627 if (j.contains("syncInPin"))
5628 {
5629 j.at("syncInPin").get_to(_syncInPin);
5630 if (_syncInPin && inputPins.empty())
5631 {
5632 nm::CreateInputPin(this, "SyncIn", Pin::Type::Object, { "TimeSync" });
5633 }
5634 }
5635 if (j.contains("synchronizationControlRegister"))
5636 {
5637 j.at("synchronizationControlRegister").get_to(_synchronizationControlRegister);
5638 if (_synchronizationControlRegister.syncOutMode == vn::protocol::uart::SyncOutMode::SYNCOUTMODE_GPSPPS
5639 && outputPins.size() <= 4)
5640 {
5641 nm::CreateOutputPin(this, "SyncOut", Pin::Type::Object, { "TimeSync" }, &_timeSyncOut);
5642 }
5643 }
5644 if (j.contains("communicationProtocolControlRegister"))
5645 {
5646 j.at("communicationProtocolControlRegister").get_to(_communicationProtocolControlRegister);
5647 }
5648 if (j.contains("binaryOutputRegisterMerge"))
5649 {
5650 j.at("binaryOutputRegisterMerge").get_to(_binaryOutputRegisterMerge);
5651 if (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output2)
5652 {
5653 outputPins.at(1).type = Pin::Type::Flow;
5654 outputPins.at(2).type = Pin::Type::None;
5655 outputPins.at(3).type = Pin::Type::Flow;
5656 }
5657 else if (_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output3
5658 || _binaryOutputRegisterMerge == BinaryRegisterMerge::Output2_Output3)
5659 {
5660 outputPins.at(1).type = Pin::Type::Flow;
5661 outputPins.at(2).type = Pin::Type::Flow;
5662 outputPins.at(3).type = Pin::Type::None;
5663 }
5664 }
5665 for (size_t b = 0; b < 3; b++)
5666 {
5667 if (j.contains(fmt::format("binaryOutputRegister{}", b + 1)))
5668 {
5669 j.at(fmt::format("binaryOutputRegister{}", b + 1)).get_to(_binaryOutputRegister.at(b));
5670 }
5671 if (j.contains(fmt::format("binaryOutputRegister{}-Frequency", b + 1)))
5672 {
5673 std::string frequency;
5674 j.at(fmt::format("binaryOutputRegister{}-Frequency", b + 1)).get_to(frequency);
5675 for (size_t i = 0; i < _dividerFrequency.second.size(); i++)
5676 {
5677 if (_dividerFrequency.second.at(i) == frequency)
5678 {
5679 _binaryOutputSelectedFrequency.at(b) = i;
5680 break;
5681 }
5682 }
5683 }
5684 }
5685
5686 // ###########################################################################################################
5687 // IMU SUBSYSTEM
5688 // ###########################################################################################################
5689
5690 if (j.contains("referenceFrameRotationMatrix"))
5691 {
5692 j.at("referenceFrameRotationMatrix").get_to(_referenceFrameRotationMatrix);
5693 }
5694 if (j.contains("imuFilteringConfigurationRegister"))
5695 {
5696 j.at("imuFilteringConfigurationRegister").get_to(_imuFilteringConfigurationRegister);
5697 }
5698 if (j.contains("deltaThetaAndDeltaVelocityConfigurationRegister"))
5699 {
5700 j.at("deltaThetaAndDeltaVelocityConfigurationRegister").get_to(_deltaThetaAndDeltaVelocityConfigurationRegister);
5701 }
5702 if (j.contains("coupleImuRateOutput"))
5703 {
5704 j.at("coupleImuRateOutput").get_to(_coupleImuRateOutput);
5705 }
5706
5707 // ###########################################################################################################
5708 // GNSS SUBSYSTEM
5709 // ###########################################################################################################
5710
5711 if (j.contains("gpsConfigurationRegister"))
5712 {
5713 j.at("gpsConfigurationRegister").get_to(_gpsConfigurationRegister);
5714 }
5715 if (j.contains("gpsAntennaOffset"))
5716 {
5717 j.at("gpsAntennaOffset").get_to(_gpsAntennaOffset);
5718 }
5719 if (j.contains("gpsCompassBaselineRegister"))
5720 {
5721 j.at("gpsCompassBaselineRegister").get_to(_gpsCompassBaselineRegister);
5722 }
5723
5724 // ###########################################################################################################
5725 // ATTITUDE SUBSYSTEM
5726 // ###########################################################################################################
5727
5728 if (j.contains("vpeBasicControlRegister"))
5729 {
5730 j.at("vpeBasicControlRegister").get_to(_vpeBasicControlRegister);
5731 }
5732 if (j.contains("vpeMagnetometerBasicTuningRegister"))
5733 {
5734 j.at("vpeMagnetometerBasicTuningRegister").get_to(_vpeMagnetometerBasicTuningRegister);
5735 }
5736 if (j.contains("vpeAccelerometerBasicTuningRegister"))
5737 {
5738 j.at("vpeAccelerometerBasicTuningRegister").get_to(_vpeAccelerometerBasicTuningRegister);
5739 }
5740 if (j.contains("vpeGyroBasicTuningRegister"))
5741 {
5742 j.at("vpeGyroBasicTuningRegister").get_to(_vpeGyroBasicTuningRegister);
5743 }
5744 if (j.contains("filterStartupGyroBias"))
5745 {
5746 j.at("filterStartupGyroBias").get_to(_filterStartupGyroBias);
5747 }
5748
5749 // ###########################################################################################################
5750 // INS SUBSYSTEM
5751 // ###########################################################################################################
5752
5753 if (j.contains("insBasicConfigurationRegisterVn300"))
5754 {
5755 j.at("insBasicConfigurationRegisterVn300").get_to(_insBasicConfigurationRegisterVn300);
5756 }
5757 if (j.contains("startupFilterBiasEstimateRegister"))
5758 {
5759 j.at("startupFilterBiasEstimateRegister").get_to(_startupFilterBiasEstimateRegister);
5760 }
5761
5762 // ###########################################################################################################
5763 // HARD/SOFT IRON ESTIMATOR SUBSYSTEM
5764 // ###########################################################################################################
5765
5766 if (j.contains("magnetometerCalibrationControlRegister"))
5767 {
5768 j.at("magnetometerCalibrationControlRegister").get_to(_magnetometerCalibrationControlRegister);
5769 }
5770
5771 // ###########################################################################################################
5772 // WORLD MAGNETIC & GRAVITY MODULE
5773 // ###########################################################################################################
5774
5775 if (j.contains("magneticAndGravityReferenceVectorsRegister"))
5776 {
5777 j.at("magneticAndGravityReferenceVectorsRegister").get_to(_magneticAndGravityReferenceVectorsRegister);
5778 }
5779 if (j.contains("referenceVectorConfigurationRegister"))
5780 {
5781 j.at("referenceVectorConfigurationRegister").get_to(_referenceVectorConfigurationRegister);
5782 }
5783
5784 // ###########################################################################################################
5785 // VELOCITY AIDING
5786 // ###########################################################################################################
5787
5788 if (j.contains("velocityCompensationControlRegister"))
5789 {
5790 j.at("velocityCompensationControlRegister").get_to(_velocityCompensationControlRegister);
5791 }
5792 }
5793
5794 bool NAV::VectorNavSensor::resetNode()
5795 {
5796 std::ranges::for_each(_lastMessageTime, [](InsTime& insTime) { insTime.reset(); });
5797 std::ranges::for_each(_lastMessageTimeSinceStartup, [](uint64_t& value) { value = 0; });
5798 _gnssTimeCounter = {};
5799
5800 _timeSyncOut.ppsTime.reset();
5801 _timeSyncOut.syncOutCnt = 0;
5802 _binaryOutputRegisterMergeObservation = nullptr;
5803
5804 return true;
5805 }
5806
5807 bool NAV::VectorNavSensor::initialize()
5808 {
5809 LOG_TRACE("{}: called", nameId());
5810
5811 // Some settings need to be wrote to the device and reset afterwards
5812 bool deviceNeedsResetAfterInitialization = false;
5813
5814 // ###########################################################################################################
5815 // Connecting
5816 // ###########################################################################################################
5817
5818 // Choose baudrate
5819 Baudrate targetBaudrate = sensorBaudrate() == BAUDRATE_FASTEST
5820 ? static_cast<Baudrate>(vn::sensors::VnSensor::supportedBaudrates()[vn::sensors::VnSensor::supportedBaudrates().size() - 1])
5821 : sensorBaudrate();
5822
5823 Baudrate connectedBaudrate{};
5824 _connectedSensorPort.clear();
5825
5826 // Search for the VectorNav Sensor
5827 size_t maxTries = std::filesystem::exists(_sensorPort) ? 5 : 0;
5828 for (size_t i = 0; i < maxTries; ++i)
5829 {
5830 if (int32_t foundBaudrate = 0;
5831 vn::sensors::Searcher::search(_sensorPort, &foundBaudrate))
5832 {
5833 // Sensor was found at specified port with the baudrate 'foundBaudrate'
5834 connectedBaudrate = static_cast<Baudrate>(foundBaudrate);
5835 _connectedSensorPort = _sensorPort;
5836 LOG_DEBUG("{}: Found VectorNav sensor on specified port '{}' with baudrate {} (it took {} attempts to connect)",
5837 nameId(), _connectedSensorPort, foundBaudrate, i + 1);
5838 break;
5839 }
5840 }
5841 if (_connectedSensorPort.empty())
5842 {
5843 if (std::filesystem::exists(_sensorPort))
5844 {
5845 LOG_ERROR("{}: Could not connect to VectorNav sensor on specified port '{}', but port exists.", nameId(), _sensorPort);
5846 return false;
5847 }
5848
5849 for (size_t i = 0; i < 5; ++i)
5850 {
5851 if (!_connectedSensorPort.empty())
5852 {
5853 break;
5854 }
5855
5856 if (std::vector<std::pair<std::string, uint32_t>> foundSensors = vn::sensors::Searcher::search();
5857 !foundSensors.empty())
5858 {
5859 for (const auto& [port, baudrate] : foundSensors)
5860 {
5861 _vs.connect(port, baudrate);
5862 std::string modelNumber = _vs.readModelNumber();
5863 _vs.disconnect();
5864
5865 LOG_DEBUG("{}: Found VectorNav sensor '{}' on port '{}' with baudrate {} ({} VN sensors connected in total)",
5866 nameId(), modelNumber, port, baudrate, foundSensors.size());
5867
5868 // Identify the sensor by its model number
5869 const std::string searchString = _sensorModel == VectorNavModel::VN100_VN110 ? "VN-1" : "VN-3";
5870 if (modelNumber.find(searchString) != std::string::npos)
5871 {
5872 _connectedSensorPort = port;
5873 connectedBaudrate = static_cast<Baudrate>(baudrate);
5874
5875 LOG_DEBUG("{}: Selected VectorNav sensor '{}' on port '{}' (it took {} attempts to connect)",
5876 nameId(), modelNumber, port, i + 1);
5877 break;
5878 }
5879 }
5880 }
5881 }
5882 if (_connectedSensorPort.empty())
5883 {
5884 LOG_ERROR("{}: Could not connect. Is the sensor connected and do you have read permissions?", nameId());
5885 return false;
5886 }
5887 }
5888
5889 try
5890 {
5891 // Connect to the sensor (vs.verifySensorConnectivity does not have to be called as sensor is already tested)
5892 _vs.connect(_connectedSensorPort, connectedBaudrate);
5893 }
5894 catch (const std::exception& e)
5895 {
5896 LOG_ERROR("{}: Failed to connect to sensor on port '{}' with baudrate {} with error: {}", nameId(),
5897 _connectedSensorPort, connectedBaudrate, e.what());
5898 return false;
5899 }
5900
5901 try
5902 {
5903 if (!_vs.verifySensorConnectivity())
5904 {
5905 LOG_ERROR("{}: Connected to sensor on port '{}' with baudrate {} but sensor does not answer", nameId(),
5906 _connectedSensorPort, connectedBaudrate);
5907 return false;
5908 }
5909 }
5910 catch (const std::exception& e)
5911 {
5912 LOG_ERROR("{}: Connected to sensor on port '{}' with baudrate {} but sensor threw an exception: {}", nameId(),
5913 _connectedSensorPort, connectedBaudrate, e.what());
5914 return false;
5915 }
5916
5917 // Query the sensor's model number
5918 auto modelNumber = _vs.readModelNumber();
5919 LOG_DEBUG("{}: {} connected on port '{}' with baudrate {}", nameId(), _vs.readModelNumber(), _connectedSensorPort, connectedBaudrate);
5920
5921 if (_sensorModel == VectorNavModel::VN100_VN110 && (!modelNumber.starts_with("VN-100") && !modelNumber.starts_with("VN-110")))
5922 {
5923 LOG_ERROR("{}: VN100/VN110 configured, but sensor identifies as {}", nameId(), modelNumber);
5924 doDeinitialize();
5925 return false;
5926 }
5927 if (_sensorModel == VectorNavModel::VN310 && !modelNumber.starts_with("VN-310"))
5928 {
5929 LOG_ERROR("{}: VN310 configured, but sensor identifies as {}", nameId(), modelNumber);
5930 doDeinitialize();
5931 return false;
5932 }
5933
5934 // ###########################################################################################################
5935 // SYSTEM MODULE
5936 // ###########################################################################################################
5937
5938 // Change Connection Baudrate
5939 if (targetBaudrate != connectedBaudrate)
5940 {
5941 auto suppBaud = vn::sensors::VnSensor::supportedBaudrates();
5942 if (std::ranges::find(suppBaud, targetBaudrate) != suppBaud.end())
5943 {
5944 _vs.changeBaudRate(targetBaudrate);
5945 LOG_DEBUG("{}: Baudrate changed to {}", nameId(), static_cast<size_t>(targetBaudrate));
5946 }
5947 else
5948 {
5949 LOG_ERROR("{}: Does not support baudrate {}", nameId(), static_cast<size_t>(targetBaudrate));
5950 return false;
5951 }
5952 }
5953 if (_vs.readSerialBaudRate() != targetBaudrate)
5954 {
5955 LOG_ERROR("{}: Changing the baudrate from {} to {} was not successfull", nameId(), _vs.readSerialBaudRate(), targetBaudrate);
5956 doDeinitialize();
5957 return false;
5958 }
5959
5960 _vs.writeSynchronizationControl(_synchronizationControlRegister);
5961 if (auto vnSynchronizationControlRegister = _vs.readSynchronizationControl();
5962 vnSynchronizationControlRegister.syncInMode != _synchronizationControlRegister.syncInMode
5963 || vnSynchronizationControlRegister.syncInEdge != _synchronizationControlRegister.syncInEdge
5964 || vnSynchronizationControlRegister.syncInSkipFactor != _synchronizationControlRegister.syncInSkipFactor
5965 || vnSynchronizationControlRegister.syncOutMode != _synchronizationControlRegister.syncOutMode
5966 || vnSynchronizationControlRegister.syncOutPolarity != _synchronizationControlRegister.syncOutPolarity
5967 || vnSynchronizationControlRegister.syncOutSkipFactor != _synchronizationControlRegister.syncOutSkipFactor
5968 || vnSynchronizationControlRegister.syncOutPulseWidth != _synchronizationControlRegister.syncOutPulseWidth)
5969 {
5970 LOG_ERROR("{}: Writing the synchronizationControlRegister was not successfull.\n"
5971 "Target: syncInMode = {}, syncInEdge = {}, syncInSkipFactor = {}, syncOutMode = {}, syncOutPolarity = {}, syncOutSkipFactor = {}, syncOutPulseWidth = {}\n"
5972 "Sensor: syncInMode = {}, syncInEdge = {}, syncInSkipFactor = {}, syncOutMode = {}, syncOutPolarity = {}, syncOutSkipFactor = {}, syncOutPulseWidth = {}",
5973 nameId(),
5974 _synchronizationControlRegister.syncInMode, _synchronizationControlRegister.syncInEdge, _synchronizationControlRegister.syncInSkipFactor, _synchronizationControlRegister.syncOutMode, _synchronizationControlRegister.syncOutPolarity, _synchronizationControlRegister.syncOutSkipFactor, _synchronizationControlRegister.syncOutPulseWidth,
5975 vnSynchronizationControlRegister.syncInMode, vnSynchronizationControlRegister.syncInEdge, vnSynchronizationControlRegister.syncInSkipFactor, vnSynchronizationControlRegister.syncOutMode, vnSynchronizationControlRegister.syncOutPolarity, vnSynchronizationControlRegister.syncOutSkipFactor, vnSynchronizationControlRegister.syncOutPulseWidth);
5976 doDeinitialize();
5977 return false;
5978 }
5979
5980 _vs.writeCommunicationProtocolControl(_communicationProtocolControlRegister);
5981 if (auto vnCommunicationProtocolControlRegister = _vs.readCommunicationProtocolControl();
5982 vnCommunicationProtocolControlRegister.serialCount != _communicationProtocolControlRegister.serialCount
5983 || vnCommunicationProtocolControlRegister.serialStatus != _communicationProtocolControlRegister.serialStatus
5984 || vnCommunicationProtocolControlRegister.spiCount != _communicationProtocolControlRegister.spiCount
5985 || vnCommunicationProtocolControlRegister.spiStatus != _communicationProtocolControlRegister.spiStatus
5986 || vnCommunicationProtocolControlRegister.serialChecksum != _communicationProtocolControlRegister.serialChecksum
5987 || vnCommunicationProtocolControlRegister.spiChecksum != _communicationProtocolControlRegister.spiChecksum
5988 || vnCommunicationProtocolControlRegister.errorMode != _communicationProtocolControlRegister.errorMode)
5989 {
5990 LOG_ERROR("{}: Writing the communicationProtocolControlRegister was not successfull.\n"
5991 "Target: serialCount = {}, serialStatus = {}, spiCount = {}, spiStatus = {}, serialChecksum = {}, spiChecksum = {}, errorMode = {}\n"
5992 "Sensor: serialCount = {}, serialStatus = {}, spiCount = {}, spiStatus = {}, serialChecksum = {}, spiChecksum = {}, errorMode = {}",
5993 nameId(),
5994 _communicationProtocolControlRegister.serialCount, _communicationProtocolControlRegister.serialStatus, _communicationProtocolControlRegister.spiCount, _communicationProtocolControlRegister.spiStatus, _communicationProtocolControlRegister.serialChecksum, _communicationProtocolControlRegister.spiChecksum, _communicationProtocolControlRegister.errorMode,
5995 vnCommunicationProtocolControlRegister.serialCount, vnCommunicationProtocolControlRegister.serialStatus, vnCommunicationProtocolControlRegister.spiCount, vnCommunicationProtocolControlRegister.spiStatus, vnCommunicationProtocolControlRegister.serialChecksum, vnCommunicationProtocolControlRegister.spiChecksum, vnCommunicationProtocolControlRegister.errorMode);
5996 doDeinitialize();
5997 return false;
5998 }
5999
6000 // _vs.writeSynchronizationStatus(vn::sensors::SynchronizationStatusRegister); // User manual VN-310 - 8.3.1 (p 105) / VN-100 - 5.3.1 (p 76)
6001
6002 // ###########################################################################################################
6003 // IMU SUBSYSTEM
6004 // ###########################################################################################################
6005
6006 // _vs.writeMagnetometerCompensation(vn::sensors::MagnetometerCompensationRegister()); // User manual VN-310 - 9.2.1 (p 111) / VN-100 - 6.2.1 (p 82)
6007 // _vs.writeAccelerationCompensation(vn::sensors::AccelerationCompensationRegister()); // User manual VN-310 - 9.2.2 (p 112) / VN-100 - 6.2.2 (p 83)
6008 // _vs.writeGyroCompensation(vn::sensors::GyroCompensationRegister()); // User manual VN-310 - 9.2.3 (p 113) / VN-100 - 6.2.3 (p 84)
6009
6010 auto vnReferenceFrameRotationMatrix = _vs.readReferenceFrameRotation();
6011 if (vnReferenceFrameRotationMatrix != _referenceFrameRotationMatrix)
6012 {
6013 deviceNeedsResetAfterInitialization = true;
6014 }
6015
6016 _vs.writeReferenceFrameRotation(_referenceFrameRotationMatrix);
6017 if (vnReferenceFrameRotationMatrix = _vs.readReferenceFrameRotation();
6018 vnReferenceFrameRotationMatrix != _referenceFrameRotationMatrix)
6019 {
6020 LOG_ERROR("{}: Writing the referenceFrameRotationMatrix was not successfull.\n"
6021 "Target: DCM = {}\n"
6022 "Sensor: DCM = {}",
6023 nameId(), _referenceFrameRotationMatrix, vnReferenceFrameRotationMatrix);
6024 doDeinitialize();
6025 return false;
6026 }
6027
6028 _vs.writeImuFilteringConfiguration(_imuFilteringConfigurationRegister);
6029 if (auto vnImuFilteringConfigurationRegister = _vs.readImuFilteringConfiguration();
6030 vnImuFilteringConfigurationRegister.magWindowSize != _imuFilteringConfigurationRegister.magWindowSize
6031 || vnImuFilteringConfigurationRegister.accelWindowSize != _imuFilteringConfigurationRegister.accelWindowSize
6032 || vnImuFilteringConfigurationRegister.gyroWindowSize != _imuFilteringConfigurationRegister.gyroWindowSize
6033 || vnImuFilteringConfigurationRegister.tempWindowSize != _imuFilteringConfigurationRegister.tempWindowSize
6034 || vnImuFilteringConfigurationRegister.presWindowSize != _imuFilteringConfigurationRegister.presWindowSize
6035 || vnImuFilteringConfigurationRegister.magFilterMode != _imuFilteringConfigurationRegister.magFilterMode
6036 || vnImuFilteringConfigurationRegister.accelFilterMode != _imuFilteringConfigurationRegister.accelFilterMode
6037 || vnImuFilteringConfigurationRegister.gyroFilterMode != _imuFilteringConfigurationRegister.gyroFilterMode
6038 || vnImuFilteringConfigurationRegister.tempFilterMode != _imuFilteringConfigurationRegister.tempFilterMode
6039 || vnImuFilteringConfigurationRegister.presFilterMode != _imuFilteringConfigurationRegister.presFilterMode)
6040 {
6041 LOG_ERROR("{}: Writing the imuFilteringConfigurationRegister was not successfull.\n"
6042 "Target: magWindowSize = {}, accelWindowSize = {}, gyroWindowSize = {}, tempWindowSize = {}, presWindowSize = {}, magFilterMode = {}, accelFilterMode = {}, gyroFilterMode = {}, tempFilterMode = {}, presFilterMode = {}\n"
6043 "Sensor: magWindowSize = {}, accelWindowSize = {}, gyroWindowSize = {}, tempWindowSize = {}, presWindowSize = {}, magFilterMode = {}, accelFilterMode = {}, gyroFilterMode = {}, tempFilterMode = {}, presFilterMode = {}",
6044 nameId(),
6045 _imuFilteringConfigurationRegister.magWindowSize, _imuFilteringConfigurationRegister.accelWindowSize, _imuFilteringConfigurationRegister.gyroWindowSize, _imuFilteringConfigurationRegister.tempWindowSize, _imuFilteringConfigurationRegister.presWindowSize, _imuFilteringConfigurationRegister.magFilterMode, _imuFilteringConfigurationRegister.accelFilterMode, _imuFilteringConfigurationRegister.gyroFilterMode, _imuFilteringConfigurationRegister.tempFilterMode, _imuFilteringConfigurationRegister.presFilterMode,
6046 vnImuFilteringConfigurationRegister.magWindowSize, vnImuFilteringConfigurationRegister.accelWindowSize, vnImuFilteringConfigurationRegister.gyroWindowSize, vnImuFilteringConfigurationRegister.tempWindowSize, vnImuFilteringConfigurationRegister.presWindowSize, vnImuFilteringConfigurationRegister.magFilterMode, vnImuFilteringConfigurationRegister.accelFilterMode, vnImuFilteringConfigurationRegister.gyroFilterMode, vnImuFilteringConfigurationRegister.tempFilterMode, vnImuFilteringConfigurationRegister.presFilterMode);
6047 doDeinitialize();
6048 return false;
6049 }
6050
6051 _vs.writeDeltaThetaAndDeltaVelocityConfiguration(_deltaThetaAndDeltaVelocityConfigurationRegister);
6052 if (auto vnDeltaThetaAndDeltaVelocityConfigurationRegister = _vs.readDeltaThetaAndDeltaVelocityConfiguration();
6053 vnDeltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame != _deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame
6054 || vnDeltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation != _deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation
6055 || vnDeltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation != _deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation
6056 || vnDeltaThetaAndDeltaVelocityConfigurationRegister.earthRateCorrection != _deltaThetaAndDeltaVelocityConfigurationRegister.earthRateCorrection)
6057 {
6058 LOG_ERROR("{}: Writing the deltaThetaAndDeltaVelocityConfigurationRegister was not successfull.\n"
6059 "Target: integrationFrame = {}, gyroCompensation = {}, accelCompensation = {}, earthRateCorrection = {}\n"
6060 "Sensor: integrationFrame = {}, gyroCompensation = {}, accelCompensation = {}, earthRateCorrection = {}",
6061 nameId(),
6062 _deltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame, _deltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation, _deltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation, _deltaThetaAndDeltaVelocityConfigurationRegister.earthRateCorrection,
6063 vnDeltaThetaAndDeltaVelocityConfigurationRegister.integrationFrame, vnDeltaThetaAndDeltaVelocityConfigurationRegister.gyroCompensation, vnDeltaThetaAndDeltaVelocityConfigurationRegister.accelCompensation, vnDeltaThetaAndDeltaVelocityConfigurationRegister.earthRateCorrection);
6064 doDeinitialize();
6065 return false;
6066 }
6067
6068 // ###########################################################################################################
6069 // GNSS SUBSYSTEM
6070 // ###########################################################################################################
6071
6072 if (_sensorModel == VectorNavModel::VN310)
6073 {
6074 _vs.writeGpsConfiguration(_gpsConfigurationRegister);
6075 if (auto vnGpsConfigurationRegister = _vs.readGpsConfiguration();
6076 vnGpsConfigurationRegister.mode != _gpsConfigurationRegister.mode
6077 || vnGpsConfigurationRegister.ppsSource != _gpsConfigurationRegister.ppsSource
6078 || vnGpsConfigurationRegister.rate != _gpsConfigurationRegister.rate
6079 || vnGpsConfigurationRegister.antPow != _gpsConfigurationRegister.antPow)
6080 {
6081 LOG_ERROR("{}: Writing the gpsConfigurationRegister was not successfull.\n"
6082 "Target: mode = {}, ppsSource = {}, rate = {}, antPow = {}\n"
6083 "Sensor: mode = {}, ppsSource = {}, rate = {}, antPow = {}",
6084 nameId(),
6085 _gpsConfigurationRegister.mode, _gpsConfigurationRegister.ppsSource, _gpsConfigurationRegister.rate, _gpsConfigurationRegister.antPow,
6086 vnGpsConfigurationRegister.mode, vnGpsConfigurationRegister.ppsSource, vnGpsConfigurationRegister.rate, vnGpsConfigurationRegister.antPow);
6087 doDeinitialize();
6088 return false;
6089 }
6090
6091 _vs.writeGpsAntennaOffset(_gpsAntennaOffset);
6092 if (auto vnGpsAntennaOffset = _vs.readGpsAntennaOffset();
6093 vnGpsAntennaOffset != _gpsAntennaOffset)
6094 {
6095 LOG_ERROR("{}: Writing the gpsAntennaOffset was not successfull.\n"
6096 "Target: {}\n"
6097 "Sensor: {}",
6098 nameId(),
6099 _gpsAntennaOffset,
6100 vnGpsAntennaOffset);
6101 doDeinitialize();
6102 return false;
6103 }
6104
6105 _vs.writeGpsCompassBaseline(_gpsCompassBaselineRegister);
6106 if (auto vnGpsCompassBaselineRegister = _vs.readGpsCompassBaseline();
6107 vnGpsCompassBaselineRegister.position != _gpsCompassBaselineRegister.position
6108 || vnGpsCompassBaselineRegister.uncertainty != _gpsCompassBaselineRegister.uncertainty)
6109 {
6110 LOG_ERROR("{}: Writing the gpsCompassBaselineRegister was not successfull.\n"
6111 "Target: position = {}, uncertainty = {}\n"
6112 "Sensor: position = {}, uncertainty = {}",
6113 nameId(),
6114 _gpsCompassBaselineRegister.position, _gpsCompassBaselineRegister.uncertainty,
6115 vnGpsCompassBaselineRegister.position, vnGpsCompassBaselineRegister.uncertainty);
6116 doDeinitialize();
6117 return false;
6118 }
6119 }
6120
6121 // ###########################################################################################################
6122 // ATTITUDE SUBSYSTEM
6123 // ###########################################################################################################
6124 if (_sensorModel == VectorNavModel::VN100_VN110)
6125 {
6126 // _vs.tare(); // User manual VN-100 - 7.1.1 (p 92)
6127 // _vs.magneticDisturbancePresent(bool); // User manual VN-100 - 7.1.2 (p 92)
6128 // _vs.accelerationDisturbancePresent(true); // User manual VN-100 - 7.1.3 (p 92f)
6129 }
6130 // _vs.setGyroBias(); // User manual VN-310 - 11.1.1 (p 148) / VN-100 - 7.1.4 (p 93)
6131
6132 // TODO: Implement in vnproglib: _vs.setInitialHeading() - User manual VN-310 - 11.1.2 (p 148)
6133
6134 _vs.writeVpeBasicControl(_vpeBasicControlRegister);
6135 if (auto vnVpeBasicControlRegister = _vs.readVpeBasicControl();
6136 vnVpeBasicControlRegister.enable != _vpeBasicControlRegister.enable
6137 || vnVpeBasicControlRegister.headingMode != _vpeBasicControlRegister.headingMode
6138 || vnVpeBasicControlRegister.filteringMode != _vpeBasicControlRegister.filteringMode
6139 || vnVpeBasicControlRegister.tuningMode != _vpeBasicControlRegister.tuningMode)
6140 {
6141 LOG_ERROR("{}: Writing the vpeBasicControlRegister was not successfull.\n"
6142 "Target: enable = {}, headingMode = {}, filteringMode = {}, tuningMode = {}\n"
6143 "Sensor: enable = {}, headingMode = {}, filteringMode = {}, tuningMode = {}",
6144 nameId(),
6145 _vpeBasicControlRegister.enable, _vpeBasicControlRegister.headingMode, _vpeBasicControlRegister.filteringMode, _vpeBasicControlRegister.tuningMode,
6146 vnVpeBasicControlRegister.enable, vnVpeBasicControlRegister.headingMode, vnVpeBasicControlRegister.filteringMode, vnVpeBasicControlRegister.tuningMode);
6147 doDeinitialize();
6148 return false;
6149 }
6150
6151 if (_sensorModel == VectorNavModel::VN100_VN110)
6152 {
6153 _vs.writeVpeMagnetometerBasicTuning(_vpeMagnetometerBasicTuningRegister);
6154 if (auto vnVpeMagnetometerBasicTuningRegister = _vs.readVpeMagnetometerBasicTuning();
6155 vnVpeMagnetometerBasicTuningRegister.baseTuning != _vpeMagnetometerBasicTuningRegister.baseTuning
6156 || vnVpeMagnetometerBasicTuningRegister.adaptiveTuning != _vpeMagnetometerBasicTuningRegister.adaptiveTuning
6157 || vnVpeMagnetometerBasicTuningRegister.adaptiveFiltering != _vpeMagnetometerBasicTuningRegister.adaptiveFiltering)
6158 {
6159 LOG_ERROR("{}: Writing the vpeMagnetometerBasicTuningRegister was not successfull.\n"
6160 "Target: baseTuning = {}, adaptiveTuning = {}, adaptiveFiltering = {}\n"
6161 "Sensor: baseTuning = {}, adaptiveTuning = {}, adaptiveFiltering = {}",
6162 nameId(),
6163 _vpeMagnetometerBasicTuningRegister.baseTuning, _vpeMagnetometerBasicTuningRegister.adaptiveTuning, _vpeMagnetometerBasicTuningRegister.adaptiveFiltering,
6164 vnVpeMagnetometerBasicTuningRegister.baseTuning, vnVpeMagnetometerBasicTuningRegister.adaptiveTuning, vnVpeMagnetometerBasicTuningRegister.adaptiveFiltering);
6165 doDeinitialize();
6166 return false;
6167 }
6168
6169 _vs.writeVpeAccelerometerBasicTuning(_vpeAccelerometerBasicTuningRegister);
6170 if (auto vnVpeAccelerometerBasicTuningRegister = _vs.readVpeAccelerometerBasicTuning();
6171 vnVpeAccelerometerBasicTuningRegister.baseTuning != _vpeAccelerometerBasicTuningRegister.baseTuning
6172 || vnVpeAccelerometerBasicTuningRegister.adaptiveTuning != _vpeAccelerometerBasicTuningRegister.adaptiveTuning
6173 || vnVpeAccelerometerBasicTuningRegister.adaptiveFiltering != _vpeAccelerometerBasicTuningRegister.adaptiveFiltering)
6174 {
6175 LOG_ERROR("{}: Writing the vpeAccelerometerBasicTuningRegister was not successfull.\n"
6176 "Target: baseTuning = {}, adaptiveTuning = {}, adaptiveFiltering = {}\n"
6177 "Sensor: baseTuning = {}, adaptiveTuning = {}, adaptiveFiltering = {}",
6178 nameId(),
6179 _vpeAccelerometerBasicTuningRegister.baseTuning, _vpeAccelerometerBasicTuningRegister.adaptiveTuning, _vpeAccelerometerBasicTuningRegister.adaptiveFiltering,
6180 vnVpeAccelerometerBasicTuningRegister.baseTuning, vnVpeAccelerometerBasicTuningRegister.adaptiveTuning, vnVpeAccelerometerBasicTuningRegister.adaptiveFiltering);
6181 doDeinitialize();
6182 return false;
6183 }
6184
6185 _vs.writeFilterStartupGyroBias(_filterStartupGyroBias);
6186 if (auto vnFilterStartupGyroBias = _vs.readFilterStartupGyroBias();
6187 vnFilterStartupGyroBias != _filterStartupGyroBias)
6188 {
6189 LOG_ERROR("{}: Writing the filterStartupGyroBias was not successfull.\n"
6190 "Target: {}\n"
6191 "Sensor: {}",
6192 nameId(),
6193 _filterStartupGyroBias,
6194 vnFilterStartupGyroBias);
6195 doDeinitialize();
6196 return false;
6197 }
6198
6199 _vs.writeVpeGyroBasicTuning(_vpeGyroBasicTuningRegister);
6200 if (auto vnVpeGyroBasicTuningRegister = _vs.readVpeGyroBasicTuning();
6201 vnVpeGyroBasicTuningRegister.angularWalkVariance != _vpeGyroBasicTuningRegister.angularWalkVariance
6202 || vnVpeGyroBasicTuningRegister.baseTuning != _vpeGyroBasicTuningRegister.baseTuning
6203 || vnVpeGyroBasicTuningRegister.adaptiveTuning != _vpeGyroBasicTuningRegister.adaptiveTuning)
6204 {
6205 LOG_ERROR("{}: Writing the vpeGyroBasicTuningRegister was not successfull.\n"
6206 "Target: angularWalkVariance = {}, baseTuning = {}, adaptiveTuning = {}\n"
6207 "Sensor: angularWalkVariance = {}, baseTuning = {}, adaptiveTuning = {}",
6208 nameId(),
6209 _vpeGyroBasicTuningRegister.angularWalkVariance, _vpeGyroBasicTuningRegister.baseTuning, _vpeGyroBasicTuningRegister.adaptiveTuning,
6210 vnVpeGyroBasicTuningRegister.angularWalkVariance, vnVpeGyroBasicTuningRegister.baseTuning, vnVpeGyroBasicTuningRegister.adaptiveTuning);
6211 doDeinitialize();
6212 return false;
6213 }
6214 }
6215
6216 // ###########################################################################################################
6217 // INS SUBSYSTEM
6218 // ###########################################################################################################
6219
6220 if (_sensorModel == VectorNavModel::VN310)
6221 {
6222 _vs.writeInsBasicConfigurationVn300(_insBasicConfigurationRegisterVn300);
6223 if (auto vnInsBasicConfigurationRegisterVn300 = _vs.readInsBasicConfigurationVn300();
6224 vnInsBasicConfigurationRegisterVn300.scenario != _insBasicConfigurationRegisterVn300.scenario
6225 || vnInsBasicConfigurationRegisterVn300.ahrsAiding != _insBasicConfigurationRegisterVn300.ahrsAiding
6226 || vnInsBasicConfigurationRegisterVn300.estBaseline != _insBasicConfigurationRegisterVn300.estBaseline)
6227 {
6228 LOG_ERROR("{}: Writing the insBasicConfigurationRegisterVn300 was not successfull.\n"
6229 "Target: scenario = {}, ahrsAiding = {}, estBaseline = {}\n"
6230 "Sensor: scenario = {}, ahrsAiding = {}, estBaseline = {}",
6231 nameId(),
6232 _insBasicConfigurationRegisterVn300.scenario, _insBasicConfigurationRegisterVn300.ahrsAiding, _insBasicConfigurationRegisterVn300.estBaseline,
6233 vnInsBasicConfigurationRegisterVn300.scenario, vnInsBasicConfigurationRegisterVn300.ahrsAiding, vnInsBasicConfigurationRegisterVn300.estBaseline);
6234 doDeinitialize();
6235 return false;
6236 }
6237
6238 _vs.writeStartupFilterBiasEstimate(_startupFilterBiasEstimateRegister);
6239 if (auto vnStartupFilterBiasEstimateRegister = _vs.readStartupFilterBiasEstimate();
6240 vnStartupFilterBiasEstimateRegister.gyroBias != _startupFilterBiasEstimateRegister.gyroBias
6241 || vnStartupFilterBiasEstimateRegister.accelBias != _startupFilterBiasEstimateRegister.accelBias
6242 || vnStartupFilterBiasEstimateRegister.pressureBias != _startupFilterBiasEstimateRegister.pressureBias)
6243 {
6244 LOG_ERROR("{}: Writing the startupFilterBiasEstimateRegister was not successfull.\n"
6245 "Target: gyroBias = {}, accelBias = {}, pressureBias = {}\n"
6246 "Sensor: gyroBias = {}, accelBias = {}, pressureBias = {}",
6247 nameId(),
6248 _startupFilterBiasEstimateRegister.gyroBias, _startupFilterBiasEstimateRegister.accelBias, _startupFilterBiasEstimateRegister.pressureBias,
6249 vnStartupFilterBiasEstimateRegister.gyroBias, vnStartupFilterBiasEstimateRegister.accelBias, vnStartupFilterBiasEstimateRegister.pressureBias);
6250 doDeinitialize();
6251 return false;
6252 }
6253 }
6254
6255 // ###########################################################################################################
6256 // HARD/SOFT IRON ESTIMATOR SUBSYSTEM
6257 // ###########################################################################################################
6258
6259 _vs.writeMagnetometerCalibrationControl(_magnetometerCalibrationControlRegister);
6260 if (auto vnMagnetometerCalibrationControlRegister = _vs.readMagnetometerCalibrationControl();
6261 vnMagnetometerCalibrationControlRegister.hsiMode != _magnetometerCalibrationControlRegister.hsiMode
6262 || vnMagnetometerCalibrationControlRegister.hsiOutput != _magnetometerCalibrationControlRegister.hsiOutput
6263 || vnMagnetometerCalibrationControlRegister.convergeRate != _magnetometerCalibrationControlRegister.convergeRate)
6264 {
6265 LOG_ERROR("{}: Writing the magnetometerCalibrationControlRegister was not successfull.\n"
6266 "Target: hsiMode = {}, hsiOutput = {}, convergeRate = {}\n"
6267 "Sensor: hsiMode = {}, hsiOutput = {}, convergeRate = {}",
6268 nameId(),
6269 _magnetometerCalibrationControlRegister.hsiMode, _magnetometerCalibrationControlRegister.hsiOutput, _magnetometerCalibrationControlRegister.convergeRate,
6270 vnMagnetometerCalibrationControlRegister.hsiMode, vnMagnetometerCalibrationControlRegister.hsiOutput, vnMagnetometerCalibrationControlRegister.convergeRate);
6271 doDeinitialize();
6272 return false;
6273 }
6274
6275 // ###########################################################################################################
6276 // WORLD MAGNETIC & GRAVITY MODULE
6277 // ###########################################################################################################
6278
6279 _vs.writeMagneticAndGravityReferenceVectors(_magneticAndGravityReferenceVectorsRegister);
6280 if (auto vnMagneticAndGravityReferenceVectorsRegister = _vs.readMagneticAndGravityReferenceVectors();
6281 (!_referenceVectorConfigurationRegister.useMagModel && vnMagneticAndGravityReferenceVectorsRegister.magRef != _magneticAndGravityReferenceVectorsRegister.magRef)
6282 || (!_referenceVectorConfigurationRegister.useGravityModel && vnMagneticAndGravityReferenceVectorsRegister.accRef != _magneticAndGravityReferenceVectorsRegister.accRef))
6283 {
6284 LOG_ERROR("{}: Writing the magneticAndGravityReferenceVectorsRegister was not successfull.\n"
6285 "Target: magRef = {}, accRef = {}\n"
6286 "Sensor: magRef = {}, accRef = {}",
6287 nameId(),
6288 _magneticAndGravityReferenceVectorsRegister.magRef, _magneticAndGravityReferenceVectorsRegister.accRef,
6289 vnMagneticAndGravityReferenceVectorsRegister.magRef, vnMagneticAndGravityReferenceVectorsRegister.accRef);
6290 doDeinitialize();
6291 return false;
6292 }
6293
6294 _vs.writeReferenceVectorConfiguration(_referenceVectorConfigurationRegister);
6295 if (auto vnReferenceVectorConfigurationRegister = _vs.readReferenceVectorConfiguration();
6296 vnReferenceVectorConfigurationRegister.useMagModel != _referenceVectorConfigurationRegister.useMagModel
6297 || vnReferenceVectorConfigurationRegister.useGravityModel != _referenceVectorConfigurationRegister.useGravityModel
6298 || vnReferenceVectorConfigurationRegister.recalcThreshold != _referenceVectorConfigurationRegister.recalcThreshold
6299 || vnReferenceVectorConfigurationRegister.year != _referenceVectorConfigurationRegister.year
6300 || vnReferenceVectorConfigurationRegister.position != _referenceVectorConfigurationRegister.position)
6301 {
6302 LOG_ERROR("{}: Writing the referenceVectorConfigurationRegister was not successfull.\n"
6303 "Target: useMagModel = {}, useGravityModel = {}, recalcThreshold = {}, year = {}, position = {}\n"
6304 "Sensor: useMagModel = {}, useGravityModel = {}, recalcThreshold = {}, year = {}, position = {}",
6305 nameId(),
6306 _referenceVectorConfigurationRegister.useMagModel, _referenceVectorConfigurationRegister.useGravityModel, _referenceVectorConfigurationRegister.recalcThreshold, _referenceVectorConfigurationRegister.year, _referenceVectorConfigurationRegister.position,
6307 vnReferenceVectorConfigurationRegister.useMagModel, vnReferenceVectorConfigurationRegister.useGravityModel, vnReferenceVectorConfigurationRegister.recalcThreshold, vnReferenceVectorConfigurationRegister.year, vnReferenceVectorConfigurationRegister.position);
6308 doDeinitialize();
6309 return false;
6310 }
6311
6312 // ###########################################################################################################
6313 // Velocity Aiding
6314 // ###########################################################################################################
6315
6316 if (_sensorModel == VectorNavModel::VN100_VN110)
6317 {
6318 _vs.writeVelocityCompensationControl(_velocityCompensationControlRegister);
6319 if (auto vnVelocityCompensationControlRegister = _vs.readVelocityCompensationControl();
6320 vnVelocityCompensationControlRegister.mode != _velocityCompensationControlRegister.mode
6321 || vnVelocityCompensationControlRegister.velocityTuning != _velocityCompensationControlRegister.velocityTuning
6322 || vnVelocityCompensationControlRegister.rateTuning != _velocityCompensationControlRegister.rateTuning)
6323 {
6324 LOG_ERROR("{}: Writing the velocityCompensationControlRegister was not successfull.\n"
6325 "Target: mode = {}, velocityTuning = {}, rateTuning = {}\n"
6326 "Sensor: mode = {}, velocityTuning = {}, rateTuning = {}",
6327 nameId(),
6328 _velocityCompensationControlRegister.mode, _velocityCompensationControlRegister.velocityTuning, _velocityCompensationControlRegister.rateTuning,
6329 vnVelocityCompensationControlRegister.mode, vnVelocityCompensationControlRegister.velocityTuning, vnVelocityCompensationControlRegister.rateTuning);
6330 doDeinitialize();
6331 return false;
6332 }
6333
6334 // _vs.writeVelocityCompensationMeasurement(vn::math::vec3f); // User manual VN-100 - 10.3.1 (p 124)
6335 }
6336
6337 // ###########################################################################################################
6338 // Outputs
6339 // ###########################################################################################################
6340
6341 _vs.writeAsyncDataOutputType(_asyncDataOutputType);
6342 if (auto vnAsyncDataOutputType = _vs.readAsyncDataOutputType();
6343 vnAsyncDataOutputType != _asyncDataOutputType)
6344 {
6345 LOG_ERROR("{}: Writing the asyncDataOutputType was not successfull.\n"
6346 "Target: {}\n"
6347 "Sensor: {}",
6348 nameId(),
6349 _asyncDataOutputType,
6350 vnAsyncDataOutputType);
6351 doDeinitialize();
6352 return false;
6353 }
6354
6355 if (_asyncDataOutputType != vn::protocol::uart::AsciiAsync::VNOFF)
6356 {
6357 _vs.writeAsyncDataOutputFrequency(_asyncDataOutputFrequency);
6358 if (auto vnAsyncDataOutputFrequency = _vs.readAsyncDataOutputType();
6359 vnAsyncDataOutputFrequency != _asyncDataOutputFrequency)
6360 {
6361 LOG_ERROR("{}: Writing the asyncDataOutputFrequency was not successfull.\n"
6362 "Target: {}\n"
6363 "Sensor: {}",
6364 nameId(),
6365 _asyncDataOutputFrequency,
6366 vnAsyncDataOutputFrequency);
6367 doDeinitialize();
6368 return false;
6369 }
6370 }
6371
6372 [[maybe_unused]] size_t binaryOutputRegisterCounter = 1; // To give a proper error message
6373 try
6374 {
6375 // The sensor does somehow report wrong flags here, so we can't check if it actually worked
6376
6377 // auto checkBinaryRegister = [&](const vn::sensors::BinaryOutputRegister& current, const vn::sensors::BinaryOutputRegister& target) {
6378 // if (current.asyncMode != target.asyncMode
6379 // || current.rateDivisor != target.rateDivisor
6380 // || current.commonField != target.commonField
6381 // || current.timeField != target.timeField
6382 // || current.imuField != target.imuField
6383 // || current.gpsField != target.gpsField
6384 // || current.attitudeField != target.attitudeField
6385 // || current.insField != target.insField
6386 // || current.gps2Field != target.gps2Field)
6387 // {
6388 // LOG_ERROR("{}: Writing the binaryOutputRegister {} was not successfull.\n"
6389 // "Target: asyncMode = {}, rateDivisor = {}, commonField = {}, timeField = {}, imuField = {}, gpsField = {}, attitudeField = {}, insField = {}, gps2Field = {}\n"
6390 // "Sensor: asyncMode = {}, rateDivisor = {}, commonField = {}, timeField = {}, imuField = {}, gpsField = {}, attitudeField = {}, insField = {}, gps2Field = {}",
6391 // nameId(), binaryOutputRegisterCounter,
6392 // target.asyncMode, target.rateDivisor, target.commonField, target.timeField, target.imuField, target.gpsField, target.attitudeField, target.insField, target.gps2Field,
6393 // current.asyncMode, current.rateDivisor, current.commonField, current.timeField, current.imuField, current.gpsField, current.attitudeField, current.insField, current.gps2Field);
6394 // return false;
6395 // }
6396 // return true;
6397 // };
6398
6399 _vs.writeBinaryOutput1(_binaryOutputRegister.at(0));
6400 // if (!checkBinaryRegister(_vs.readBinaryOutput1(), _binaryOutputRegister.at(0)))
6401 // {
6402 // DoDeinitialize();;
6403 // return false;
6404 // }
6405
6406 binaryOutputRegisterCounter++;
6407 _vs.writeBinaryOutput2(_binaryOutputRegister.at(1));
6408 // if (!checkBinaryRegister(_vs.readBinaryOutput2(), _binaryOutputRegister.at(1)))
6409 // {
6410 // DoDeinitialize();;
6411 // return false;
6412 // }
6413
6414 binaryOutputRegisterCounter++;
6415 _vs.writeBinaryOutput3(_binaryOutputRegister.at(2));
6416 // if (!checkBinaryRegister(_vs.readBinaryOutput3(), _binaryOutputRegister.at(2)))
6417 // {
6418 // DoDeinitialize();;
6419 // return false;
6420 // }
6421 }
6422 catch (const std::exception& e)
6423 {
6424 LOG_ERROR("{}: Could not configure binary output register {}: {}", nameId(), binaryOutputRegisterCounter, e.what());
6425 doDeinitialize();
6426 return false;
6427 }
6428
6429 // Some changes need to be set at startup, therefore write the settings and reset the device
6430 LOG_DEBUG("{}: writing settings", nameId());
6431 _vs.writeSettings();
6432
6433 if (deviceNeedsResetAfterInitialization)
6434 {
6435 LOG_DEBUG("{}: Resetting device to apply permenent settings", nameId());
6436 _vs.reset();
6437 }
6438
6439 // TODO: Implement in vnproglib: _vs.writeNmeaOutput1(...) - User manual VN-310 - 8.2.14 (p 103)
6440 // TODO: Implement in vnproglib: _vs.writeNmeaOutput2(...) - User manual VN-310 - 8.2.15 (p 104)
6441
6442 _vs.registerAsyncPacketReceivedHandler(this, asciiOrBinaryAsyncMessageReceived);
6443
6444 LOG_DEBUG("{}: successfully initialized", nameId());
6445
6446 return true;
6447 }
6448
6449 void NAV::VectorNavSensor::deinitialize()
6450 {
6451 LOG_TRACE("{}: called", nameId());
6452
6453 try
6454 {
6455 if (_vs.isConnected())
6456 {
6457 try
6458 {
6459 _vs.unregisterAsyncPacketReceivedHandler();
6460 }
6461 catch (const std::exception& e)
6462 {
6463 LOG_WARN("{}: Could not unregisterAsyncPacketReceivedHandler ({})", nameId(), e.what());
6464 }
6465
6466 try
6467 {
6468 vn::sensors::BinaryOutputRegister bor{
6469 vn::protocol::uart::AsyncMode::ASYNCMODE_NONE, // AsyncMode
6470 800, // RateDivisor
6471 vn::protocol::uart::CommonGroup::COMMONGROUP_NONE, // CommonGroup
6472 vn::protocol::uart::TimeGroup::TIMEGROUP_NONE, // TimeGroup
6473 vn::protocol::uart::ImuGroup::IMUGROUP_NONE, // IMUGroup
6474 vn::protocol::uart::GpsGroup::GPSGROUP_NONE, // GNSS1Group
6475 vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_NONE, // AttitudeGroup
6476 vn::protocol::uart::InsGroup::INSGROUP_NONE, // INSGroup
6477 vn::protocol::uart::GpsGroup::GPSGROUP_NONE // GNSS2Group
6478 };
6479
6480 _vs.writeBinaryOutput1(bor);
6481 _vs.writeBinaryOutput2(bor);
6482 _vs.writeBinaryOutput3(bor);
6483 _vs.writeAsyncDataOutputType(vn::protocol::uart::AsciiAsync::VNOFF);
6484 _vs.changeBaudRate(NAV::UartSensor::Baudrate::BAUDRATE_115200);
6485 _vs.writeSettings();
6486 LOG_DEBUG("{}: Sensor output turned off", nameId());
6487 }
6488 catch (const std::exception& e)
6489 {
6490 LOG_WARN("{}: Could not turn off sensor output ({})", nameId(), e.what());
6491 }
6492
6493 try
6494 {
6495 _vs.disconnect();
6496 LOG_DEBUG("{}: Sensor disconnected", nameId());
6497 }
6498 catch (const std::exception& e)
6499 {
6500 LOG_WARN("{}: Could not disconnect ({})", nameId(), e.what());
6501 }
6502 }
6503 }
6504 catch (const std::exception& e)
6505 {
6506 LOG_WARN("{}: Unhandled exception while deinitializing sensor ({})", nameId(), e.what());
6507 }
6508 }
6509
6510 void NAV::VectorNavSensor::mergeVectorNavBinaryObservations(const std::shared_ptr<VectorNavBinaryOutput>& target, const std::shared_ptr<VectorNavBinaryOutput>& source)
6511 {
6512 target->insTime = !target->insTime.empty() ? target->insTime : source->insTime;
6513 // Group 2 (Time)
6514 if (!target->timeOutputs && source->timeOutputs)
6515 {
6516 target->timeOutputs = source->timeOutputs;
6517 }
6518 else if (target->timeOutputs && source->timeOutputs)
6519 {
6520 target->timeOutputs->timeStartup = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP ? target->timeOutputs->timeStartup : source->timeOutputs->timeStartup;
6521 target->timeOutputs->timeGps = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPS ? target->timeOutputs->timeGps : source->timeOutputs->timeGps;
6522 target->timeOutputs->gpsTow = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW ? target->timeOutputs->gpsTow : source->timeOutputs->gpsTow;
6523 target->timeOutputs->gpsWeek = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK ? target->timeOutputs->gpsWeek : source->timeOutputs->gpsWeek;
6524 target->timeOutputs->timeSyncIn = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESYNCIN ? target->timeOutputs->timeSyncIn : source->timeOutputs->timeSyncIn;
6525 target->timeOutputs->timePPS = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPSPPS ? target->timeOutputs->timePPS : source->timeOutputs->timePPS;
6526 // target->timeOutputs->timeUtc = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEUTC ? target->timeOutputs->timeUtc : source->timeOutputs->timeUtc;
6527 target->timeOutputs->syncInCnt = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCINCNT ? target->timeOutputs->syncInCnt : source->timeOutputs->syncInCnt;
6528 target->timeOutputs->syncOutCnt = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCOUTCNT ? target->timeOutputs->syncOutCnt : source->timeOutputs->syncOutCnt;
6529 target->timeOutputs->timeStatus = target->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS ? target->timeOutputs->timeStatus : source->timeOutputs->timeStatus;
6530
6531 target->timeOutputs->timeField |= source->timeOutputs->timeField;
6532 }
6533 // Group 3 (IMU)
6534 if (!target->imuOutputs && source->imuOutputs)
6535 {
6536 target->imuOutputs = source->imuOutputs;
6537 }
6538 else if (target->imuOutputs && source->imuOutputs)
6539 {
6540 target->imuOutputs->imuStatus = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_IMUSTATUS ? target->imuOutputs->imuStatus : source->imuOutputs->imuStatus;
6541 target->imuOutputs->uncompMag = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPMAG ? target->imuOutputs->uncompMag : source->imuOutputs->uncompMag;
6542 target->imuOutputs->uncompAccel = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL ? target->imuOutputs->uncompAccel : source->imuOutputs->uncompAccel;
6543 target->imuOutputs->uncompGyro = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO ? target->imuOutputs->uncompGyro : source->imuOutputs->uncompGyro;
6544 target->imuOutputs->temp = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_TEMP ? target->imuOutputs->temp : source->imuOutputs->temp;
6545 target->imuOutputs->pres = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_PRES ? target->imuOutputs->pres : source->imuOutputs->pres;
6546 target->imuOutputs->deltaTime = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA ? target->imuOutputs->deltaTime : source->imuOutputs->deltaTime;
6547 target->imuOutputs->deltaTheta = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA ? target->imuOutputs->deltaTheta : source->imuOutputs->deltaTheta;
6548 target->imuOutputs->deltaV = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL ? target->imuOutputs->deltaV : source->imuOutputs->deltaV;
6549 target->imuOutputs->mag = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_MAG ? target->imuOutputs->mag : source->imuOutputs->mag;
6550 target->imuOutputs->accel = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL ? target->imuOutputs->accel : source->imuOutputs->accel;
6551 target->imuOutputs->angularRate = target->imuOutputs->imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE ? target->imuOutputs->angularRate : source->imuOutputs->angularRate;
6552
6553 target->imuOutputs->imuField |= source->imuOutputs->imuField;
6554 }
6555 // Group 4 (GNSS1)
6556 if (!target->gnss1Outputs && source->gnss1Outputs)
6557 {
6558 target->gnss1Outputs = source->gnss1Outputs;
6559 }
6560 else if (target->gnss1Outputs && source->gnss1Outputs)
6561 {
6562 // target->gnss1Outputs->timeUtc = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_UTC ? target->gnss1Outputs->timeUtc : source->gnss1Outputs->timeUtc;
6563 target->gnss1Outputs->tow = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_TOW ? target->gnss1Outputs->tow : source->gnss1Outputs->tow;
6564 target->gnss1Outputs->week = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK ? target->gnss1Outputs->week : source->gnss1Outputs->week;
6565 target->gnss1Outputs->numSats = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_NUMSATS ? target->gnss1Outputs->numSats : source->gnss1Outputs->numSats;
6566 target->gnss1Outputs->fix = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_FIX ? target->gnss1Outputs->fix : source->gnss1Outputs->fix;
6567 target->gnss1Outputs->posLla = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA ? target->gnss1Outputs->posLla : source->gnss1Outputs->posLla;
6568 target->gnss1Outputs->posEcef = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF ? target->gnss1Outputs->posEcef : source->gnss1Outputs->posEcef;
6569 target->gnss1Outputs->velNed = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED ? target->gnss1Outputs->velNed : source->gnss1Outputs->velNed;
6570 target->gnss1Outputs->velEcef = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF ? target->gnss1Outputs->velEcef : source->gnss1Outputs->velEcef;
6571 target->gnss1Outputs->posU = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_POSU ? target->gnss1Outputs->posU : source->gnss1Outputs->posU;
6572 target->gnss1Outputs->velU = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_VELU ? target->gnss1Outputs->velU : source->gnss1Outputs->velU;
6573 target->gnss1Outputs->timeU = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEU ? target->gnss1Outputs->timeU : source->gnss1Outputs->timeU;
6574 target->gnss1Outputs->timeInfo = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO ? target->gnss1Outputs->timeInfo : source->gnss1Outputs->timeInfo;
6575 target->gnss1Outputs->dop = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_DOP ? target->gnss1Outputs->dop : source->gnss1Outputs->dop;
6576 target->gnss1Outputs->satInfo = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_SATINFO ? target->gnss1Outputs->satInfo : source->gnss1Outputs->satInfo;
6577 target->gnss1Outputs->raw = target->gnss1Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_RAWMEAS ? target->gnss1Outputs->raw : source->gnss1Outputs->raw;
6578
6579 target->gnss1Outputs->gnssField |= source->gnss1Outputs->gnssField;
6580 }
6581 // Group 5 (Attitude)
6582 if (!target->attitudeOutputs && source->attitudeOutputs)
6583 {
6584 target->attitudeOutputs = source->attitudeOutputs;
6585 }
6586 else if (target->attitudeOutputs && source->attitudeOutputs)
6587 {
6588 target->attitudeOutputs->vpeStatus = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_VPESTATUS ? target->attitudeOutputs->vpeStatus : source->attitudeOutputs->vpeStatus;
6589 target->attitudeOutputs->ypr = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YAWPITCHROLL ? target->attitudeOutputs->ypr : source->attitudeOutputs->ypr;
6590 target->attitudeOutputs->qtn = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_QUATERNION ? target->attitudeOutputs->qtn : source->attitudeOutputs->qtn;
6591 target->attitudeOutputs->dcm = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_DCM ? target->attitudeOutputs->dcm : source->attitudeOutputs->dcm;
6592 target->attitudeOutputs->magNed = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_MAGNED ? target->attitudeOutputs->magNed : source->attitudeOutputs->magNed;
6593 target->attitudeOutputs->accelNed = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_ACCELNED ? target->attitudeOutputs->accelNed : source->attitudeOutputs->accelNed;
6594 target->attitudeOutputs->linearAccelBody = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELBODY ? target->attitudeOutputs->linearAccelBody : source->attitudeOutputs->linearAccelBody;
6595 target->attitudeOutputs->linearAccelNed = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELNED ? target->attitudeOutputs->linearAccelNed : source->attitudeOutputs->linearAccelNed;
6596 target->attitudeOutputs->yprU = target->attitudeOutputs->attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YPRU ? target->attitudeOutputs->yprU : source->attitudeOutputs->yprU;
6597
6598 target->attitudeOutputs->attitudeField |= source->attitudeOutputs->attitudeField;
6599 }
6600 // Group 6 (INS)
6601 if (!target->insOutputs && source->insOutputs)
6602 {
6603 target->insOutputs = source->insOutputs;
6604 }
6605 else if (target->insOutputs && source->insOutputs)
6606 {
6607 target->insOutputs->insStatus = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_INSSTATUS ? target->insOutputs->insStatus : source->insOutputs->insStatus;
6608 target->insOutputs->posLla = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_POSLLA ? target->insOutputs->posLla : source->insOutputs->posLla;
6609 target->insOutputs->posEcef = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_POSECEF ? target->insOutputs->posEcef : source->insOutputs->posEcef;
6610 target->insOutputs->velBody = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_VELBODY ? target->insOutputs->velBody : source->insOutputs->velBody;
6611 target->insOutputs->velNed = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_VELNED ? target->insOutputs->velNed : source->insOutputs->velNed;
6612 target->insOutputs->velEcef = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_VELECEF ? target->insOutputs->velEcef : source->insOutputs->velEcef;
6613 target->insOutputs->magEcef = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_MAGECEF ? target->insOutputs->magEcef : source->insOutputs->magEcef;
6614 target->insOutputs->accelEcef = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_ACCELECEF ? target->insOutputs->accelEcef : source->insOutputs->accelEcef;
6615 target->insOutputs->linearAccelEcef = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_LINEARACCELECEF ? target->insOutputs->linearAccelEcef : source->insOutputs->linearAccelEcef;
6616 target->insOutputs->posU = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_POSU ? target->insOutputs->posU : source->insOutputs->posU;
6617 target->insOutputs->velU = target->insOutputs->insField & vn::protocol::uart::InsGroup::INSGROUP_VELU ? target->insOutputs->velU : source->insOutputs->velU;
6618
6619 target->insOutputs->insField |= source->insOutputs->insField;
6620 }
6621 // Group 7 (GNSS2)
6622 if (!target->gnss2Outputs && source->gnss2Outputs)
6623 {
6624 target->gnss2Outputs = source->gnss2Outputs;
6625 }
6626 else if (target->gnss2Outputs && source->gnss2Outputs)
6627 {
6628 // target->gnss2Outputs->timeUtc = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_UTC ? target->gnss2Outputs->timeUtc : source->gnss2Outputs->timeUtc;
6629 target->gnss2Outputs->tow = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_TOW ? target->gnss2Outputs->tow : source->gnss2Outputs->tow;
6630 target->gnss2Outputs->week = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK ? target->gnss2Outputs->week : source->gnss2Outputs->week;
6631 target->gnss2Outputs->numSats = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_NUMSATS ? target->gnss2Outputs->numSats : source->gnss2Outputs->numSats;
6632 target->gnss2Outputs->fix = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_FIX ? target->gnss2Outputs->fix : source->gnss2Outputs->fix;
6633 target->gnss2Outputs->posLla = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA ? target->gnss2Outputs->posLla : source->gnss2Outputs->posLla;
6634 target->gnss2Outputs->posEcef = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF ? target->gnss2Outputs->posEcef : source->gnss2Outputs->posEcef;
6635 target->gnss2Outputs->velNed = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED ? target->gnss2Outputs->velNed : source->gnss2Outputs->velNed;
6636 target->gnss2Outputs->velEcef = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF ? target->gnss2Outputs->velEcef : source->gnss2Outputs->velEcef;
6637 target->gnss2Outputs->posU = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_POSU ? target->gnss2Outputs->posU : source->gnss2Outputs->posU;
6638 target->gnss2Outputs->velU = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_VELU ? target->gnss2Outputs->velU : source->gnss2Outputs->velU;
6639 target->gnss2Outputs->timeU = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEU ? target->gnss2Outputs->timeU : source->gnss2Outputs->timeU;
6640 target->gnss2Outputs->timeInfo = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO ? target->gnss2Outputs->timeInfo : source->gnss2Outputs->timeInfo;
6641 target->gnss2Outputs->dop = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_DOP ? target->gnss2Outputs->dop : source->gnss2Outputs->dop;
6642 target->gnss2Outputs->satInfo = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_SATINFO ? target->gnss2Outputs->satInfo : source->gnss2Outputs->satInfo;
6643 target->gnss2Outputs->raw = target->gnss2Outputs->gnssField & vn::protocol::uart::GpsGroup::GPSGROUP_RAWMEAS ? target->gnss2Outputs->raw : source->gnss2Outputs->raw;
6644
6645 target->gnss2Outputs->gnssField |= source->gnss2Outputs->gnssField;
6646 }
6647 }
6648
6649 void NAV::VectorNavSensor::asciiOrBinaryAsyncMessageReceived(void* userData, vn::protocol::uart::Packet& p, [[maybe_unused]] size_t index)
6650 {
6651 auto* vnSensor = static_cast<VectorNavSensor*>(userData);
6652
6653 LOG_DATA("{}: Received message", vnSensor->nameId());
6654
6655 if (p.getPacketLength() > 2500)
6656 {
6657 LOG_ERROR("{} Packet size is {} bytes. VectorNav internal buffer overflows happen if the size is > 2550 bytes. "
6658 "You potentially already lost packages without noticing. "
6659 "Consider splitting the packet to different Binary outputs.",
6660 vnSensor->nameId(), p.getPacketLength());
6661 }
6662 else if (p.getPacketLength() > 2200)
6663 {
6664 LOG_WARN("{} Packet size is {} bytes. VectorNav internal buffer overflows happen if the size is > 2550 bytes. "
6665 "Consider splitting the packet to different Binary outputs.",
6666 vnSensor->nameId(), p.getPacketLength());
6667 }
6668
6669 if (p.type() == vn::protocol::uart::Packet::TYPE_BINARY)
6670 {
6671 for (size_t b = 0; b < 3; b++)
6672 {
6673 // Make sure that the binary packet is from the type we expect
6674 if (p.isCompatible(vnSensor->_binaryOutputRegister.at(b).commonField,
6675 vnSensor->_binaryOutputRegister.at(b).timeField,
6676 vnSensor->_binaryOutputRegister.at(b).imuField,
6677 vnSensor->_binaryOutputRegister.at(b).gpsField,
6678 vnSensor->_binaryOutputRegister.at(b).attitudeField,
6679 vnSensor->_binaryOutputRegister.at(b).insField,
6680 vnSensor->_binaryOutputRegister.at(b).gps2Field))
6681 {
6682 auto obs = std::make_shared<VectorNavBinaryOutput>(vnSensor->_imuPos);
6683
6684 // // Group 1 (Common)
6685 // if (vnSensor->_binaryOutputRegister.at(b).commonField != vn::protocol::uart::CommonGroup::COMMONGROUP_NONE)
6686 // {
6687 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_TIMESTARTUP)
6688 // {
6689 // if (!obs->timeOutputs)
6690 // {
6691 // obs->timeOutputs = std::make_shared<NAV::vendor::vectornav::TimeOutputs>();
6692 // obs->timeOutputs->timeField |= vnSensor->_binaryOutputRegister.at(b).timeField;
6693 // }
6694 // obs->timeOutputs->timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP;
6695 // obs->timeOutputs->timeStartup = p.extractUint64();
6696 // }
6697 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_TIMEGPS)
6698 // {
6699 // if (!obs->timeOutputs)
6700 // {
6701 // obs->timeOutputs = std::make_shared<NAV::vendor::vectornav::TimeOutputs>();
6702 // obs->timeOutputs->timeField |= vnSensor->_binaryOutputRegister.at(b).timeField;
6703 // }
6704 // obs->timeOutputs->timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPS;
6705 // obs->timeOutputs->timeStartup = p.extractUint64();
6706 // }
6707 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_TIMESYNCIN)
6708 // {
6709 // if (!obs->timeOutputs)
6710 // {
6711 // obs->timeOutputs = std::make_shared<NAV::vendor::vectornav::TimeOutputs>();
6712 // obs->timeOutputs->timeField |= vnSensor->_binaryOutputRegister.at(b).timeField;
6713 // }
6714 // obs->timeOutputs->timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESYNCIN;
6715 // obs->timeOutputs->timeSyncIn = p.extractUint64();
6716 // }
6717 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_YAWPITCHROLL)
6718 // {
6719 // if (!obs->attitudeOutputs)
6720 // {
6721 // obs->attitudeOutputs = std::make_shared<NAV::vendor::vectornav::AttitudeOutputs>();
6722 // obs->attitudeOutputs->attitudeField |= vnSensor->_binaryOutputRegister.at(b).attitudeField;
6723 // }
6724 // obs->attitudeOutputs->attitudeField |= vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YAWPITCHROLL;
6725 // auto vec = p.extractVec3f();
6726 // obs->attitudeOutputs->ypr = { vec.x, vec.y, vec.z };
6727 // }
6728 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_QUATERNION)
6729 // {
6730 // if (!obs->attitudeOutputs)
6731 // {
6732 // obs->attitudeOutputs = std::make_shared<NAV::vendor::vectornav::AttitudeOutputs>();
6733 // obs->attitudeOutputs->attitudeField |= vnSensor->_binaryOutputRegister.at(b).attitudeField;
6734 // }
6735 // obs->attitudeOutputs->attitudeField |= vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_QUATERNION;
6736 // auto vec = p.extractVec4f();
6737 // obs->attitudeOutputs->qtn = { vec.w, vec.x, vec.y, vec.z };
6738 // }
6739 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_ANGULARRATE)
6740 // {
6741 // if (!obs->imuOutputs)
6742 // {
6743 // obs->imuOutputs = std::make_shared<NAV::vendor::vectornav::ImuOutputs>();
6744 // obs->imuOutputs->imuField |= vnSensor->_binaryOutputRegister.at(b).imuField;
6745 // }
6746 // obs->imuOutputs->imuField |= vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE;
6747 // auto vec = p.extractVec3f();
6748 // obs->imuOutputs->angularRate = { vec.x, vec.y, vec.z };
6749 // }
6750 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_POSITION)
6751 // {
6752 // if (!obs->insOutputs)
6753 // {
6754 // obs->insOutputs = std::make_shared<NAV::vendor::vectornav::InsOutputs>();
6755 // obs->insOutputs->insField |= vnSensor->_binaryOutputRegister.at(b).insField;
6756 // }
6757 // obs->insOutputs->insField |= vn::protocol::uart::InsGroup::INSGROUP_POSLLA;
6758 // auto vec = p.extractVec3d();
6759 // obs->insOutputs->posLla = { vec.x, vec.y, vec.z };
6760 // }
6761 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_VELOCITY)
6762 // {
6763 // if (!obs->insOutputs)
6764 // {
6765 // obs->insOutputs = std::make_shared<NAV::vendor::vectornav::InsOutputs>();
6766 // obs->insOutputs->insField |= vnSensor->_binaryOutputRegister.at(b).insField;
6767 // }
6768 // obs->insOutputs->insField |= vn::protocol::uart::InsGroup::INSGROUP_VELNED;
6769 // auto vec = p.extractVec3f();
6770 // obs->insOutputs->velNed = { vec.x, vec.y, vec.z };
6771 // }
6772 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_ACCEL)
6773 // {
6774 // if (!obs->imuOutputs)
6775 // {
6776 // obs->imuOutputs = std::make_shared<NAV::vendor::vectornav::ImuOutputs>();
6777 // obs->imuOutputs->imuField |= vnSensor->_binaryOutputRegister.at(b).imuField;
6778 // }
6779 // obs->imuOutputs->imuField |= vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL;
6780 // auto vec = p.extractVec3f();
6781 // obs->imuOutputs->accel = { vec.x, vec.y, vec.z };
6782 // }
6783 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_IMU)
6784 // {
6785 // if (!obs->imuOutputs)
6786 // {
6787 // obs->imuOutputs = std::make_shared<NAV::vendor::vectornav::ImuOutputs>();
6788 // obs->imuOutputs->imuField |= vnSensor->_binaryOutputRegister.at(b).imuField;
6789 // }
6790 // obs->imuOutputs->imuField |= vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL;
6791 // auto vec = p.extractVec3f();
6792 // obs->imuOutputs->uncompAccel = { vec.x, vec.y, vec.z };
6793 // obs->imuOutputs->imuField |= vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO;
6794 // vec = p.extractVec3f();
6795 // obs->imuOutputs->uncompGyro = { vec.x, vec.y, vec.z };
6796 // }
6797 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_MAGPRES)
6798 // {
6799 // if (!obs->imuOutputs)
6800 // {
6801 // obs->imuOutputs = std::make_shared<NAV::vendor::vectornav::ImuOutputs>();
6802 // obs->imuOutputs->imuField |= vnSensor->_binaryOutputRegister.at(b).imuField;
6803 // }
6804 // obs->imuOutputs->imuField |= vn::protocol::uart::ImuGroup::IMUGROUP_MAG;
6805 // auto vec = p.extractVec3f();
6806 // obs->imuOutputs->mag = { vec.x, vec.y, vec.z };
6807 // obs->imuOutputs->imuField |= vn::protocol::uart::ImuGroup::IMUGROUP_TEMP;
6808 // obs->imuOutputs->temp = p.extractFloat();
6809 // obs->imuOutputs->imuField |= vn::protocol::uart::ImuGroup::IMUGROUP_PRES;
6810 // obs->imuOutputs->pres = p.extractFloat();
6811 // }
6812 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_DELTATHETA)
6813 // {
6814 // if (!obs->imuOutputs)
6815 // {
6816 // obs->imuOutputs = std::make_shared<NAV::vendor::vectornav::ImuOutputs>();
6817 // obs->imuOutputs->imuField |= vnSensor->_binaryOutputRegister.at(b).imuField;
6818 // }
6819 // obs->imuOutputs->imuField |= vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA;
6820 // obs->imuOutputs->deltaTime = p.extractFloat();
6821 // auto vec = p.extractVec3f();
6822 // obs->imuOutputs->deltaTheta = { vec.x, vec.y, vec.z };
6823 // obs->imuOutputs->imuField |= vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL;
6824 // vec = p.extractVec3f();
6825 // obs->imuOutputs->deltaV = { vec.x, vec.y, vec.z };
6826 // }
6827 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_INSSTATUS)
6828 // {
6829 // if (!obs->insOutputs)
6830 // {
6831 // obs->insOutputs = std::make_shared<NAV::vendor::vectornav::InsOutputs>();
6832 // obs->insOutputs->insField |= vnSensor->_binaryOutputRegister.at(b).insField;
6833 // }
6834 // obs->insOutputs->insField |= vn::protocol::uart::InsGroup::INSGROUP_INSSTATUS;
6835 // obs->insOutputs->insStatus = p.extractUint16();
6836 // }
6837 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_TIMESYNCIN)
6838 // {
6839 // if (!obs->timeOutputs)
6840 // {
6841 // obs->timeOutputs = std::make_shared<NAV::vendor::vectornav::TimeOutputs>();
6842 // obs->timeOutputs->timeField |= vnSensor->_binaryOutputRegister.at(b).timeField;
6843 // }
6844 // obs->timeOutputs->timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCINCNT;
6845 // obs->timeOutputs->syncInCnt = p.extractUint32();
6846 // }
6847 // if (vnSensor->_binaryOutputRegister.at(b).commonField & vn::protocol::uart::CommonGroup::COMMONGROUP_TIMEGPSPPS)
6848 // {
6849 // if (!obs->timeOutputs)
6850 // {
6851 // obs->timeOutputs = std::make_shared<NAV::vendor::vectornav::TimeOutputs>();
6852 // obs->timeOutputs->timeField |= vnSensor->_binaryOutputRegister.at(b).timeField;
6853 // }
6854 // obs->timeOutputs->timeField |= vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPSPPS;
6855 // obs->timeOutputs->timePPS = p.extractUint64();
6856 // }
6857 // }
6858
6859 // Group 2 (Time)
6860 if (vnSensor->_binaryOutputRegister.at(b).timeField != vn::protocol::uart::TimeGroup::TIMEGROUP_NONE)
6861 {
6862 if (!obs->timeOutputs)
6863 {
6864 obs->timeOutputs = std::make_shared<NAV::vendor::vectornav::TimeOutputs>();
6865 obs->timeOutputs->timeField |= vnSensor->_binaryOutputRegister.at(b).timeField;
6866 }
6867
6868 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP)
6869 {
6870 obs->timeOutputs->timeStartup = p.extractUint64();
6871 }
6872 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPS)
6873 {
6874 obs->timeOutputs->timeGps = p.extractUint64();
6875 }
6876 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)
6877 {
6878 obs->timeOutputs->gpsTow = p.extractUint64();
6879 }
6880 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK)
6881 {
6882 obs->timeOutputs->gpsWeek = p.extractUint16();
6883 }
6884 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESYNCIN)
6885 {
6886 obs->timeOutputs->timeSyncIn = p.extractUint64();
6887 }
6888 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPSPPS)
6889 {
6890 obs->timeOutputs->timePPS = p.extractUint64();
6891 }
6892 // if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEUTC)
6893 // {
6894 // obs->timeOutputs->timeUtc.year = p.extractInt8();
6895 // obs->timeOutputs->timeUtc.month = p.extractUint8();
6896 // obs->timeOutputs->timeUtc.day = p.extractUint8();
6897 // obs->timeOutputs->timeUtc.hour = p.extractUint8();
6898 // obs->timeOutputs->timeUtc.min = p.extractUint8();
6899 // obs->timeOutputs->timeUtc.sec = p.extractUint8();
6900 // obs->timeOutputs->timeUtc.ms = p.extractUint16();
6901 // }
6902 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCINCNT)
6903 {
6904 obs->timeOutputs->syncInCnt = p.extractUint32();
6905 }
6906 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCOUTCNT)
6907 {
6908 obs->timeOutputs->syncOutCnt = p.extractUint32();
6909 }
6910 if (vnSensor->_binaryOutputRegister.at(b).timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS)
6911 {
6912 obs->timeOutputs->timeStatus = p.extractUint8();
6913 }
6914 }
6915 // Group 3 (IMU)
6916 if (vnSensor->_binaryOutputRegister.at(b).imuField != vn::protocol::uart::ImuGroup::IMUGROUP_NONE)
6917 {
6918 if (!obs->imuOutputs)
6919 {
6920 obs->imuOutputs = std::make_shared<NAV::vendor::vectornav::ImuOutputs>();
6921 obs->imuOutputs->imuField |= vnSensor->_binaryOutputRegister.at(b).imuField;
6922 }
6923
6924 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_IMUSTATUS)
6925 {
6926 obs->imuOutputs->imuStatus = p.extractUint16();
6927 }
6928 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPMAG)
6929 {
6930 auto vec = p.extractVec3f();
6931 obs->imuOutputs->uncompMag = { vec.x, vec.y, vec.z };
6932 }
6933 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPACCEL)
6934 {
6935 auto vec = p.extractVec3f();
6936 obs->imuOutputs->uncompAccel = { vec.x, vec.y, vec.z };
6937 }
6938 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_UNCOMPGYRO)
6939 {
6940 auto vec = p.extractVec3f();
6941 obs->imuOutputs->uncompGyro = { vec.x, vec.y, vec.z };
6942 }
6943 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_TEMP)
6944 {
6945 obs->imuOutputs->temp = p.extractFloat();
6946 }
6947 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_PRES)
6948 {
6949 obs->imuOutputs->pres = p.extractFloat();
6950 }
6951 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTATHETA)
6952 {
6953 obs->imuOutputs->deltaTime = p.extractFloat();
6954 auto vec = p.extractVec3f();
6955 obs->imuOutputs->deltaTheta = { vec.x, vec.y, vec.z };
6956 }
6957 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_DELTAVEL)
6958 {
6959 auto vec = p.extractVec3f();
6960 obs->imuOutputs->deltaV = { vec.x, vec.y, vec.z };
6961 }
6962 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_MAG)
6963 {
6964 auto vec = p.extractVec3f();
6965 obs->imuOutputs->mag = { vec.x, vec.y, vec.z };
6966 }
6967 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ACCEL)
6968 {
6969 auto vec = p.extractVec3f();
6970 obs->imuOutputs->accel = { vec.x, vec.y, vec.z };
6971 }
6972 if (vnSensor->_binaryOutputRegister.at(b).imuField & vn::protocol::uart::ImuGroup::IMUGROUP_ANGULARRATE)
6973 {
6974 auto vec = p.extractVec3f();
6975 obs->imuOutputs->angularRate = { vec.x, vec.y, vec.z };
6976 }
6977 }
6978 // Group 4 (GNSS1)
6979 if (vnSensor->_binaryOutputRegister.at(b).gpsField != vn::protocol::uart::GpsGroup::GPSGROUP_NONE)
6980 {
6981 if (!obs->gnss1Outputs)
6982 {
6983 obs->gnss1Outputs = std::make_shared<NAV::vendor::vectornav::GnssOutputs>();
6984 obs->gnss1Outputs->gnssField |= vnSensor->_binaryOutputRegister.at(b).gpsField;
6985 }
6986
6987 // if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_UTC)
6988 // {
6989 // obs->gnss1Outputs->timeUtc.year = p.extractInt8();
6990 // obs->gnss1Outputs->timeUtc.month = p.extractUint8();
6991 // obs->gnss1Outputs->timeUtc.day = p.extractUint8();
6992 // obs->gnss1Outputs->timeUtc.hour = p.extractUint8();
6993 // obs->gnss1Outputs->timeUtc.min = p.extractUint8();
6994 // obs->gnss1Outputs->timeUtc.sec = p.extractUint8();
6995 // obs->gnss1Outputs->timeUtc.ms = p.extractUint16();
6996 // }
6997 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
6998 {
6999 obs->gnss1Outputs->tow = p.extractUint64();
7000 }
7001 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
7002 {
7003 obs->gnss1Outputs->week = p.extractUint16();
7004 }
7005 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_NUMSATS)
7006 {
7007 obs->gnss1Outputs->numSats = p.extractUint8();
7008 }
7009 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
7010 {
7011 obs->gnss1Outputs->fix = p.extractUint8();
7012 }
7013 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA)
7014 {
7015 auto vec = p.extractVec3d();
7016 obs->gnss1Outputs->posLla = { vec.x, vec.y, vec.z };
7017 }
7018 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF)
7019 {
7020 auto vec = p.extractVec3d();
7021 obs->gnss1Outputs->posEcef = { vec.x, vec.y, vec.z };
7022 }
7023 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED)
7024 {
7025 auto vec = p.extractVec3f();
7026 obs->gnss1Outputs->velNed = { vec.x, vec.y, vec.z };
7027 }
7028 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF)
7029 {
7030 auto vec = p.extractVec3f();
7031 obs->gnss1Outputs->velEcef = { vec.x, vec.y, vec.z };
7032 }
7033 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_POSU)
7034 {
7035 auto vec = p.extractVec3f();
7036 obs->gnss1Outputs->posU = { vec.x, vec.y, vec.z };
7037 }
7038 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_VELU)
7039 {
7040 obs->gnss1Outputs->velU = p.extractFloat();
7041 }
7042 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEU)
7043 {
7044 obs->gnss1Outputs->timeU = p.extractFloat();
7045 }
7046 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO)
7047 {
7048 obs->gnss1Outputs->timeInfo.status = p.extractUint8();
7049 obs->gnss1Outputs->timeInfo.leapSeconds = p.extractInt8();
7050 }
7051 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_DOP)
7052 {
7053 obs->gnss1Outputs->dop.gDop = p.extractFloat();
7054 obs->gnss1Outputs->dop.pDop = p.extractFloat();
7055 obs->gnss1Outputs->dop.tDop = p.extractFloat();
7056 obs->gnss1Outputs->dop.vDop = p.extractFloat();
7057 obs->gnss1Outputs->dop.hDop = p.extractFloat();
7058 obs->gnss1Outputs->dop.nDop = p.extractFloat();
7059 obs->gnss1Outputs->dop.eDop = p.extractFloat();
7060 }
7061 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_SATINFO)
7062 {
7063 obs->gnss1Outputs->satInfo.numSats = p.extractUint8();
7064 p.extractUint8(); // Reserved for future use
7065
7066 LOG_DATA("{}: SatInfo: numSats {}", vnSensor->nameId(), obs->gnss1Outputs->satInfo.numSats);
7067 for (size_t i = 0; i < obs->gnss1Outputs->satInfo.numSats; i++)
7068 {
7069 auto sys = p.extractInt8();
7070 auto svId = p.extractUint8();
7071 auto flags = p.extractUint8();
7072 auto cno = p.extractUint8();
7073 auto qi = p.extractUint8();
7074 auto el = p.extractInt8();
7075 auto az = p.extractInt16();
7076 obs->gnss1Outputs->satInfo.satellites.emplace_back(sys, svId, flags, cno, qi, el, az);
7077 LOG_DATA("{}: SatInfo: sys {}, svId {}, flags {}, cno {}, qi {}, el {}, az {}", vnSensor->nameId(),
7078 sys, svId, flags, cno, qi, el, az);
7079 }
7080 }
7081 if (vnSensor->_binaryOutputRegister.at(b).gpsField & vn::protocol::uart::GpsGroup::GPSGROUP_RAWMEAS)
7082 {
7083 obs->gnss1Outputs->raw.tow = p.extractDouble();
7084 obs->gnss1Outputs->raw.week = p.extractUint16();
7085 obs->gnss1Outputs->raw.numSats = p.extractUint8();
7086 p.extractUint8(); // Reserved for future use
7087 LOG_DATA("{}: RawMeas: tow {}, week {}, numSats {}", vnSensor->nameId(),
7088 obs->gnss1Outputs->raw.tow, obs->gnss1Outputs->raw.week, obs->gnss1Outputs->raw.numSats);
7089
7090 for (size_t i = 0; i < obs->gnss1Outputs->raw.numSats; i++)
7091 {
7092 auto sys = p.extractUint8();
7093 auto svId = p.extractUint8();
7094 auto freq = p.extractUint8();
7095 auto chan = p.extractUint8();
7096 auto slot = p.extractInt8();
7097 auto cno = p.extractUint8();
7098 auto flags = p.extractUint16();
7099 auto pr = p.extractDouble();
7100 auto cp = p.extractDouble();
7101 auto dp = p.extractFloat();
7102 obs->gnss1Outputs->raw.satellites.emplace_back(sys, svId, freq, chan, slot, cno, flags, pr, cp, dp);
7103 LOG_DATA("{}: RawMeas: sys {}, svId {}, freq {}, chan {}, slot {}, cno {}, flags {}, pr {}, cp {}, dp {}",
7104 vnSensor->nameId(), static_cast<int>(sys), static_cast<int>(svId), static_cast<int>(freq), static_cast<int>(chan),
7105 static_cast<int>(slot), static_cast<int>(cno), static_cast<int>(flags), pr, cp, dp);
7106 }
7107 }
7108 }
7109 // Group 5 (Attitude)
7110 if (vnSensor->_binaryOutputRegister.at(b).attitudeField != vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_NONE)
7111 {
7112 if (!obs->attitudeOutputs)
7113 {
7114 obs->attitudeOutputs = std::make_shared<NAV::vendor::vectornav::AttitudeOutputs>();
7115 obs->attitudeOutputs->attitudeField |= vnSensor->_binaryOutputRegister.at(b).attitudeField;
7116 }
7117
7118 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_VPESTATUS)
7119 {
7120 obs->attitudeOutputs->vpeStatus = p.extractUint16();
7121 }
7122 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YAWPITCHROLL)
7123 {
7124 auto vec = p.extractVec3f();
7125 obs->attitudeOutputs->ypr = { vec.x, vec.y, vec.z };
7126 }
7127 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_QUATERNION)
7128 {
7129 auto vec = p.extractVec4f();
7130 obs->attitudeOutputs->qtn = { vec.w, vec.x, vec.y, vec.z };
7131 }
7132 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_DCM)
7133 {
7134 auto col0 = p.extractVec3f();
7135 auto col1 = p.extractVec3f();
7136 auto col2 = p.extractVec3f();
7137 obs->attitudeOutputs->dcm << col0.x, col1.x, col2.x,
7138 col0.y, col1.y, col2.y,
7139 col0.z, col1.z, col2.z;
7140 }
7141 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_MAGNED)
7142 {
7143 auto vec = p.extractVec3f();
7144 obs->attitudeOutputs->magNed = { vec.x, vec.y, vec.z };
7145 }
7146 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_ACCELNED)
7147 {
7148 auto vec = p.extractVec3f();
7149 obs->attitudeOutputs->accelNed = { vec.x, vec.y, vec.z };
7150 }
7151 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELBODY)
7152 {
7153 auto vec = p.extractVec3f();
7154 obs->attitudeOutputs->linearAccelBody = { vec.x, vec.y, vec.z };
7155 }
7156 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_LINEARACCELNED)
7157 {
7158 auto vec = p.extractVec3f();
7159 obs->attitudeOutputs->linearAccelNed = { vec.x, vec.y, vec.z };
7160 }
7161 if (vnSensor->_binaryOutputRegister.at(b).attitudeField & vn::protocol::uart::AttitudeGroup::ATTITUDEGROUP_YPRU)
7162 {
7163 auto vec = p.extractVec3f();
7164 obs->attitudeOutputs->yprU = { vec.x, vec.y, vec.z };
7165 }
7166 }
7167 // Group 6 (INS)
7168 if (vnSensor->_binaryOutputRegister.at(b).insField != vn::protocol::uart::InsGroup::INSGROUP_NONE)
7169 {
7170 if (!obs->insOutputs)
7171 {
7172 obs->insOutputs = std::make_shared<NAV::vendor::vectornav::InsOutputs>();
7173 obs->insOutputs->insField |= vnSensor->_binaryOutputRegister.at(b).insField;
7174 }
7175
7176 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_INSSTATUS)
7177 {
7178 obs->insOutputs->insStatus = p.extractUint16();
7179 }
7180 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_POSLLA)
7181 {
7182 auto vec = p.extractVec3d();
7183 obs->insOutputs->posLla = { vec.x, vec.y, vec.z };
7184 }
7185 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_POSECEF)
7186 {
7187 auto vec = p.extractVec3d();
7188 obs->insOutputs->posEcef = { vec.x, vec.y, vec.z };
7189 }
7190 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_VELBODY)
7191 {
7192 auto vec = p.extractVec3f();
7193 obs->insOutputs->velBody = { vec.x, vec.y, vec.z };
7194 }
7195 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_VELNED)
7196 {
7197 auto vec = p.extractVec3f();
7198 obs->insOutputs->velNed = { vec.x, vec.y, vec.z };
7199 }
7200 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_VELECEF)
7201 {
7202 auto vec = p.extractVec3f();
7203 obs->insOutputs->velEcef = { vec.x, vec.y, vec.z };
7204 }
7205 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_MAGECEF)
7206 {
7207 auto vec = p.extractVec3f();
7208 obs->insOutputs->magEcef = { vec.x, vec.y, vec.z };
7209 }
7210 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_ACCELECEF)
7211 {
7212 auto vec = p.extractVec3f();
7213 obs->insOutputs->accelEcef = { vec.x, vec.y, vec.z };
7214 }
7215 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_LINEARACCELECEF)
7216 {
7217 auto vec = p.extractVec3f();
7218 obs->insOutputs->linearAccelEcef = { vec.x, vec.y, vec.z };
7219 }
7220 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_POSU)
7221 {
7222 obs->insOutputs->posU = p.extractFloat();
7223 }
7224 if (vnSensor->_binaryOutputRegister.at(b).insField & vn::protocol::uart::InsGroup::INSGROUP_VELU)
7225 {
7226 obs->insOutputs->velU = p.extractFloat();
7227 }
7228 }
7229 // Group 7 (GNSS2)
7230 if (vnSensor->_binaryOutputRegister.at(b).gps2Field != vn::protocol::uart::GpsGroup::GPSGROUP_NONE)
7231 {
7232 if (!obs->gnss2Outputs)
7233 {
7234 obs->gnss2Outputs = std::make_shared<NAV::vendor::vectornav::GnssOutputs>();
7235 obs->gnss2Outputs->gnssField |= vnSensor->_binaryOutputRegister.at(b).gps2Field;
7236 }
7237
7238 // if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_UTC)
7239 // {
7240 // obs->gnss2Outputs->timeUtc.year = p.extractInt8();
7241 // obs->gnss2Outputs->timeUtc.month = p.extractUint8();
7242 // obs->gnss2Outputs->timeUtc.day = p.extractUint8();
7243 // obs->gnss2Outputs->timeUtc.hour = p.extractUint8();
7244 // obs->gnss2Outputs->timeUtc.min = p.extractUint8();
7245 // obs->gnss2Outputs->timeUtc.sec = p.extractUint8();
7246 // obs->gnss2Outputs->timeUtc.ms = p.extractUint16();
7247 // }
7248 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_TOW)
7249 {
7250 obs->gnss2Outputs->tow = p.extractUint64();
7251 }
7252 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_WEEK)
7253 {
7254 obs->gnss2Outputs->week = p.extractUint16();
7255 }
7256 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_NUMSATS)
7257 {
7258 obs->gnss2Outputs->numSats = p.extractUint8();
7259 }
7260 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_FIX)
7261 {
7262 obs->gnss2Outputs->fix = p.extractUint8();
7263 }
7264 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_POSLLA)
7265 {
7266 auto vec = p.extractVec3d();
7267 obs->gnss2Outputs->posLla = { vec.x, vec.y, vec.z };
7268 }
7269 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_POSECEF)
7270 {
7271 auto vec = p.extractVec3d();
7272 obs->gnss2Outputs->posEcef = { vec.x, vec.y, vec.z };
7273 }
7274 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_VELNED)
7275 {
7276 auto vec = p.extractVec3f();
7277 obs->gnss2Outputs->velNed = { vec.x, vec.y, vec.z };
7278 }
7279 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_VELECEF)
7280 {
7281 auto vec = p.extractVec3f();
7282 obs->gnss2Outputs->velEcef = { vec.x, vec.y, vec.z };
7283 }
7284 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_POSU)
7285 {
7286 auto vec = p.extractVec3f();
7287 obs->gnss2Outputs->posU = { vec.x, vec.y, vec.z };
7288 }
7289 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_VELU)
7290 {
7291 obs->gnss2Outputs->velU = p.extractFloat();
7292 }
7293 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEU)
7294 {
7295 obs->gnss2Outputs->timeU = p.extractFloat();
7296 }
7297 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_TIMEINFO)
7298 {
7299 obs->gnss2Outputs->timeInfo.status = p.extractUint8();
7300 obs->gnss2Outputs->timeInfo.leapSeconds = p.extractInt8();
7301 }
7302 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_DOP)
7303 {
7304 obs->gnss2Outputs->dop.gDop = p.extractFloat();
7305 obs->gnss2Outputs->dop.pDop = p.extractFloat();
7306 obs->gnss2Outputs->dop.tDop = p.extractFloat();
7307 obs->gnss2Outputs->dop.vDop = p.extractFloat();
7308 obs->gnss2Outputs->dop.hDop = p.extractFloat();
7309 obs->gnss2Outputs->dop.nDop = p.extractFloat();
7310 obs->gnss2Outputs->dop.eDop = p.extractFloat();
7311 }
7312 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_SATINFO)
7313 {
7314 obs->gnss2Outputs->satInfo.numSats = p.extractUint8();
7315 p.extractUint8(); // Reserved for future use
7316 for (size_t i = 0; i < obs->gnss2Outputs->satInfo.numSats; i++)
7317 {
7318 auto sys = p.extractInt8();
7319 auto svId = p.extractUint8();
7320 auto flags = p.extractUint8();
7321 auto cno = p.extractUint8();
7322 auto qi = p.extractUint8();
7323 auto el = p.extractInt8();
7324 auto az = p.extractInt16();
7325 obs->gnss2Outputs->satInfo.satellites.emplace_back(sys, svId, flags, cno, qi, el, az);
7326 }
7327 }
7328 if (vnSensor->_binaryOutputRegister.at(b).gps2Field & vn::protocol::uart::GpsGroup::GPSGROUP_RAWMEAS)
7329 {
7330 obs->gnss2Outputs->raw.tow = p.extractDouble();
7331 obs->gnss2Outputs->raw.week = p.extractUint16();
7332 obs->gnss2Outputs->raw.numSats = p.extractUint8();
7333 p.extractUint8(); // Reserved for future use
7334 for (size_t i = 0; i < obs->gnss2Outputs->raw.numSats; i++)
7335 {
7336 auto sys = p.extractUint8();
7337 auto svId = p.extractUint8();
7338 auto freq = p.extractUint8();
7339 auto chan = p.extractUint8();
7340 auto slot = p.extractInt8();
7341 auto cno = p.extractUint8();
7342 auto flags = p.extractUint16();
7343 auto pr = p.extractDouble();
7344 auto cp = p.extractDouble();
7345 auto dp = p.extractFloat();
7346 obs->gnss2Outputs->raw.satellites.emplace_back(sys, svId, freq, chan, slot, cno, flags, pr, cp, dp);
7347 }
7348 }
7349 }
7350
7351 if (p.getCurExtractLoc() != p.getPacketLength() - 2) // 2 Bytes CRC should be left
7352 {
7353 LOG_DEBUG("{}: Only {} of {} bytes were extracted from the Binary Output {}", vnSensor->nameId(), p.getCurExtractLoc(), p.getPacketLength(), b + 1);
7354 }
7355
7356 // --------------------------------------------- Fetch InsTime -----------------------------------------------
7357 auto updateSyncOut = [vnSensor, obs](const InsTime& ppsTime) {
7358 if (vnSensor->_synchronizationControlRegister.syncOutMode == vn::protocol::uart::SYNCOUTMODE_GPSPPS
7359 && (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_SYNCOUTCNT))
7360 {
7361 if (obs->timeOutputs->syncOutCnt > 0)
7362 {
7363 if (vnSensor->_timeSyncOut.syncOutCnt == 0)
7364 {
7365 LOG_INFO("{}: Found PPS time {} and is providing it to its connected nodes", vnSensor->nameId(), ppsTime.toYMDHMS());
7366 }
7367 vnSensor->_timeSyncOut.ppsTime = ppsTime;
7368 vnSensor->_timeSyncOut.syncOutCnt = obs->timeOutputs->syncOutCnt;
7369 LOG_DATA("{}: Syncing time {}, pps {}, syncOutCnt {}",
7370 vnSensor->nameId(), obs->insTime.toGPSweekTow(),
7371 vnSensor->_timeSyncOut.ppsTime.toGPSweekTow(), vnSensor->_timeSyncOut.syncOutCnt);
7372 }
7373 }
7374 };
7375
7376 // Group 2 (Time)
7377 if (obs->timeOutputs && (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS))
7378 {
7379 if (obs->timeOutputs->timeStatus.dateOk())
7380 {
7381 if (obs->timeOutputs->timeStatus.timeOk()
7382 && (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)
7383 && (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK))
7384 {
7385 obs->insTime = InsTime(InsTime_GPSweekTow(0, obs->timeOutputs->gpsWeek, obs->timeOutputs->gpsTow * 1e-9L));
7386 updateSyncOut(InsTime(0, obs->timeOutputs->gpsWeek, std::floor(obs->timeOutputs->gpsTow * 1e-9L)));
7387 }
7388 else if (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMEGPS)
7389 {
7390 auto secondsSinceEpoche = static_cast<long double>(obs->timeOutputs->timeGps) * 1e-9L;
7391 auto week = static_cast<uint16_t>(secondsSinceEpoche / static_cast<long double>(InsTimeUtil::SECONDS_PER_DAY * InsTimeUtil::DAYS_PER_WEEK));
7392 auto tow = secondsSinceEpoche - week * InsTimeUtil::SECONDS_PER_DAY * InsTimeUtil::DAYS_PER_WEEK;
7393
7394 obs->insTime = InsTime(InsTime_GPSweekTow(0, week, tow));
7395 updateSyncOut(InsTime(0, week, std::floor(tow)));
7396 }
7397 }
7398 }
7399
7400 if (obs->insTime.empty() // Look for master to give GNSS time
7401 && (obs->timeOutputs->timeField & vn::protocol::uart::TIMEGROUP_TIMESYNCIN) // We need syncin time for this
7402 && vnSensor->_syncInPin && vnSensor->inputPins.front().isPinLinked()) // Try to get a sync from the master
7403 {
7404 if (auto timeSyncMaster = vnSensor->getInputValue<TimeSync>(0);
7405 timeSyncMaster && !timeSyncMaster->v->ppsTime.empty())
7406 {
7407 // This can have the following values
7408 // - -1: PPS -> VN310 message -> VN100 message (which happened before the VN310 message)
7409 // - 0: PPS -> VN310 message -> VN100 message
7410 // - 1: PPS -> VN100 message -> VN310 message
7411 int64_t syncCntDiff = obs->timeOutputs->syncInCnt - timeSyncMaster->v->syncOutCnt;
7412 obs->insTime = timeSyncMaster->v->ppsTime + std::chrono::nanoseconds(obs->timeOutputs->timeSyncIn)
7413 + std::chrono::seconds(syncCntDiff);
7414 LOG_DATA("{}: Syncing time {}, pps {}, syncOutCnt {}, syncInCnt {}, syncCntDiff {}",
7415 vnSensor->nameId(), obs->insTime.toGPSweekTow(), timeSyncMaster->v->ppsTime.toGPSweekTow(),
7416 timeSyncMaster->v->syncOutCnt, obs->timeOutputs->syncInCnt, syncCntDiff);
7417 }
7418 }
7419
7420 if (obs->insTime.empty()
7421 && !vnSensor->_lastMessageTime.at(b).empty()
7422 && obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP)
7423 {
7424 obs->insTime = vnSensor->_lastMessageTime.at(b) + std::chrono::nanoseconds(obs->timeOutputs->timeStartup - vnSensor->_lastMessageTimeSinceStartup.at(b));
7425 }
7426
7427 if (obs->insTime.empty() // Look if other sensors have set a global time
7428 && !(obs->timeOutputs // but only if we did not request the time from the sensor via GNSS
7429 && (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTATUS)
7430 && (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSTOW)
7431 && (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_GPSWEEK)))
7432 {
7433 if (InsTime currentTime = util::time::GetCurrentInsTime();
7434 !currentTime.empty())
7435 {
7436 obs->insTime = currentTime;
7437 }
7438 }
7439
7440 if (!obs->insTime.empty())
7441 {
7442 if (!vnSensor->_lastMessageTime.at(b).empty())
7443 {
7444 // FIXME: This seems like a bug in clang-tidy. Check if it is working in future versions of clang-tidy
7445 // NOLINTNEXTLINE(hicpp-use-nullptr, modernize-use-nullptr)
7446 if (obs->insTime - vnSensor->_lastMessageTime.at(b) >= std::chrono::duration<double>(1.5 * (vnSensor->_binaryOutputRegister.at(b).rateDivisor / IMU_DEFAULT_FREQUENCY)))
7447 {
7448 LOG_WARN("{}: Potentially lost a message. dt = {:.4} s, expect {} s. (Previous message at [{}], current message [{}])", vnSensor->nameId(),
7449 static_cast<double>((obs->insTime - vnSensor->_lastMessageTime.at(b)).count()),
7450 1. / IMU_DEFAULT_FREQUENCY * vnSensor->_binaryOutputRegister.at(b).rateDivisor,
7451 vnSensor->_lastMessageTime.at(b), obs->insTime);
7452 }
7453 }
7454 vnSensor->_lastMessageTime.at(b) = obs->insTime;
7455 if (obs->timeOutputs->timeField & vn::protocol::uart::TimeGroup::TIMEGROUP_TIMESTARTUP)
7456 {
7457 vnSensor->_lastMessageTimeSinceStartup.at(b) = obs->timeOutputs->timeStartup;
7458 }
7459 }
7460
7461 if ((vnSensor->_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output2 && (b == 0 || b == 1))
7462 || (vnSensor->_binaryOutputRegisterMerge == BinaryRegisterMerge::Output1_Output3 && (b == 0 || b == 2))
7463 || (vnSensor->_binaryOutputRegisterMerge == BinaryRegisterMerge::Output2_Output3 && (b == 1 || b == 2)))
7464 {
7465 if (vnSensor->_binaryOutputRegisterMergeObservation == nullptr)
7466 {
7467 std::stringstream sstream;
7468 if (!obs->insTime.empty())
7469 {
7470 sstream << obs->insTime;
7471 }
7472 else
7473 {
7474 sstream << obs->timeOutputs->timeStartup;
7475 }
7476 LOG_DATA("{}: {} - Storing message with time {}", vnSensor->nameId(), b, sstream.str());
7477
7478 vnSensor->_binaryOutputRegisterMergeObservation = obs;
7479 vnSensor->_binaryOutputRegisterMergeIndex = b;
7480 }
7481 else
7482 {
7483 auto allowedTimeDiff = std::chrono::microseconds(static_cast<int>(0.5 * (vnSensor->_binaryOutputRegister.at(b).rateDivisor / IMU_DEFAULT_FREQUENCY) * 1e6));
7484 if (vnSensor->_binaryOutputRegisterMergeIndex != b
7485 && ((!obs->insTime.empty() && !vnSensor->_binaryOutputRegisterMergeObservation->insTime.empty()
7486 && (obs->insTime - vnSensor->_binaryOutputRegisterMergeObservation->insTime < allowedTimeDiff)) // NOLINT(hicpp-use-nullptr, modernize-use-nullptr)
7487 || (obs->timeOutputs != nullptr && vnSensor->_binaryOutputRegisterMergeObservation->timeOutputs != nullptr
7488 && obs->timeOutputs->timeField & vn::protocol::uart::TIMEGROUP_TIMESTARTUP
7489 && vnSensor->_binaryOutputRegisterMergeObservation->timeOutputs->timeField & vn::protocol::uart::TIMEGROUP_TIMESTARTUP
7490 && (std::chrono::nanoseconds(obs->timeOutputs->timeStartup - vnSensor->_binaryOutputRegisterMergeObservation->timeOutputs->timeStartup) < allowedTimeDiff)))) // NOLINT(hicpp-use-nullptr, modernize-use-nullptr)
7491 {
7492 // Stored and new observation have same time, so merge them
7493 mergeVectorNavBinaryObservations(obs, vnSensor->_binaryOutputRegisterMergeObservation);
7494 vnSensor->_binaryOutputRegisterMergeObservation = nullptr;
7495
7496 std::stringstream sstream;
7497 if (!obs->insTime.empty())
7498 {
7499 sstream << obs->insTime;
7500 }
7501 else
7502 {
7503 sstream << obs->timeOutputs->timeStartup;
7504 }
7505 LOG_DATA("{}: {} - Merged message with time {}", vnSensor->nameId(), b, sstream.str());
7506
7507 vnSensor->invokeCallbacks(std::min(b, vnSensor->_binaryOutputRegisterMergeIndex) + 1, obs);
7508 }
7509 else
7510 {
7511 [[maybe_unused]] long double timeDiff = 0.0L;
7512 std::stringstream sstreamOld;
7513 std::stringstream sstreamNew;
7514 if (!obs->insTime.empty() && !vnSensor->_binaryOutputRegisterMergeObservation->insTime.empty())
7515 {
7516 timeDiff = (obs->insTime - vnSensor->_binaryOutputRegisterMergeObservation->insTime).count();
7517 sstreamOld << vnSensor->_binaryOutputRegisterMergeObservation->insTime;
7518 sstreamNew << obs->insTime;
7519 }
7520 else
7521 {
7522 sstreamOld << vnSensor->_binaryOutputRegisterMergeObservation->timeOutputs->timeStartup;
7523 sstreamNew << obs->timeOutputs->timeStartup;
7524 timeDiff = static_cast<double>(obs->timeOutputs->timeStartup
7525 - vnSensor->_binaryOutputRegisterMergeObservation->timeOutputs->timeStartup)
7526 * 1e-9;
7527 }
7528
7529 LOG_WARN("{}: Merging failed, because timestamps do not match. Potentially lost a message (diff {} sec, old ({}) {}, new ({}) {})",
7530 vnSensor->nameId(), timeDiff, vnSensor->_binaryOutputRegisterMergeIndex, sstreamOld.str(), b, sstreamNew.str());
7531
7532 // Send out old message and save new one
7533 vnSensor->invokeCallbacks(vnSensor->_binaryOutputRegisterMergeIndex + 1, vnSensor->_binaryOutputRegisterMergeObservation);
7534 vnSensor->_binaryOutputRegisterMergeObservation = obs;
7535 vnSensor->_binaryOutputRegisterMergeIndex = b;
7536 }
7537 }
7538 }
7539 else
7540 {
7541 if (!obs->insTime.empty())
7542 {
7543 LOG_DATA("{}: {} - Normal message with time {}", vnSensor->nameId(), b, obs->insTime);
7544 }
7545 // Calls all the callbacks
7546 vnSensor->invokeCallbacks(b + 1, obs);
7547 }
7548 }
7549 }
7550 }
7551 else if (p.type() == vn::protocol::uart::Packet::TYPE_ASCII)
7552 {
7553 LOG_DATA("{} received an ASCII Async message: {}", vnSensor->nameId(), p.datastr());
7554 vnSensor->_asciiOutputBuffer.push_back(p.datastr());
7555
7556 auto obs = std::make_shared<StringObs>(p.datastr());
7557
7558 if (InsTime currentTime = util::time::GetCurrentInsTime();
7559 !currentTime.empty())
7560 {
7561 obs->insTime = currentTime;
7562 }
7563 // Calls all the callbacks
7564 vnSensor->invokeCallbacks(VectorNavSensor::OUTPUT_PORT_INDEX_ASCII_OUTPUT, obs);
7565 }
7566 }
7567