Adding a Fault Type

A fault type is one new behavior inside an existing injector. Add a fault type when the message stream is already supported, but you want to mutate it in a new way.

For example, odometry covariance inflation belongs in the existing odom injector because it still operates on nav_msgs/msg/Odometry.

1. Pick the Config Key

Choose a key that describes the parameter, not the implementation detail.

Good examples:

pose_covariance_scale
pose_covariance_floor
yaw_bias_deg
range_noise_stddev

Use units in the key when the unit is not obvious:

yaw_bias_deg
delay_ms

2. Add the Key to the Schema

Update fault_config_schema so the key is accepted for the correct injector type.

Example for odom:

const std::unordered_set<std::string> kOdomKeys = {
    "drop_probability",
    "delay_ms",
    "pose_covariance_scale",
    "pose_covariance_floor",
};

This keeps YAML validation, runtime config updates, and documentation aligned.

3. Add Value Validation

Update scenario_validator when the key has constraints.

Typical rules:

  • standard deviations must be non-negative

  • probabilities must be between 0.0 and 1.0

  • covariance scale/floor values must be non-negative

  • enum-like strings should be checked against known values

For numeric non-negative keys, use the existing validator helper pattern:

validate_non_negative_number_key(fault, "pose_covariance_scale", result);

4. Implement the Mutation

Add a small method to the typed injector and call it from the message callback.

For odom covariance:

void OdomFaultInjector::apply_covariance_scale(nav_msgs::msg::Odometry& msg) {
  const double pose_scale = active_max_double("pose_covariance_scale", 1.0);
  const double pose_floor = active_max_double("pose_covariance_floor", 0.0);

  if (pose_scale != 1.0 || pose_floor > 0.0) {
    for (auto& value : msg.pose.covariance) {
      value = std::max(value * pose_scale, pose_floor);
    }
  }
}

Use helpers from FaultInjectorBase where possible:

  • active_max_double(...) when the strongest active value should win

  • active_sum_double(...) when active faults should stack additively

  • active_max_int(...) for integer configuration

  • should_drop() for common message dropping

  • active_delay() for common message delay

5. Decide How Faults Combine

Be explicit about how multiple active faults interact.

Common patterns:

bias values stack additively
noise values can use max or sum depending on desired behavior
scale/floor values usually use max
drop_probability usually uses max
delay_ms usually uses max

Document the choice in a short code comment when it is not obvious.

6. Add YAML Examples

Add a focused example in config/ or extend multi_injector_faults.yaml.

Example:

- id: odom_covariance_inflation
  injector_id: odom
  active_on_startup: false
  config:
    pose_covariance_scale: 10.0
    pose_covariance_floor: 0.05
    twist_covariance_scale: 10.0
    twist_covariance_floor: 0.05

7. Add Tests

Test the smallest behavior that proves the new fault works.

For a typed injector, prefer publishing a raw message and checking the output topic:

publish /odom_raw -> injector -> receive /odom

For schema and validation:

  • schema accepts the new key

  • validator rejects invalid values

  • service config updates accept the new key when appropriate

8. Update Documentation

Update:

  • README.md fault key tables

  • example YAML files

  • Doxygen comments if the public header changed

  • this guide if the process changed

Then regenerate docs:

cd /home/reece/fault_injection_ws
source /opt/ros/jazzy/setup.bash
colcon build --symlink-install --packages-select ros2_fault_injection --cmake-target generate_doxygen_api

Checklist

  • Config key added to fault_config_schema

  • Invalid values rejected in scenario_validator

  • Typed injector applies the fault

  • Callback calls the new mutation method

  • Example YAML added

  • README updated

  • Tests added or updated

  • Doxygen docs regenerate cleanly