12#include "VoxlVioIngest.h"
24HealthCheck::HealthCheck()
31 last_health_check_ns_ = _apps_time_monotonic_ns();
39HealthCheck::~HealthCheck()
52 std::lock_guard<std::mutex> lock(health_mutex_);
56 std::cerr <<
"HealthCheck already running" << std::endl;
60 running_.store(
true, std::memory_order_release);
61 health_thread_ = std::thread(&HealthCheck::healthCheckLoop,
this);
62 health_thread_.detach();
64 std::cout <<
"HealthCheck started - monitoring at 30Hz" << std::endl;
74 std::lock_guard<std::mutex> lock(health_mutex_);
81 running_.store(
false, std::memory_order_release);
84 std::this_thread::sleep_for(std::chrono::milliseconds(100));
86 std::cout <<
"HealthCheck stopped" << std::endl;
98void HealthCheck::healthCheckLoop()
100 const int64_t health_check_period_ns = 33333333;
104 auto start_time = _apps_time_monotonic_ns();
106 checkSystemConnectivity();
112 std::cerr <<
"[HEALTH] ERROR: IMU disconnected; publishing blank VIO data" << std::endl;
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;
121 monitorSystemPerformance();
122 checkAutoResetConditions();
123 checkVINSResetRequest();
127 health_check_count_++;
128 last_health_check_ns_ = start_time;
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);
136 std::this_thread::sleep_for(std::chrono::nanoseconds(sleep_ns));
147void HealthCheck::analyzeErrorCodes()
152 uint32_t new_errors = current_error_codes & ~last_error_codes_;
153 uint32_t cleared_errors = last_error_codes_ & ~current_error_codes;
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;
161 if (new_errors & ERROR_CODE_COVARIANCE)
163 std::cerr <<
"[HEALTH] ERROR: Covariance matrix not positive definite" << std::endl;
165 if (new_errors & ERROR_CODE_IMU_OOB)
167 std::cerr <<
"[HEALTH] ERROR: IMU exceeded range (out of bounds)" << std::endl;
169 if (new_errors & ERROR_CODE_IMU_BW)
171 std::cerr <<
"[HEALTH] ERROR: IMU bandwidth too low" << std::endl;
173 if (new_errors & ERROR_CODE_NOT_STATIONARY)
178 std::cerr <<
"[HEALTH] ERROR: estimation quality below threshold for too long (bit reused: NOT_STATIONARY)" << std::endl;
180 if (new_errors & ERROR_CODE_NO_FEATURES)
182 std::cerr <<
"[HEALTH] ERROR: No features for extended period" << std::endl;
184 if (new_errors & ERROR_CODE_CONSTRAINT)
186 std::cerr <<
"[HEALTH] ERROR: Insufficient constraints from features" << std::endl;
188 if (new_errors & ERROR_CODE_FEATURE_ADD)
190 std::cerr <<
"[HEALTH] ERROR: Failed to add new features" << std::endl;
192 if (new_errors & ERROR_CODE_VEL_INST_CERT)
194 std::cerr <<
"[HEALTH] ERROR: Exceeded instant velocity uncertainty" << std::endl;
196 if (new_errors & ERROR_CODE_VEL_WINDOW_CERT)
198 std::cerr <<
"[HEALTH] ERROR: Exceeded velocity uncertainty" << std::endl;
200 if (new_errors & ERROR_CODE_DROPPED_IMU)
202 std::cerr <<
"[HEALTH] WARNING: Dropped IMU samples" << std::endl;
204 if (new_errors & ERROR_CODE_BAD_CAM_CAL)
206 std::cerr <<
"[HEALTH] ERROR: Intrinsic camera calibration questionable" << std::endl;
208 if (new_errors & ERROR_CODE_LOW_FEATURES)
210 std::cerr <<
"[HEALTH] ERROR: Insufficient good features to initialize" << std::endl;
212 if (new_errors & ERROR_CODE_DROPPED_CAM)
214 std::cerr <<
"[HEALTH] WARNING: Dropped camera frame" << std::endl;
216 if (new_errors & ERROR_CODE_DROPPED_GPS_VEL)
218 std::cerr <<
"[HEALTH] WARNING: Dropped GPS velocity sample" << std::endl;
220 if (new_errors & ERROR_CODE_BAD_TIMESTAMP)
222 std::cerr <<
"[HEALTH] ERROR: Sensor measurements with bad timestamps" << std::endl;
224 if (new_errors & ERROR_CODE_IMU_MISSING)
226 std::cerr <<
"[HEALTH] ERROR: Missing IMU data" << std::endl;
228 if (new_errors & ERROR_CODE_CAM_MISSING)
230 std::cerr <<
"[HEALTH] ERROR: Missing camera frames" << std::endl;
232 if (new_errors & ERROR_CODE_CAM_BAD_RES)
234 std::cerr <<
"[HEALTH] ERROR: Camera resolution unsupported" << std::endl;
236 if (new_errors & ERROR_CODE_CAM_BAD_FORMAT)
238 std::cerr <<
"[HEALTH] ERROR: Camera format unsupported" << std::endl;
240 if (new_errors & ERROR_CODE_UNKNOWN)
242 std::cerr <<
"[HEALTH] ERROR: Unknown error" << std::endl;
244 if (new_errors & ERROR_CODE_STALLED)
246 std::cerr <<
"[HEALTH] ERROR: Frame processing stalled" << std::endl;
250 if (cleared_errors != 0)
252 std::cout <<
"[HEALTH] Errors cleared: 0x" << std::hex << (int)cleared_errors << std::dec << std::endl;
255 last_error_codes_ = current_error_codes;
264void HealthCheck::checkSystemConnectivity()
268 const int64_t sensor_timeout_ns = 5000000000;
269 int64_t now_ns = _apps_time_monotonic_ns();
275 std::cerr <<
"[HEALTH] IMU likely disconnected --> stale data (no data for "
285 std::cerr <<
"[HEALTH] Camera likely disconnected --> stale data (no data for "
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);
295 if (current_imu_connected != last_imu_connected_)
297 if (current_imu_connected)
299 std::cout <<
"[HEALTH] IMU connected" << std::endl;
304 std::cout <<
"[HEALTH] Reset requested due to IMU reconnection" << std::endl;
308 std::cerr <<
"[HEALTH] ERROR: IMU disconnected" << std::endl;
311 last_imu_connected_ = current_imu_connected;
315 if (current_cam_connected != last_cam_connected_)
317 if (current_cam_connected)
319 std::cout <<
"[HEALTH] Camera connected" << std::endl;
325 if (first_camera_connection_seen_) {
328 std::cout <<
"[HEALTH] Reset requested due to camera reconnection" << std::endl;
330 first_camera_connection_seen_ =
true;
335 std::cerr <<
"[HEALTH] ERROR: Camera disconnected" << std::endl;
338 last_cam_connected_ = current_cam_connected;
342 uint8_t current_vio_state =
vio_state.load(std::memory_order_acquire);
343 if (current_vio_state != last_vio_state_)
345 std::cout <<
"[HEALTH] VIO state changed: " << (int)last_vio_state_ <<
" -> " << (
int)current_vio_state << std::endl;
346 last_vio_state_ = current_vio_state;
356void HealthCheck::monitorSystemPerformance()
358 static int64_t last_performance_log_ns = 0;
359 int64_t current_time_ns = _apps_time_monotonic_ns();
362 if (current_time_ns - last_performance_log_ns > 5000000000)
364 std::cout <<
"[HEALTH] Performance - Health checks: " << health_check_count_
368 last_performance_log_ns = current_time_ns;
369 health_check_count_ = 0;
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;
391static constexpr int64_t WARNING_DECAY_NS = 10000000000LL;
393void HealthCheck::checkAutoResetConditions()
401 int64_t now = _apps_time_monotonic_ns();
402 if (now - time_of_last_reset < INIT_FAILURE_TIMEOUT_NS)
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)
423 last_warn_raise_ns = now;
425 last_warn_codes = warn_codes;
426 if (warn_codes != 0 && (now - last_warn_raise_ns) > WARNING_DECAY_NS)
431 uint32_t reset_codes = current_error_codes & AUTO_RESET_ERROR_MASK;
432 if (reset_codes != 0)
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;
448void HealthCheck::checkVINSResetRequest()
451 bool want_hard =
reset_requested.exchange(
false, std::memory_order_acq_rel);
453 if (!want_hard && !want_soft)
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)
463 std::cout <<
"[HEALTH] Reset requested but last reset was too recent ("
464 << (time_since_reset / 1000000) <<
"ms ago), ignoring request" << std::endl;
469 if (
is_resetting.exchange(
true, std::memory_order_acq_rel))
471 std::cout <<
"[HEALTH] Reset already in progress, ignoring request\n";
476 std::cout <<
"[HEALTH] Reset requested, preparing to reset VIO system" << std::endl;
481 rc = want_soft ? doSoftReset() : doHardReset();
489 catch (
const std::exception &e)
491 fprintf(stderr,
"[ERROR] Exception during reset: %s\n", e.what());
493 if (strstr(e.what(),
"Operation not permitted") !=
nullptr)
495 fprintf(stderr,
"[ERROR] Permission denied during reset - this may be due to insufficient privileges\n");
502 std::cout <<
"[HEALTH] VIO system reset successfully" << std::endl;
505 vio_state.store(VIO_STATE_INITIALIZING, std::memory_order_release);
506 std::cout <<
"[HEALTH] VIO state set to INITIALIZING after reset" << std::endl;
514 std::cerr <<
"[HEALTH] VIO system reset failed with code: " << rc << std::endl;
516 vio_state.store(VIO_STATE_INITIALIZING, std::memory_order_release);
521 time_of_last_reset = _apps_time_monotonic_ns();
528int HealthCheck::doHardReset()
532 std::unique_lock<std::mutex> lk(
reset_mtx);
534 bool wait_result =
reset_cv.wait_for(lk, std::chrono::seconds(5),
543 fprintf(stderr,
"[ERROR] Timeout waiting for callbacks to finish during reset. active_callbacks=%d\n",
550 printf(
"[HEALTH] Hard reset in progress\n");
558 std::cout <<
"[HEALTH] VIO manager was uninitialized, creating a fresh instance" << std::endl;
565 catch (
const std::exception &e)
567 fprintf(stderr,
"[ERROR] Failed to create VIO manager during reset: %s\n", e.what());
575 std::unique_ptr<ov_msckf::VioManager> old_vio_manager;
576 std::unique_ptr<ov_msckf::VioManager> new_vio_manager;
583 if (!new_vio_manager)
585 fprintf(stderr,
"[ERROR] Failed to create new VIO manager object\n");
586 throw std::runtime_error(
"Failed to create new VIO manager");
592 catch (
const std::exception &e)
594 fprintf(stderr,
"[ERROR] Exception during VIO manager creation: %s\n", e.what());
601 std::cerr <<
"[HEALTH] Warning: no previous VIO manager to restore" << std::endl;
607 old_vio_manager.reset();
618int HealthCheck::doSoftReset()
622 std::unique_lock<std::mutex> lk(
reset_mtx);
623 bool wait_result =
reset_cv.wait_for(lk, std::chrono::seconds(5),
627 fprintf(stderr,
"[ERROR] Timeout waiting for callbacks during soft reset. active_callbacks=%d\n",
636 const uint32_t pending_errors =
vio_error_codes.load(std::memory_order_acquire);
638 printf(
"[HEALTH] Soft reset in progress (front-end preserved%s)\n",
639 (pending_errors != 0) ?
", divergence-suspected" :
"");
643 fprintf(stderr,
"[ERROR] No VIO manager to soft-reset; escalating to hard reset\n");
644 return doHardReset();
651 vio_manager->soft_reset(pending_errors != 0 ? ov_msckf::VioManager::SoftResetCause::DIVERGENCE
652 : ov_msckf::VioManager::SoftResetCause::CLIENT);
654 catch (
const std::exception &e)
656 fprintf(stderr,
"[ERROR] Exception during soft reset: %s -- escalating to hard reset\n", e.what());
657 return doHardReset();
679 uint32_t current_errors =
vio_error_codes.load(std::memory_order_acquire);
680 uint32_t new_errors = current_errors & ~error_mask;
686 std::cout <<
"[HEALTH] Cleared error codes: 0x" << std::hex << (int)error_mask << std::dec << std::endl;
Housekeeping and data publishing for VOXL OpenVINS.
volatile int64_t last_cam_time
Timestamp of last camera data (nanoseconds)
volatile int64_t last_imu_timestamp_ns
Timestamp of last IMU data (nanoseconds)
volatile int main_running
Main process running flag.
std::atomic< uint32_t > active_callbacks
Number of callbacks inside the system.
ov_msckf::VioManagerOptions vio_manager_options
VIO manager options.
int en_auto_reset
Enable automatic reset functionality.
std::mutex reset_mtx
Mutex used by reset thread.
std::atomic< uint32_t > soft_reset_num_counter
Counter which increments only on SOFT (front-end-preserving) resets, so field logs can distinguish so...
std::atomic< uint32_t > reset_num_counter
Counter which increments on resets (hard + soft)
std::unique_ptr< ov_msckf::VioManager > vio_manager
Main VIO manager instance.
int en_debug
Enable debug output.
std::condition_variable reset_cv
Reset conditional variable.
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.
void stop()
Stop the health check system.
void set_first_packet(bool first_packet_)
Set the first packet flag.
static Publisher & getInstance()
Get singleton instance.
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)