VOXL OpenVINS Server 0.7.0
Visual Inertial Odometry Server for VOXL Platform
Loading...
Searching...
No Matches
VoxlHealth.cpp
Go to the documentation of this file.
1/**
2 * @file VoxlHealth.cpp
3 * @brief Health check implementation for VOXL OpenVINS
4 * @author Joao Leonardo Silva Cotta (@zauberflote1)
5 * @date 2025
6 * @version 1.0
7 *
8 * This file implements the health check system for the VOXL OpenVINS server.
9 */
10
11#include "VoxlHK.h"
12#include "VoxlVioIngest.h"
13using namespace voxl;
14// ============================================================================
15// HEALTH CHECK IMPLEMENTATION
16// ============================================================================
17
18/**
19 * @brief Constructor for HealthCheck
20 *
21 * Initializes the health check system with default values.
22 * The health check is not started until start() is called.
23 */
24HealthCheck::HealthCheck()
25{
26 // Initialize with current system state
27 last_error_codes_ = vio_error_codes.load();
28 last_vio_state_ = vio_state.load();
29 last_imu_connected_ = is_imu_connected.load();
30 last_cam_connected_ = is_cam_connected.load();
31 last_health_check_ns_ = _apps_time_monotonic_ns();
32}
33
34/**
35 * @brief Destructor for HealthCheck
36 *
37 * Ensures proper cleanup by calling stop() if the health check is still running.
38 */
39HealthCheck::~HealthCheck()
40{
41 stop();
42}
43
44/**
45 * @brief Start the health check system
46 *
47 * Initializes and starts the health monitoring thread that runs at 30Hz.
48 * The thread continuously monitors system health and error conditions.
49 */
51{
52 std::lock_guard<std::mutex> lock(health_mutex_);
53
54 if (running_.load())
55 {
56 std::cerr << "HealthCheck already running" << std::endl;
57 return;
58 }
59
60 running_.store(true, std::memory_order_release);
61 health_thread_ = std::thread(&HealthCheck::healthCheckLoop, this);
62 health_thread_.detach();
63
64 std::cout << "HealthCheck started - monitoring at 30Hz" << std::endl;
65}
66
67/**
68 * @brief Stop the health check system
69 *
70 * Stops the health monitoring thread and performs cleanup.
71 */
73{
74 std::lock_guard<std::mutex> lock(health_mutex_);
75
76 if (!running_.load())
77 {
78 return;
79 }
80
81 running_.store(false, std::memory_order_release);
82
83 // Give the thread a moment to finish
84 std::this_thread::sleep_for(std::chrono::milliseconds(100));
85
86 std::cout << "HealthCheck stopped" << std::endl;
87}
88
89/**
90 * @brief Main health check loop
91 *
92 * Runs at 30Hz and performs comprehensive health monitoring including:
93 * - Error code analysis and logging
94 * - System state validation
95 * - Performance monitoring
96 * - Auto-reset condition checking
97 */
98void HealthCheck::healthCheckLoop()
99{
100 const int64_t health_check_period_ns = 33333333; // 30Hz = ~33.33ms
101
102 while (running_.load() && main_running)
103 {
104 auto start_time = _apps_time_monotonic_ns();
105 // Update connectivity status first
106 checkSystemConnectivity();
107
108 // Publish blank VIO data packets when sensors are missing
109 if (!is_imu_connected.load() || !is_cam_connected.load())
110 {
111 if (!is_imu_connected.load())
112 std::cerr << "[HEALTH] ERROR: IMU disconnected; publishing blank VIO data" << std::endl;
113 if (!is_cam_connected.load())
114 std::cerr << "[HEALTH] ERROR: Camera disconnected; publishing blank VIO data" << std::endl;
115 std::cout << "[HEALTH] Publishing blank VIO packet due to missing sensors" << std::endl;
116 Publisher::getInstance().publishBlank();
117 }
118 else
119 {
120 analyzeErrorCodes();
121 monitorSystemPerformance();
122 checkAutoResetConditions();
123 checkVINSResetRequest();
124 }
125
126 // Update counters
127 health_check_count_++;
128 last_health_check_ns_ = start_time;
129
130 // Sleep to maintain 30Hz rate
131 int64_t elapsed_ns = _apps_time_monotonic_ns() - start_time;
132 int64_t sleep_ns = std::max<int64_t>(0, health_check_period_ns - elapsed_ns);
133
134 if (sleep_ns > 0)
135 {
136 std::this_thread::sleep_for(std::chrono::nanoseconds(sleep_ns));
137 }
138 }
139}
140
141/**
142 * @brief Analyze and log error codes
143 *
144 * Examines the current error codes and logs detailed information
145 * about any active errors or warnings.
146 */
147void HealthCheck::analyzeErrorCodes()
148{
149 uint32_t current_error_codes = vio_error_codes.load();
150
151 // Check for new errors
152 uint32_t new_errors = current_error_codes & ~last_error_codes_;
153 uint32_t cleared_errors = last_error_codes_ & ~current_error_codes;
154
155 if (new_errors != 0)
156 {
157 std::cerr << "[HEALTH] New errors detected: 0x" << std::hex << (int)new_errors
158 << " (all current: 0x" << (int)current_error_codes << ")" << std::dec << std::endl;
159
160 // Log specific error details
161 if (new_errors & ERROR_CODE_COVARIANCE)
162 {
163 std::cerr << "[HEALTH] ERROR: Covariance matrix not positive definite" << std::endl;
164 }
165 if (new_errors & ERROR_CODE_IMU_OOB)
166 {
167 std::cerr << "[HEALTH] ERROR: IMU exceeded range (out of bounds)" << std::endl;
168 }
169 if (new_errors & ERROR_CODE_IMU_BW)
170 {
171 std::cerr << "[HEALTH] ERROR: IMU bandwidth too low" << std::endl;
172 }
173 if (new_errors & ERROR_CODE_NOT_STATIONARY)
174 {
175 // NOTE: this bit is raised by the publisher's quality-bad-for-too-long timeout
176 // (auto_reset_max_v_cov_timeout_s), not by a stationarity check -- the pipe
177 // interface has no dedicated code for it, so it reuses NOT_STATIONARY
178 std::cerr << "[HEALTH] ERROR: estimation quality below threshold for too long (bit reused: NOT_STATIONARY)" << std::endl;
179 }
180 if (new_errors & ERROR_CODE_NO_FEATURES)
181 {
182 std::cerr << "[HEALTH] ERROR: No features for extended period" << std::endl;
183 }
184 if (new_errors & ERROR_CODE_CONSTRAINT)
185 {
186 std::cerr << "[HEALTH] ERROR: Insufficient constraints from features" << std::endl;
187 }
188 if (new_errors & ERROR_CODE_FEATURE_ADD)
189 {
190 std::cerr << "[HEALTH] ERROR: Failed to add new features" << std::endl;
191 }
192 if (new_errors & ERROR_CODE_VEL_INST_CERT)
193 {
194 std::cerr << "[HEALTH] ERROR: Exceeded instant velocity uncertainty" << std::endl;
195 }
196 if (new_errors & ERROR_CODE_VEL_WINDOW_CERT)
197 {
198 std::cerr << "[HEALTH] ERROR: Exceeded velocity uncertainty" << std::endl;
199 }
200 if (new_errors & ERROR_CODE_DROPPED_IMU)
201 {
202 std::cerr << "[HEALTH] WARNING: Dropped IMU samples" << std::endl;
203 }
204 if (new_errors & ERROR_CODE_BAD_CAM_CAL)
205 {
206 std::cerr << "[HEALTH] ERROR: Intrinsic camera calibration questionable" << std::endl;
207 }
208 if (new_errors & ERROR_CODE_LOW_FEATURES)
209 {
210 std::cerr << "[HEALTH] ERROR: Insufficient good features to initialize" << std::endl;
211 }
212 if (new_errors & ERROR_CODE_DROPPED_CAM)
213 {
214 std::cerr << "[HEALTH] WARNING: Dropped camera frame" << std::endl;
215 }
216 if (new_errors & ERROR_CODE_DROPPED_GPS_VEL)
217 {
218 std::cerr << "[HEALTH] WARNING: Dropped GPS velocity sample" << std::endl;
219 }
220 if (new_errors & ERROR_CODE_BAD_TIMESTAMP)
221 {
222 std::cerr << "[HEALTH] ERROR: Sensor measurements with bad timestamps" << std::endl;
223 }
224 if (new_errors & ERROR_CODE_IMU_MISSING)
225 {
226 std::cerr << "[HEALTH] ERROR: Missing IMU data" << std::endl;
227 }
228 if (new_errors & ERROR_CODE_CAM_MISSING)
229 {
230 std::cerr << "[HEALTH] ERROR: Missing camera frames" << std::endl;
231 }
232 if (new_errors & ERROR_CODE_CAM_BAD_RES)
233 {
234 std::cerr << "[HEALTH] ERROR: Camera resolution unsupported" << std::endl;
235 }
236 if (new_errors & ERROR_CODE_CAM_BAD_FORMAT)
237 {
238 std::cerr << "[HEALTH] ERROR: Camera format unsupported" << std::endl;
239 }
240 if (new_errors & ERROR_CODE_UNKNOWN)
241 {
242 std::cerr << "[HEALTH] ERROR: Unknown error" << std::endl;
243 }
244 if (new_errors & ERROR_CODE_STALLED)
245 {
246 std::cerr << "[HEALTH] ERROR: Frame processing stalled" << std::endl;
247 }
248 }
249
250 if (cleared_errors != 0)
251 {
252 std::cout << "[HEALTH] Errors cleared: 0x" << std::hex << (int)cleared_errors << std::dec << std::endl;
253 }
254
255 last_error_codes_ = current_error_codes;
256}
257
258/**
259 * @brief Check system connectivity
260 *
261 * Monitors the connection status of cameras and IMU, logging
262 * any disconnection events or connectivity issues.
263 */
264void HealthCheck::checkSystemConnectivity()
265{
266 // THIS IS A REDO OF THE PAST SYSTEM CONNECTIVITY CHECK INSIDE monitorSystemPerformance --> THIS IS A BETTER APPROACH
267 // Detect stale sensor data
268 const int64_t sensor_timeout_ns = 5000000000; // 5 second timeout --> MAYBE MAKE THIS SMALLER IF NEEDED BE
269 int64_t now_ns = _apps_time_monotonic_ns();
270 // If no new IMU data within timeout, mark IMU as disconnected
271 if (last_imu_timestamp_ns != 0 && now_ns - last_imu_timestamp_ns > sensor_timeout_ns)
272 {
273 if (is_imu_connected.load(std::memory_order_acquire))
274 {
275 std::cerr << "[HEALTH] IMU likely disconnected --> stale data (no data for "
276 << (now_ns - last_imu_timestamp_ns) / 1000000 << "ms)" << std::endl;
277 }
278 is_imu_connected.store(false, std::memory_order_release);
279 }
280 // If no new camera data within timeout, mark camera as disconnected
281 if (last_cam_time != 0 && now_ns - last_cam_time > sensor_timeout_ns)
282 {
283 if (is_cam_connected.load(std::memory_order_acquire))
284 {
285 std::cerr << "[HEALTH] Camera likely disconnected --> stale data (no data for "
286 << (now_ns - last_cam_time) / 1000000 << "ms)" << std::endl;
287 }
288 is_cam_connected.store(false, std::memory_order_release);
289 }
290
291 bool current_imu_connected = is_imu_connected.load(std::memory_order_acquire);
292 bool current_cam_connected = is_cam_connected.load(std::memory_order_acquire);
293
294 // Check IMU connection changes
295 if (current_imu_connected != last_imu_connected_)
296 {
297 if (current_imu_connected)
298 {
299 std::cout << "[HEALTH] IMU connected" << std::endl;
300 // Clear IMU-related errors when connection is restored --> CURRENTLY CLEANING ALL ERRORS
301 clearErrorCodes(0, true);
302 // Request reset upon IMU reconnection
303 reset_requested.store(true, std::memory_order_release);
304 std::cout << "[HEALTH] Reset requested due to IMU reconnection" << std::endl;
305 }
306 else
307 {
308 std::cerr << "[HEALTH] ERROR: IMU disconnected" << std::endl;
309 vio_error_codes |= ERROR_CODE_IMU_MISSING;
310 }
311 last_imu_connected_ = current_imu_connected;
312 }
313
314 // Check camera connection changes
315 if (current_cam_connected != last_cam_connected_)
316 {
317 if (current_cam_connected)
318 {
319 std::cout << "[HEALTH] Camera connected" << std::endl;
320 // Clear camera-related errors when connection is restored
321 // CAN PROBABLY CLEAR ALL ERRORS HERE...
322 clearErrorCodes(ERROR_CODE_CAM_MISSING | ERROR_CODE_DROPPED_CAM);
323
324 // Don't trigger a reset on the very first connection
325 if (first_camera_connection_seen_) {
326 // Request reset upon camera reconnection
327 reset_requested.store(true, std::memory_order_release);
328 std::cout << "[HEALTH] Reset requested due to camera reconnection" << std::endl;
329 } else {
330 first_camera_connection_seen_ = true; // no reset on first connection
331 }
332 }
333 else
334 {
335 std::cerr << "[HEALTH] ERROR: Camera disconnected" << std::endl;
336 vio_error_codes |= ERROR_CODE_CAM_MISSING;
337 }
338 last_cam_connected_ = current_cam_connected;
339 }
340
341 // Check VIO state changes
342 uint8_t current_vio_state = vio_state.load(std::memory_order_acquire);
343 if (current_vio_state != last_vio_state_)
344 {
345 std::cout << "[HEALTH] VIO state changed: " << (int)last_vio_state_ << " -> " << (int)current_vio_state << std::endl;
346 last_vio_state_ = current_vio_state;
347 }
348}
349
350/**
351 * @brief Monitor system performance
352 *
353 * Tracks system performance metrics including processing rates,
354 * memory usage, and timing statistics.
355 */
356void HealthCheck::monitorSystemPerformance()
357{
358 static int64_t last_performance_log_ns = 0;
359 int64_t current_time_ns = _apps_time_monotonic_ns();
360
361 // Log performance metrics every 5 seconds
362 if (current_time_ns - last_performance_log_ns > 5000000000)
363 { // 5 seconds
364 std::cout << "[HEALTH] Performance - Health checks: " << health_check_count_
365 << ", IMU timestamp: " << last_imu_timestamp_ns
366 << ", Camera timestamp: " << last_cam_time << std::endl;
367
368 last_performance_log_ns = current_time_ns;
369 health_check_count_ = 0; // Reset counter
370 }
371}
372
373/**
374 * @brief Check auto-reset conditions
375 *
376 * Evaluates whether auto-reset conditions are met based on
377 * current system state and error conditions.
378 */
379// Only estimator-health codes justify an automatic hard reset: they mean the filter itself is
380// (suspected) poisoned. Transport warnings -- DROPPED_CAM/IMU/GPS, LOW_FEATURES, bad
381// format/timestamp glitches -- and missing-sensor codes must NOT nuke a healthy filter: the
382// async ingest legitimately disposes frames (ordering, staleness, reset teardown), and a single
383// such drop used to hard-reset the estimator mid-flight and loop. Missing sensors have their
384// own reconnect handling above.
385static constexpr uint32_t AUTO_RESET_ERROR_MASK =
386 ERROR_CODE_COVARIANCE | ERROR_CODE_IMU_OOB | ERROR_CODE_IMU_BW | ERROR_CODE_NOT_STATIONARY | ERROR_CODE_NO_FEATURES |
387 ERROR_CODE_CONSTRAINT | ERROR_CODE_FEATURE_ADD | ERROR_CODE_VEL_INST_CERT | ERROR_CODE_VEL_WINDOW_CERT;
388
389// Warning-class codes decay after this quiet period (no new raise) so a one-off drop does not
390// stick in the published error field forever (they only cleared on reconnect transitions before).
391static constexpr int64_t WARNING_DECAY_NS = 10000000000LL; // 10 s
392
393void HealthCheck::checkAutoResetConditions()
394{
395 if (!en_auto_reset)
396 {
397 return; // Auto-reset disabled
398 }
399
400 // Suppress auto-reset for a grace period after a hard reset to allow sensors to come back online
401 int64_t now = _apps_time_monotonic_ns();
402 if (now - time_of_last_reset < INIT_FAILURE_TIMEOUT_NS)
403 {
404 return;
405 }
406
407 // Also skip auto-reset logic while the VIO manager is still initializing. OpenVINS may
408 // require several seconds of IMU/vision data and heavy optimization before the
409 // "initialized()" flag is set; triggering another reset in that window leads to a loop.
410 if (!vio_manager || !vio_manager->initialized())
411 {
412 return;
413 }
414
415 uint32_t current_error_codes = vio_error_codes.load();
416
417 // Decay stale transport warnings (single-writer: this health thread)
418 static uint32_t last_warn_codes = 0;
419 static int64_t last_warn_raise_ns = 0;
420 uint32_t warn_codes = current_error_codes & ~AUTO_RESET_ERROR_MASK;
421 if ((warn_codes & ~last_warn_codes) != 0)
422 {
423 last_warn_raise_ns = now;
424 }
425 last_warn_codes = warn_codes;
426 if (warn_codes != 0 && (now - last_warn_raise_ns) > WARNING_DECAY_NS)
427 {
428 clearErrorCodes(warn_codes);
429 }
430
431 uint32_t reset_codes = current_error_codes & AUTO_RESET_ERROR_MASK;
432 if (reset_codes != 0)
433 {
434 std::cerr << "[HEALTH] AUTO-RESET RECOMMENDED: Error code(s) detected: 0x" << std::hex << (int)reset_codes
435 << " (all codes: 0x" << (int)current_error_codes << ")" << std::dec << std::endl;
436 clearErrorCodes(0, true);
437 // Set reset flag (this would trigger reset in main loop)
438 reset_requested.store(true, std::memory_order_release);
439 }
440}
441
442/**
443 * @brief Check for VINS reset request
444 *
445 * This function checks if a reset has been requested and handles the reset process.
446 * It ensures that only one reset operation can be in progress at a time.
447 */
448void HealthCheck::checkVINSResetRequest()
449{
450 // atomically check if a (hard or soft) reset has been requested, if not, return
451 bool want_hard = reset_requested.exchange(false, std::memory_order_acq_rel);
452 bool want_soft = soft_reset_requested.exchange(false, std::memory_order_acq_rel);
453 if (!want_hard && !want_soft)
454 return;
455 if (want_hard) // a hard request supersedes a pending soft one
456 want_soft = false;
457
458 // check time since last reset
459 int64_t current_time = _apps_time_monotonic_ns();
460 uint64_t time_since_reset = current_time - time_of_last_reset;
461 if (time_since_reset <= INIT_FAILURE_TIMEOUT_NS)
462 {
463 std::cout << "[HEALTH] Reset requested but last reset was too recent ("
464 << (time_since_reset / 1000000) << "ms ago), ignoring request" << std::endl;
465 return;
466 }
467
468 // If reset is requested, check if we are already resetting
469 if (is_resetting.exchange(true, std::memory_order_acq_rel))
470 {
471 std::cout << "[HEALTH] Reset already in progress, ignoring request\n";
472 return;
473 }
474
475 if (en_debug)
476 std::cout << "[HEALTH] Reset requested, preparing to reset VIO system" << std::endl;
477
478 int rc = 0;
479 try
480 {
481 rc = want_soft ? doSoftReset() : doHardReset();
482 reset_num_counter.fetch_add(1, std::memory_order_acq_rel);
483 if (want_soft)
484 soft_reset_num_counter.fetch_add(1, std::memory_order_acq_rel);
485 printf("[HEALTH] reset #%u (%s): soft=%u hard=%u\n", reset_num_counter.load(),
486 want_soft ? "soft" : "hard", soft_reset_num_counter.load(),
488 }
489 catch (const std::exception &e)
490 {
491 fprintf(stderr, "[ERROR] Exception during reset: %s\n", e.what());
492 // Check if it's a permission error
493 if (strstr(e.what(), "Operation not permitted") != nullptr)
494 {
495 fprintf(stderr, "[ERROR] Permission denied during reset - this may be due to insufficient privileges\n");
496 }
497 rc = -1;
498 }
499
500 if (rc == 0)
501 {
502 std::cout << "[HEALTH] VIO system reset successfully" << std::endl;
503
504 // CRITICAL: Clear VIO state back to INITIALIZING after successful reset
505 vio_state.store(VIO_STATE_INITIALIZING, std::memory_order_release);
506 std::cout << "[HEALTH] VIO state set to INITIALIZING after reset" << std::endl;
507
508 // Clear last sensor timestamps; they will be filled when fresh data arrives
510 last_cam_time = 0;
511 }
512 else
513 {
514 std::cerr << "[HEALTH] VIO system reset failed with code: " << rc << std::endl;
515 // Even on failure, set to INITIALIZING so system can try again
516 vio_state.store(VIO_STATE_INITIALIZING, std::memory_order_release);
517 // Clear reset flags even on failure to prevent getting stuck --> PRIME MOVE HERE
518 reset_requested.store(false, std::memory_order_release);
519 }
520
521 time_of_last_reset = _apps_time_monotonic_ns();
522
523 is_resetting.store(false, std::memory_order_release);
524 reset_cv.notify_all();
525 return;
526}
527
528int HealthCheck::doHardReset()
529{
530 // wait until all callbacks have finished processing
531 {
532 std::unique_lock<std::mutex> lk(reset_mtx);
533 // Add timeout to prevent infinite blocking --> FOR NOW, 5 SECONDS
534 bool wait_result = reset_cv.wait_for(lk, std::chrono::seconds(5),
535 [this]
536 {
537 auto cur = active_callbacks.load(std::memory_order_acquire);
538 return cur == 0;
539 });
540
541 if (!wait_result)
542 {
543 fprintf(stderr, "[ERROR] Timeout waiting for callbacks to finish during reset. active_callbacks=%d\n",
544 active_callbacks.load(std::memory_order_acquire));
545 return -1;
546 }
547 }
549 clearErrorCodes(0, true);
550 printf("[HEALTH] Hard reset in progress\n");
551
552 // ensure we have a valid VIO manager; if not, create one directly
553 // (was `!vio_manager && !vio_manager->initialized()`: short-circuit guaranteed a null deref
554 // whenever the first operand was true)
555 if (!vio_manager)
556 {
557 if (en_debug)
558 std::cout << "[HEALTH] VIO manager was uninitialized, creating a fresh instance" << std::endl;
559
560 try
561 {
562 vio_manager = std::make_unique<ov_msckf::VioManager>(vio_manager_options);
564 }
565 catch (const std::exception &e)
566 {
567 fprintf(stderr, "[ERROR] Failed to create VIO manager during reset: %s\n", e.what());
568 return -1;
569 }
570
571 return 0; // fresh manager created, nothing else to reset
572 }
573
574 // Create references for old and new VIO manager
575 std::unique_ptr<ov_msckf::VioManager> old_vio_manager;
576 std::unique_ptr<ov_msckf::VioManager> new_vio_manager;
577
578 try
579 {
580 new_vio_manager = std::make_unique<ov_msckf::VioManager>(vio_manager_options);
581 voxl::register_vio_camera_callback(*new_vio_manager);
582
583 if (!new_vio_manager)
584 {
585 fprintf(stderr, "[ERROR] Failed to create new VIO manager object\n");
586 throw std::runtime_error("Failed to create new VIO manager");
587 }
588
589 old_vio_manager = std::move(vio_manager);
590 vio_manager = std::move(new_vio_manager);
591 }
592 catch (const std::exception &e)
593 {
594 fprintf(stderr, "[ERROR] Exception during VIO manager creation: %s\n", e.what());
595 if (old_vio_manager)
596 {
597 vio_manager = std::move(old_vio_manager); // restore previous manager
598 }
599 else
600 {
601 std::cerr << "[HEALTH] Warning: no previous VIO manager to restore" << std::endl;
602 }
603 return -1;
604 }
605
606 // destroy old VIO manager
607 old_vio_manager.reset();
608
609 // The old manager's ingest buffer just disposed every still-queued frame through the
610 // processed-callback, which re-raised ERROR_CODE_DROPPED_CAM AFTER the clear at the top of
611 // this reset -- the fresh manager then booted pre-poisoned and re-reset the moment it
612 // initialized. Teardown housekeeping is not a health event: clear again.
613 clearErrorCodes(0, true);
614
615 return 0;
616}
617
618int HealthCheck::doSoftReset()
619{
620 // Wait for in-flight sensor callbacks to drain (same handshake as the hard reset).
621 {
622 std::unique_lock<std::mutex> lk(reset_mtx);
623 bool wait_result = reset_cv.wait_for(lk, std::chrono::seconds(5),
624 [this] { return active_callbacks.load(std::memory_order_acquire) == 0; });
625 if (!wait_result)
626 {
627 fprintf(stderr, "[ERROR] Timeout waiting for callbacks during soft reset. active_callbacks=%d\n",
628 active_callbacks.load(std::memory_order_acquire));
629 return -1;
630 }
631 }
633 // Capture the pending health error codes BEFORE clearing them: a soft reset issued while
634 // errors are pending is divergence-suspected, and the initializer's bias-prior gating must
635 // know (it inflates the snapshot sigmas instead of trusting a possibly-poisoned filter).
636 const uint32_t pending_errors = vio_error_codes.load(std::memory_order_acquire);
637 clearErrorCodes(0, true);
638 printf("[HEALTH] Soft reset in progress (front-end preserved%s)\n",
639 (pending_errors != 0) ? ", divergence-suspected" : "");
640
641 if (!vio_manager)
642 {
643 fprintf(stderr, "[ERROR] No VIO manager to soft-reset; escalating to hard reset\n");
644 return doHardReset();
645 }
646 try
647 {
648 // Front-end-preserving EKF reset: keeps the feature DB + IMU history so the improved
649 // any-attitude dynamic initializer re-fires fast (no full-window re-collection). The
650 // cause tag drives the live-bias prior gating inside the initializer.
651 vio_manager->soft_reset(pending_errors != 0 ? ov_msckf::VioManager::SoftResetCause::DIVERGENCE
652 : ov_msckf::VioManager::SoftResetCause::CLIENT);
653 }
654 catch (const std::exception &e)
655 {
656 fprintf(stderr, "[ERROR] Exception during soft reset: %s -- escalating to hard reset\n", e.what());
657 return doHardReset();
658 }
659 return 0;
660}
661
662/**
663 * @brief Clear specific error codes
664 *
665 * Clears the specified error codes from the global error state.
666 * This is useful when errors are resolved and should no longer
667 * be reported.
668 *
669 * @param error_mask Bit mask of error codes to clear
670 */
671void HealthCheck::clearErrorCodes(uint32_t error_mask, bool clear_all)
672{
673 if (clear_all)
674 {
675 vio_error_codes.store(0, std::memory_order_release);
676 }
677 else
678 {
679 uint32_t current_errors = vio_error_codes.load(std::memory_order_acquire);
680 uint32_t new_errors = current_errors & ~error_mask;
681 vio_error_codes.store(new_errors, std::memory_order_release);
682 }
683
684 if (en_debug)
685 {
686 std::cout << "[HEALTH] Cleared error codes: 0x" << std::hex << (int)error_mask << std::dec << std::endl;
687 }
688}
Housekeeping and data publishing for VOXL OpenVINS.
volatile int64_t last_cam_time
Timestamp of last camera data (nanoseconds)
Definition VoxlVars.cpp:188
volatile int64_t last_imu_timestamp_ns
Timestamp of last IMU data (nanoseconds)
Definition VoxlVars.cpp:178
volatile int main_running
Main process running flag.
Definition VoxlVars.cpp:38
std::atomic< uint32_t > active_callbacks
Number of callbacks inside the system.
Definition VoxlVars.cpp:63
ov_msckf::VioManagerOptions vio_manager_options
VIO manager options.
Definition VoxlVars.cpp:28
int en_auto_reset
Enable automatic reset functionality.
Definition VoxlVars.cpp:84
std::mutex reset_mtx
Mutex used by reset thread.
Definition VoxlVars.cpp:66
std::atomic< uint32_t > soft_reset_num_counter
Counter which increments only on SOFT (front-end-preserving) resets, so field logs can distinguish so...
Definition VoxlVars.cpp:60
std::atomic< uint32_t > reset_num_counter
Counter which increments on resets (hard + soft)
Definition VoxlVars.cpp:56
std::unique_ptr< ov_msckf::VioManager > vio_manager
Main VIO manager instance.
Definition VoxlVars.cpp:31
int en_debug
Enable debug output.
Definition VoxlVars.cpp:149
std::condition_variable reset_cv
Reset conditional variable.
Definition VoxlVars.cpp:69
std::atomic< bool > is_resetting
VIO reset state flag.
std::atomic< uint8_t > vio_state
Current VIO system state.
std::atomic< uint32_t > vio_error_codes
VIO error codes.
std::atomic< bool > is_imu_connected
IMU connection state.
std::atomic< bool > reset_requested
Should reset floag.
std::atomic< bool > soft_reset_requested
Flag indicating that a front-end-preserving SOFT reset was requested.
std::atomic< bool > is_cam_connected
Camera connection state.
void start()
Start the health check system.
static void clearErrorCodes(uint32_t error_mask)
Clear specific error codes.
Definition VoxlHK.h:353
void stop()
Stop the health check system.
void set_first_packet(bool first_packet_)
Set the first packet flag.
Definition VoxlHK.h:237
static Publisher & getInstance()
Get singleton instance.
Definition VoxlHK.h:137
Main namespace for VOXL OpenVINS server components.
void register_vio_camera_callback(ov_msckf::VioManager &vm)
Install the per-frame hook on a VioManager (call once per instance, right after construction)