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