Fault Manager Configuration
The ros2_medkit_fault_manager node aggregates and manages faults from multiple sources.
This page documents all configuration parameters.
Basic Configuration
Storage
fault_manager:
ros__parameters:
storage_type: "sqlite" # Storage backend: "sqlite" or "memory"
database_path: "/var/lib/ros2_medkit/faults.db" # Path for sqlite storage
Parameter |
Default |
Description |
|---|---|---|
|
|
Storage backend. |
|
|
File path for SQLite database. Directory must exist and be writable. |
Debounce Settings
The fault manager uses AUTOSAR DEM-style debounce filtering to prevent fault flapping.
fault_manager:
ros__parameters:
confirmation_threshold: -1 # Counter threshold to confirm fault
healing_enabled: false # Enable auto-healing via PASSED events
healing_threshold: 3 # Counter threshold to heal fault
auto_confirm_after_sec: 0.0 # Auto-confirm timeout (0 = disabled)
Parameter |
Default |
Description |
|---|---|---|
|
|
Number of FAILED events to confirm fault. Negative values mean more events needed.
Use |
|
|
When true, PASSED events can heal confirmed faults. |
|
|
Counter value at which a fault heals, not a number of events. Healing costs
|
|
|
Auto-confirm prefailed faults after this duration. Set to 0 to disable. |
Tip
For immediate fault confirmation (no debounce), set confirmation_threshold: -1,
which is also the default. 0 is rejected: the threshold must be strictly
negative, and the node falls back to -1 with a warning.
Faults with SEVERITY_CRITICAL always bypass debounce regardless of this setting.
Important
The counter only moves when an event arrives, so confirmation_threshold and
healing_threshold are tunable only for a reporter that keeps sending events while a
condition holds. Confirmation needs repeated FAILED; healing needs repeated PASSED.
A reporter that sends one FAILED when a condition appears and one clear when it goes away
never sends the second event. confirmation_threshold: -3 then leaves the fault in
PREFAILED, and the default fault list returns CONFIRMED only, so the fault is never seen.
Healing has the same shape: it needs healing_threshold - confirmation_threshold
consecutive PASSED events counted from where the fault confirmed, and only one is sent.
For such a reporter, leave the confirmation threshold alone and make healing reachable:
fault_manager:
ros__parameters:
confirmation_threshold: -1 # the default; the single FAILED confirms
healing_enabled: true
healing_threshold: 0 # heal on the single PASSED
auto_confirm_after_sec promotes a fault that stayed PREFAILED for that long and looks
like it would allow a deeper threshold. It does not: HEALED is latched, leaving that latch
costs healing_threshold - confirmation_threshold FAILED events, and a one-event reporter
sends one. The second occurrence of a fault code would then never confirm again. Filter
noisy samples in the reporter instead, where the samples are.
Near-Miss Retention
A near miss is a FAILED report that moved the debounce counter without the fault ending up CONFIRMED - the fault nearly happened. PASSED reports move the counter in the healing direction (the fault receding) and are not near misses.
The fault manager appends one entry per near miss to a per-fault-code series, holding the timestamp, the counter value after the report, the confirmation threshold, the severity, the reporting source and the fault status the report left behind.
The status matters when reading the series. The HEALED latch holds the status all the way from the
healing threshold down to the confirmation threshold, so reports on the way back into a fault that
does confirm are also near misses by the definition above. Entries recording PREFAILED are
approaches from a resting state; entries recording HEALED are a counter walking back down under
the latch. The recorded confirmation threshold belongs to the reporting source, while the counter
is shared by all sources of that fault code, so with per-entity thresholds it is not on its own the
distance to confirmation. The series is retained when the fault is cleared, because acknowledging one
fault cycle must not erase how often that code approached confirmation across cycles.
fault_manager:
ros__parameters:
near_miss:
max_per_fault: 200 # Entries kept per fault code (0 = unlimited)
Parameter |
Default |
Description |
|---|---|---|
|
|
Near-miss entries retained per fault code. When the bound is reached the oldest
entries are evicted, the same direction as |
Note
The series lives in the near_misses table of the fault database and is read through the
storage API (FaultStorage::get_near_misses). A database written by an earlier build gains
the table on first open. There is no service or REST surface for it yet.
Per-Entity Thresholds
Different subsystems often have different failure characteristics. For example, a lidar
sensor is binary (instant confirmation), while a motor controller may produce transient
errors that need debouncing. Per-entity thresholds let you configure different debounce
policies per reporting entity using longest-prefix matching on source_id.
fault_manager:
ros__parameters:
# Global defaults (used when no entity-specific match)
confirmation_threshold: -1
healing_enabled: false
healing_threshold: 3
# Path to YAML file with per-entity overrides
entity_thresholds:
config_file: "/etc/ros2_medkit/entity_thresholds.yaml"
The entity thresholds config file uses a simple map of entity path prefixes to threshold overrides:
# entity_thresholds.yaml
/sensors/lidar:
confirmation_threshold: -1 # instant - lidar is binary
healing_threshold: 1
/powertrain/motor_left:
confirmation_threshold: -5 # motor has transients, need 5 events
healing_threshold: 10
/safety:
confirmation_threshold: -1 # instant, never auto-heal
healing_enabled: false
Parameter |
Default |
Description |
|---|---|---|
|
|
Path to YAML file with per-entity threshold overrides. Empty = disabled. |
How matching works:
The
source_idis the identifier passed inReportFaultservice requests, typically the fully qualified name of the reporting ROS 2 node (e.g.,/sensors/lidar/front_node). You can inspect actualsource_idvalues in thereporting_sourcesfield of existing faults viaGET /api/v1/faults.The
source_idfromReportFaultrequests is matched against configured prefixes.The longest matching prefix wins. For example,
/sensors/lidar/frontmatches/sensors/lidarover/sensors.Unspecified fields in an entity override inherit from the global defaults.
If no prefix matches, the global defaults apply.
The config file is loaded once at node startup. Changes require a node restart.
Note
When multiple entities report the same fault_code, each event applies the
thresholds resolved from that event’s source_id. This means the debounce
behavior follows the reporting entity, not the fault.
auto_confirm_after_sec is global-only and cannot be overridden per-entity.
Critical faults skip debounce and confirm on their first occurrence; that is
built in, not a parameter, so it can be neither disabled nor set per entity.
Snapshot Configuration
Snapshots capture diagnostic data when faults occur.
Basic Snapshot Settings
fault_manager:
ros__parameters:
snapshots:
enabled: true # Enable snapshot capture
background_capture: false # Capture in background thread
timeout_sec: 1.0 # Timeout for topic sampling
max_message_size: 65536 # Max message size in bytes (64KB)
default_topics: [] # Topics to capture for all faults
config_file: "" # Path to YAML config file
recapture_cooldown_sec: 60.0 # Min seconds between snapshot captures per fault
max_per_fault: 10 # Max snapshots stored per fault code (0 = unlimited)
capture_pool_size: 2 # Max concurrent capture threads (>= 1)
capture_queue_depth: 16 # Max pending captures before policy applies (>= 1)
capture_queue_full_policy: reject_newest # reject_newest | drop_oldest
Parameter |
Default |
Description |
|---|---|---|
|
|
Master switch to enable/disable snapshot capture. |
|
|
Capture snapshots in background thread (non-blocking). |
|
|
Timeout for sampling each topic. |
|
|
Maximum message size to capture (bytes). Larger messages are truncated. |
|
|
List of topics to capture for all faults. Empty entries are ignored. |
|
|
Zero-config fallback: when no explicit config matches a fault code,
capture the reporting source node’s own published topics. Set to
|
|
|
Path to YAML file with fault-specific snapshot configurations. |
|
|
Minimum seconds between snapshot captures for the same fault code. Prevents snapshot storms when a fault is reported repeatedly. Set to 0 to disable. |
|
|
Maximum number of snapshot rows stored per fault code. One confirmation writes one row per configured topic, and those rows are evicted together: past the limit the OLDEST capture set is dropped whole. A capture larger than the cap is kept anyway rather than torn, since half a freeze frame is indistinguishable from topics that were silent. Set to 0 for unlimited. |
|
|
Keep a fault’s value snapshots when it is acknowledged. |
|
|
Max concurrent capture threads under a fault storm (>= 1). The capture pool is
shared and created when snapshots or rosbag is enabled, so these parameters
bound both. |
|
|
Max pending captures before the full-queue policy applies (>= 1). |
|
|
Policy when the queue is full: |
Rosbag Recording
Capture continuous rosbag recordings around fault events.
fault_manager:
ros__parameters:
snapshots:
rosbag:
enabled: false # Enable rosbag recording
duration_sec: 5.0 # Pre-fault buffer duration
duration_after_sec: 1.0 # Post-fault recording duration
topics: "entity" # Topic selection: "entity" (default), "config", "all", "explicit"
include_topics: [] # Additional topics to include
exclude_topics: [] # Topics to exclude
exclude_sensor_topics: true # Auto-exclude image/points/depth/compressed in broad modes
lazy_start: false # Start recording on first fault
format: "mcap" # Storage format (default: mcap)
qos_match: true # Match each topic's publisher QoS
storage_path: "" # Custom storage path
max_buffer_mb: 256 # Ring-buffer RAM cap
max_bag_size_mb: 50 # Max size per bag file
max_total_storage_mb: 500 # Max total storage
max_bags_per_fault: 1 # Recordings kept per fault code
auto_cleanup: true # Auto-delete old bags
Parameter |
Default |
Description |
|---|---|---|
|
|
Enable rosbag recording for snapshots. |
|
|
Duration of pre-fault circular buffer. |
|
|
How long to record after fault. A fault confirming while this window is
still running attaches to the in-flight recording and shares its bag,
with one metadata entry per fault. At most 32 faults attach on top of
the first; any beyond that are recorded in the bag’s data but get no
per-fault entry (logged as a warning). A fault confirming just after
the window closes gets a post-fault-only bag of its own - see
Recording Lifecycle and the Window Boundary below. Setting this to |
|
|
Topic selection mode: |
|
|
In broad modes ( |
|
|
Topics to record on top of whatever the selection mode picked. This is
how a topic dropped by |
|
|
Topics to drop from whatever the selection mode picked. |
|
|
Directory the bag files are written to. Empty falls back to the system temporary directory, which on most systems is cleared on reboot - set this if the bags have to survive one. |
|
|
Subscribe with each topic’s publisher-offered QoS for faithful capture instead of forcing best-effort. |
|
|
Bag storage format: |
|
|
Ring-buffer RAM cap; oldest buffered messages drop past it. |
|
|
Start recording only when first fault occurs. |
|
|
Maximum size per rosbag file (MB). |
|
|
Maximum total storage for all rosbags (MB). A recording shared by a burst of faults counts once towards the total, and eviction removes a whole burst’s bag at a time (oldest first). |
|
|
How many recordings one fault code keeps. Past the cap the oldest is
unlinked, so the default reproduces the historical behaviour exactly: a
new recording replaces the previous one. |
|
|
Delete a fault’s bags when the fault is cleared. A recording shared by a
burst survives until the last fault referencing it clears. Has no effect
once |
Note
max_bags_per_fault is a fairness knob, not a depth knob.
max_total_storage_mb is the real disk bound and eviction across it is
global and oldest-first, so a fault that flaps often enough will consume the
budget and push out every other fault’s black box. Raise the per-fault cap
when you need the history of a specific intermittent fault; raise the total
budget with it if other faults still need theirs.
The cap keeps the newest recordings and evicts the oldest, the same direction
as snapshots.max_per_fault. Refusing a NEW recording instead would mean a
technician standing next to a machine faulting right now downloads a bag from
three days ago.
snapshots.recapture_cooldown_sec (default 60 s) gates the capture job as a
whole, rosbags included, so it puts a floor under how fast a history can grow:
a fault that returns sooner than the cooldown keeps ONE recording however high
this cap is. That is the fast-flapping fault the cap exists for, so lower the
cooldown when you raise the cap. The fault manager logs a warning at startup
when the two are configured against each other.
Recording Lifecycle and the Window Boundary
Only one recording is open at a time: there is one ring buffer, one bag writer and
one post-fault window per fault manager. That single-writer design decides what a
fault gets depending on when it confirms relative to the previous fault’s
duration_after_sec window.
While no recording is open, with history buffered. Messages accumulate in the
ring buffer. A confirmation flushes the whole buffer into a new bag and, if
duration_after_sec > 0, keeps that bag open for the post-fault window. The
result is a full bag: pre-fault history plus post-fault response.
Inside an open window. The confirming fault attaches to the in-flight
recording and shares its bag, getting its own metadata entry (see
duration_after_sec above). Nothing is buffered while a window is open -
incoming messages are written straight to the open bag.
While no recording is open, with the buffer empty. A fault confirming here has
no pre-fault history to write. It gets a post-fault-only bag: a recording of its
own containing just its duration_after_sec window. It is a normal recording in
every other respect, so a further fault of the burst attaches to it as usual.
The usual way to reach it is right after a window closes. The flush that opened the
previous recording moved the whole buffer out, and everything published during its
window went into that bag rather than back into the buffer, so the fault confirming
in that moment - typically the second fault of a burst - has nothing to write. A
fault confirming before any captured topic has published, just after startup or with
lazy_start, finds the same empty buffer and gets the same bag. Either way the
fault manager logs the cause:
No pre-fault data buffered for fault 'BRAKE_PRESSURE_LOW' - recording post-fault window only
How often this case actually arises depends on topics. The broad modes -
all, auto and entity, which is the default - subscribe to everything on
the graph, and that includes /fault_manager/events. Reporting a fault publishes
an event there, so the buffer is refilled by the act of reporting and the next fault
of the burst usually finds history rather than an empty buffer. The empty-buffer case
belongs mainly to a narrowed capture: explicit, a topic list, config, or a
broad mode with /fault_manager/events in exclude_topics.
With duration_after_sec: 0 there is no window to record into and no history to
write, so such a fault gets no bag at all (also logged). If the bag cannot be
written (unwritable storage_path, missing storage backend), no recording is
opened and no metadata entry is stored.
A capture never leaves a row pointing at a bag that is not there, and never leaves a bag that no row points at. If the metadata cannot be stored, the recording is discarded with it: nothing could reach that bag anyway, since retrieval is keyed by fault code and the quota is computed from rows, so it would sit on disk unreachable and uncounted. If the quota sweep that follows fails instead, the bag and its rows both stay - the sweep is about the whole store, not about this recording, and it runs again on the next capture.
A post-fault-only bag on a quiet or heavily filtered system can contain zero
messages. It still finalises normally on both mcap and sqlite3, is listed
by the bulk-data endpoints and can be downloaded; only its payload is empty.
What ``duration_sec`` on a stored bag means. The value returned by
~/get_rosbag and the gateway’s bulk-data listing is the span the recording was
open, not the configured windows. It is a recording span, not a content span:
the zero-message bag above still reports its window rather than 0.0, because
“the black box covered these seconds and nothing was published” is the useful
statement and a bare zero would be indistinguishable from a broken artifact. A
post-fault-only bag therefore reports roughly duration_after_sec, and a bag
flushed from a buffer that never filled reports the history it holds plus its
window. The span can also exceed duration_sec + duration_after_sec, because pruning is
driven by message arrival rather than by a timer: when every captured topic goes
quiet nothing prunes, and the buffer keeps its last window until the next
confirmation flushes it. That is deliberate - a black box should keep the final
messages before everything stopped. Note the condition: a single captured topic that
goes quiet while others keep publishing is pruned like anything else, because each
arrival prunes the whole buffer by age.
See also
Configuring Snapshot Capture for detailed snapshot configuration examples.
Audit Log
An append-only, hash-chained record of fault state transitions, for deployments that have to show afterwards what the fault state was and when it changed. Off by default: with it off there is no table, no file and no write cost.
fault_manager:
ros__parameters:
audit_log:
enabled: false
transitions: "all"
retention_max_records: 0
fail_closed: false
database_path: ""
Parameter |
Default |
Description |
|---|---|---|
|
|
Turn the audit log on. |
|
|
Which transitions are recorded: |
|
|
Seal and prune the oldest segment once the log passes this many records.
|
|
|
Re-raise a failed audit append as a hard error instead of carrying on. Note what this does and does not do: it does NOT roll back the fault state change that triggered the transition, because that change has already committed to the separate fault-store database and the two databases cannot be made atomic. It signals a broken audit chain so an operator has to act. Off by default so the audit can never block fault processing. |
|
|
Where the audit database lives. Empty puts it beside the fault database, or in memory when the fault store is itself in memory or not SQLite. |
Correlation Configuration
Fault correlation identifies root causes and filters symptom faults.
fault_manager:
ros__parameters:
correlation:
config_file: "/path/to/correlation_rules.yaml"
cleanup_interval_sec: 5.0 # Interval for cleanup tasks
Parameter |
Default |
Description |
|---|---|---|
|
|
Path to YAML file defining correlation rules. |
|
|
Interval for running correlation cleanup tasks. |
See also
Configuring Fault Correlation for correlation rule syntax and examples.
Complete Example
fault_manager:
ros__parameters:
# Storage
storage_type: "sqlite"
database_path: "/var/lib/ros2_medkit/faults.db"
# Debounce for a reporter that repeats its events while a condition holds:
# three FAILED events confirm, and four PASSED events heal from there.
# For a reporter that sends one event per transition, use -1 with
# healing_threshold 0 instead - see the note under Debounce Settings.
confirmation_threshold: -3
healing_enabled: true
healing_threshold: 3
# Per-entity debounce overrides
entity_thresholds:
config_file: "/etc/ros2_medkit/entity_thresholds.yaml"
# Snapshots
snapshots:
enabled: true
background_capture: true
timeout_sec: 2.0
max_message_size: 131072
recapture_cooldown_sec: 60.0
max_per_fault: 10
default_topics:
- /diagnostics
- /rosout
config_file: "/etc/ros2_medkit/snapshot_config.yaml"
rosbag:
enabled: true
duration_sec: 10.0
duration_after_sec: 2.0
topics: "config"
max_bag_size_mb: 100
max_total_storage_mb: 1000
auto_cleanup: true
# Correlation
correlation:
config_file: "/etc/ros2_medkit/correlation_rules.yaml"
cleanup_interval_sec: 10.0
See Also
Configuring Snapshot Capture - Diagnostic snapshot configuration
Configuring Fault Correlation - Fault correlation rules
Message Definitions - Message definitions (Fault.msg, FaultEvent.msg)
ros2_medkit_fault_manager - FaultManager architecture