Platform details
- Robot / chassis: custom differential drive, vesc motor controller, skateboard brushless motors 6374 with integrated HALL sensor (only 42 cpr)
- IMU: BNO085
- GPS receiver: RTK with EUREF correction data and nmea messages RMC, GGA with NavSatFix structure
- Drive type: differential
What you need
results of the sensorfusion are pretty wild
Do you find any mistakes in my cfg? Or have you any hints to improve my results?
I tried in a second test the same cfg with gps data, but the result was not better.
Config (based on your cfg wheels_indoor.yaml)
fusioncore:
ros__parameters:
base_frame: base_link
odom_frame: odom
publish_rate: 100.0
publish.force_2d: true # ground robot
motion_model: "DifferentialDrive"
# ── IMU ───────────────────────────────────────────────────────────────────
# Generic 6-axis MEMS defaults. Adjust from your IMU datasheet if available.
# has_magnetometer: set true if you have a 9-axis IMU (BNO085, VectorNav).
# true = yaw from magnetometer: available immediately, no drift (but
# sensitive to motor/wiring interference on robot platforms)
# false = yaw from gyro integration: drifts slowly, SLAM loop closure
# corrects it. Safer default for most robots.
imu.has_magnetometer: true
imu.gyro_noise: 0.005 # rad/s : conservative MEMS default
imu.accel_noise: 0.1 # m/s²
imu.remove_gravitational_acceleration: false
# Set your IMU TF frame if your driver doesn't fill header.frame_id.
# Leave empty to use whatever the driver publishes in the message header.
imu.frame_id: "bno_link"
imu.topic: "/imu"
# ── Wheel encoders ────────────────────────────────────────────────────────
# Generic values for mid-quality differential drive.
# Tighten vel_noise if you have high-resolution encoders (≥500 CPR).
# Loosen if your surface is slippery, wheels are soft, or odom is noisy.
encoder.vel_noise: 0.05 # m/s
encoder.yaw_noise: 0.02 # rad/s
# ── Optional second velocity source ──────────────────────────────────────
# Uncomment to add LiDAR ICP or visual odometry as a second velocity source.
# FusionCore fuses both independently: wheel odom primary, ICP cross-check.
# encoder2.topic: "/kiss/odometry" # KISS-ICP
# encoder2.topic: "/icp_odom" # rtabmap icp_odometry
# ── GPS: disabled ─────────────────────────────────────────────────────────
gnss.base_noise_xy: 2.5
gnss.base_noise_z: 5.0
gnss.max_hdop: 4.0
gnss.min_satellites: 4
gnss.min_fix_type: 1
gnss.fix2_topic: ""
gnss.heading_topic: ""
gnss.azimuth_topic: ""
# ── Outlier rejection ─────────────────────────────────────────────────────
outlier_rejection: true
outlier_threshold_gnss: 16.27
outlier_threshold_imu: 15.09
outlier_threshold_enc: 11.34
outlier_threshold_hdg: 10.83
# ── Adaptive noise ────────────────────────────────────────────────────────
adaptive.imu: true
adaptive.encoder: true
adaptive.gnss: false # no GPS
adaptive.window: 50
adaptive.alpha: 0.01
# ── ZUPT ──────────────────────────────────────────────────────────────────
# Critical indoors: suppresses IMU drift during standstill.
# Without GPS corrections, every second of stationary drift matters.
zupt.enabled: true
zupt.velocity_threshold: 0.05 # m/s
zupt.angular_threshold: 0.05 # rad/s
zupt.noise_sigma: 0.01
# Flat-terrain Z position constraint: 0.0 = disabled (default).
# Indoor robots on flat floors can set this to ~0.3 if GPS altitude noise
# is causing Z drift. Rarely needed indoors since GPS is not active.
ground_constraint.z_position_sigma: 0.0
# ── Startup bias init ─────────────────────────────────────────────────────
# Recommended indoors: no GPS to correct early drift.
# Robot must be stationary for 2 seconds after launch.
init.stationary_window: 2.0
# ── UKF process noise ─────────────────────────────────────────────────────
ukf.q_position: 0.01
ukf.q_orientation: 1.0e-9 # quaternion regularization only: do not increase
ukf.q_velocity: 0.1
ukf.q_angular_vel: 0.1
ukf.q_acceleration: 1.0
ukf.q_gyro_bias: 1.0e-5
ukf.q_accel_bias: 1.0e-5
ukf.q_encoder_wz_bias: 1.0e-7
outlier_threshold_vslam: 22.46
adaptive.ground_constraint: true
input.gnss_crs: "EPSG:4326"
output.crs: "EPSG:4978"
output.convert_to_enu_at_reference: true
reference.use_first_fix: false # no GPS: start at origin
reference.x: 0.0
reference.y: 0.0
reference.z: 0.0
Field tested?
Yes on real hardware
Drive a line of 6 meters, turn around and go back
Odometry drifts in y axis one meter away, x axis good, imu has fitting orientation
Fusioncore results:
Position: x goes up to 30 meters and afterwards goes to -35 meter, y increases straight forward to 169 meter
Orientation: jumps and does not fit to imu nor to odom orientation
Additional context
I remapped the topics to my output topics and set the imu frame according to my settings
Fusioncore Log:
[INFO] [1784704071.013150281] [fusioncore]: FusionCore node created
[INFO] [1784704072.910055006] [fusioncore]: Configuring FusionCore...
[INFO] [1784704072.931470094] [fusioncore]: IMU lever arm: will auto-resolve from TF on first IMU message
[INFO] [1784704072.931536471] [fusioncore]: IMU gravity removal: disabled
[INFO] [1784704072.931560383] [fusioncore]: IMU frame override: bno_link
[INFO] [1784704072.931603060] [fusioncore]: GNSS min_fix_type: 1 (1=GPS, 2=DGPS, 3=RTK_FLOAT, 4=RTK_FIXED)
[INFO] [1784704072.958508929] [fusioncore]: PROJ: transform ready (EPSG:4326 → EPSG:4978)
[INFO] [1784704072.958609298] [fusioncore]: PROJ: fixed reference origin (0.000, 0.000, 0.000) → lat=90.000000 lon=0.000000 alt=-6356752.31
[INFO] [1784704072.958765678] [fusioncore]: Motion model: DifferentialDrive
[INFO] [1784704072.980020166] [fusioncore]: Dual antenna heading disabled.
[INFO] [1784704072.980379875] [fusioncore]: FusionCore configured. base_frame=base_link odom_frame=odom rate=100Hz
[INFO] [1784704072.980523009] [fusioncore]: Autostart enabled: activating in 200ms.
[INFO] [1784704072.987806163] [fusioncore]: Activating FusionCore...
[INFO] [1784704072.987859553] [fusioncore]: --- TF Validation ---
[INFO] [1784704072.998129787] [fusioncore]: [OK] bno_link -> base_link
[INFO] [1784704072.998189087] [fusioncore]: ---------------------
[INFO] [1784704073.002110341] [fusioncore]: IMU topic: /imu
[INFO] [1784704073.028663174] [fusioncore]: FusionCore active. Listening for sensors.
[INFO] [1784704073.031797897] [fusioncore]: Collecting 2.0s bias window before init...
[WARN] [1784704073.180606527] [rcl_lifecycle]: No transition matching 3 found for current state active
[INFO] [1784704074.124910024] [fusioncore]: Bias window done (gyro only, no orientation): gyro=[0.0000,0.0000,0.0000]
[INFO] [1784704074.125041639] [fusioncore]: Filter initialized at t=1784704075.332
[INFO] [1784704074.181788132] [fusioncore]: IMU lever arm auto-resolved from TF base_link -> bno_link: x=0.295 y=0.130 z=0.125 m
[INFO] [1784704146.026336218] [rclcpp]: signal_handler(SIGINT/SIGTERM)
Platform details
What you need
results of the sensorfusion are pretty wild
Do you find any mistakes in my cfg? Or have you any hints to improve my results?
I tried in a second test the same cfg with gps data, but the result was not better.
Config (based on your cfg wheels_indoor.yaml)
fusioncore:
ros__parameters:
base_frame: base_link
odom_frame: odom
publish_rate: 100.0
publish.force_2d: true # ground robot
Field tested?
Yes on real hardware
Drive a line of 6 meters, turn around and go back
Odometry drifts in y axis one meter away, x axis good, imu has fitting orientation
Fusioncore results:
Position: x goes up to 30 meters and afterwards goes to -35 meter, y increases straight forward to 169 meter
Orientation: jumps and does not fit to imu nor to odom orientation
Additional context
I remapped the topics to my output topics and set the imu frame according to my settings
Fusioncore Log:
[INFO] [1784704071.013150281] [fusioncore]: FusionCore node created
[INFO] [1784704072.910055006] [fusioncore]: Configuring FusionCore...
[INFO] [1784704072.931470094] [fusioncore]: IMU lever arm: will auto-resolve from TF on first IMU message
[INFO] [1784704072.931536471] [fusioncore]: IMU gravity removal: disabled
[INFO] [1784704072.931560383] [fusioncore]: IMU frame override: bno_link
[INFO] [1784704072.931603060] [fusioncore]: GNSS min_fix_type: 1 (1=GPS, 2=DGPS, 3=RTK_FLOAT, 4=RTK_FIXED)
[INFO] [1784704072.958508929] [fusioncore]: PROJ: transform ready (EPSG:4326 → EPSG:4978)
[INFO] [1784704072.958609298] [fusioncore]: PROJ: fixed reference origin (0.000, 0.000, 0.000) → lat=90.000000 lon=0.000000 alt=-6356752.31
[INFO] [1784704072.958765678] [fusioncore]: Motion model: DifferentialDrive
[INFO] [1784704072.980020166] [fusioncore]: Dual antenna heading disabled.
[INFO] [1784704072.980379875] [fusioncore]: FusionCore configured. base_frame=base_link odom_frame=odom rate=100Hz
[INFO] [1784704072.980523009] [fusioncore]: Autostart enabled: activating in 200ms.
[INFO] [1784704072.987806163] [fusioncore]: Activating FusionCore...
[INFO] [1784704072.987859553] [fusioncore]: --- TF Validation ---
[INFO] [1784704072.998129787] [fusioncore]: [OK] bno_link -> base_link
[INFO] [1784704072.998189087] [fusioncore]: ---------------------
[INFO] [1784704073.002110341] [fusioncore]: IMU topic: /imu
[INFO] [1784704073.028663174] [fusioncore]: FusionCore active. Listening for sensors.
[INFO] [1784704073.031797897] [fusioncore]: Collecting 2.0s bias window before init...
[WARN] [1784704073.180606527] [rcl_lifecycle]: No transition matching 3 found for current state active
[INFO] [1784704074.124910024] [fusioncore]: Bias window done (gyro only, no orientation): gyro=[0.0000,0.0000,0.0000]
[INFO] [1784704074.125041639] [fusioncore]: Filter initialized at t=1784704075.332
[INFO] [1784704074.181788132] [fusioncore]: IMU lever arm auto-resolved from TF base_link -> bno_link: x=0.295 y=0.130 z=0.125 m
[INFO] [1784704146.026336218] [rclcpp]: signal_handler(SIGINT/SIGTERM)