FixTheRobot Explore Robotics / Systems Engineering / Engineering Guides / Module 11

Engineering Guides · Module 11

Configuring robot_localization

A minimal, correct starting configuration and the reasoning for each choice. Parameter names are checked against the package’s own example file.

  • Advanced
  • Module 11: Sensor Fusion & State Estimation
  • Written for ROS 2 Jazzy where ROS 2 is used
  • Sources checked 2026-11-04

Assumptions: ROS 2 Jazzy with the robot_localization package; planar robot (two_d_mode); wheel odometry and IMU as inputs. The YAML block is parsed by the site’s tests and every parameter name is checked against the names confirmed from the package’s ekf.yaml (ros2 branch, fetched 2026-11-04). The configuration has not been run on a robot by the site’s tests.

1. Engineering problem

Wheel odometry is smooth but drifts, the IMU gives a good yaw rate but no position. An EKF combines them into one continuous pose. Whether it works depends on which variables each sensor is allowed to influence and on the covariances those sensors report.

2. When is this needed?

  • Producing a continuous odom → base_link transform for a wheeled robot.
  • Adding GNSS or visual/LiDAR odometry later (a second filter or more inputs).
  • Replacing an ad-hoc complementary filter with a tunable estimator.

3. Selection and design decisions

Which variables should each sensor contribute?

Each *_config parameter is 15 booleans in this order (as documented for the package): x, y, z, roll, pitch, yaw, vx, vy, vz, vroll, vpitch, vyaw, ax, ay, az.

State variables to fuse per sensor
SensorGood to fuseUsually avoidWhy
Wheel odometryvx, vyawAbsolute x, y, yawPose integrates slip and drifts; velocity is a direct measurement
IMUvyaw (yaw rate)Absolute yaw unless it comes from a calibrated magnetometer-aided unit; linear acceleration unless you need itGyro rate is reliable; absolute heading and double-integrated acceleration are not
GNSS (via a second filter)x, yHeading, velocityPosition bound, outputs low-rate and noisy
Visual/LiDAR odometryvx, vy, vyaw or pose differencesAbsolute pose if it resetsCheck each system’s covariance and frame

Per-sensor options (names confirmed from ekf.yaml)

  • *_differential: fuse the change between successive measurements instead of the absolute value (for sources with a drifting or arbitrary reference).
  • *_relative: treat the first measurement as the origin.
  • *_queue_size: how many messages to buffer; too small drops data from fast sensors.
  • *_rejection_threshold variants (_pose_, _twist_, _linear_acceleration_): Mahalanobis-distance gates that reject outlying measurements.
  • sensor_timeout: after this long without data the filter runs prediction only.

4. Advantages and disadvantages

  • Fewer fused variables: more robust to a bad sensor, less information. Start minimal, add one variable at a time and test.
  • EKF vs UKF (the package provides both): the EKF is simpler and faster; the UKF handles strong nonlinearity better at higher cost. For wheeled robots at ordinary speeds the EKF is the usual start.
  • Higher filter frequency: smoother output, more CPU; set it at or above the fastest important sensor.

5. Requirements and constraints

  • The measurement covariance comes from the message (odometry pose/twist covariance, IMU covariance). A driver that publishes zeros tells the filter the data is perfect: fix the driver or override the covariances.
  • Frames: world_frame is odom for the local filter; base_link_frame must match your URDF; sensor frames must exist in TF.
  • All timestamps must be consistent with the ROS clock; see the timing guides.

6. Architecture

/wheel/odometry ──┐
                  ├──▶ ekf_filter_node ──▶ /odometry/filtered
/imu/data ────────┘          │
                             └──▶ TF: odom → base_link

7. Implementation

Starting configuration (parsed and name-checked by the site’s tests):

ekf_filter_node:
  ros__parameters:
    frequency: 30.0
    sensor_timeout: 0.1
    two_d_mode: true
    publish_tf: true
    map_frame: map
    odom_frame: odom
    base_link_frame: base_link
    world_frame: odom

    # wheel odometry: fuse forward speed (vx) and yaw rate (vyaw) only
    odom0: /wheel/odometry
    odom0_config: [false, false, false,
                   false, false, false,
                   true,  false, false,
                   false, false, true,
                   false, false, false]
    odom0_queue_size: 10
    odom0_differential: false
    odom0_relative: false

    # IMU: fuse yaw rate only
    imu0: /imu/data
    imu0_config: [false, false, false,
                  false, false, false,
                  false, false, false,
                  false, false, true,
                  false, false, false]
    imu0_queue_size: 10
    imu0_differential: false
    imu0_relative: false
  • two_d_mode: true constrains the filter to the plane: z, roll, pitch and their rates are ignored.
  • The two *_config arrays select vx and vyaw from the wheels and vyaw from the IMU.
  • No process_noise_covariance is set: the package supplies a default. Override the 15×15 diagonal once you have data that shows lag (too small) or noise (too large); use the EKF Tuning Playground to see the effect.

Run and inspect

Not executed by the site’s automated tests (they need a ROS 2 installation).

ros2 run robot_localization ekf_node --ros-args --params-file ekf.yaml -r __node:=ekf_filter_node
ros2 topic echo /odometry/filtered --once      # pose, twist and covariance
ros2 topic hz /odometry/filtered               # should match "frequency"
ros2 run tf2_ros tf2_echo odom base_link       # the transform the filter publishes

The node name must match the top-level key in the YAML (ekf_filter_node) for the parameters to apply.

8. Real examples

  1. Robot drifts in yaw while stationary: the IMU bias; check imu0_config uses the rate, not absolute yaw, and that the IMU covariance is realistic.
  2. Pose jumps when the wheels slip: add an odom0_twist_rejection_threshold gate and consider fusing only vx.
  3. Output lags real motion: process noise too small for the dynamics, or too small a queue dropping measurements.

9. Failure scenarios

  • Fusing absolute yaw from an uncalibrated magnetometer.
  • Two nodes publishing odom → base_link (the EKF and the wheel driver): turn one off (publish_tf).
  • Zero covariance in sensor messages: the filter ignores everything else.
  • Using *_differential on sources that are already relative: values cancel.
  • Forgetting that the config array order is x, y, z, roll, pitch, yaw, then velocities, then accelerations.

10. Troubleshooting

  1. ros2 topic hz on each input and on /odometry/filtered.
  2. Compare covariance in the input messages against reality (zeros are wrong).
  3. Fuse one sensor at a time: wheels only, then add the IMU. Check each output for sanity against a hand-driven path.
  4. Set print_diagnostics: true and read /diagnostics for dropped or rejected measurements.
  5. Adjust the rejection thresholds and process noise based on the recorded data, not guesses.

11. Validation

  • Drive a closed square or circle: the pose ends within your tolerance of the start (and compare with wheel-only).
  • Spin in place: yaw returns to the start within the IMU’s expected drift.
  • Stationary for 10 minutes: no pose drift beyond the specified limit.
  • No two publishers on odom → base_link (ros2 run tf2_tools view_frames).
  • The YAML parses and every parameter name is one confirmed from ekf.yaml (checked by the site’s tests).

12. Engineering checklist

13. Further references

  • robot_localization example configuration (ros2 branch), parameter names confirmed on 2026-11-04: params/ekf.yaml.
  • The 15-element configuration order and the behaviour of each option are documented in the robot_localization package documentation; that documentation could not be fetched in the build environment, so check it against your installed version.

Related on FixTheRobot