Library

fusioncore v0.3.6

Versionv0.3.6
Stars★ 306
Released2026-08-12

ROS 2 sensor fusion SDK: UKF, 3D native, proper GNSS, zero manual tuning. Apache 2.0.

Release notes

Four GNSS defects, all of them found by running FusionCore on real hardware rather than by the test suite. Three were reported or triggered by issue #73.

The common thread: a quality gate expressed as an absolute distance in metres, calibrated against clean or simulated GPS, silently discarding good measurements on a real receiver. A rejected fix costs accuracy but surfaces nowhere except a throttled log line, so the symptom looks like "the filter is bad" rather than "the gate is wrong".

### The DOP gate compared metres against thresholds named as DOP

`sensor_msgs/NavSatFix` carries no DOP fields, so the node derives fix quality from `position_covariance` as `sqrt(variance)`, which is **metres**. That was compared against `gnss.max_hdop` (4.0) and `gnss.max_vdop` (6.0), parameters every ROS user reads as the dimensionless geometry factor. What the defaults actually meant was "reject any fix worse than 4 m horizontal or 6 m vertical".

Measured on 500 fixes from a u-blox NEO-M9N outdoor run: 3.6 to 6.0 m horizontal, 14.4 to 24.0 m vertical. **All 500 were rejected at shipped defaults**, and the filter dead-reckoned the whole run.

`GnssFix` now carries explicit `sigma_xy` / `sigma_z` in metres, gated by the new `gnss.max_sigma_xy` (25.0) and `gnss.max_sigma_z` (50.0). `max_hdop` / `max_vdop` keep their original meaning and apply only when a fix carries no covariance, which in practice means `gps_msgs/GPSFix` reporting receiver-native DOP. Accepted fixes are fused exactly as before: replaying the same bag gives a bit-identical trajectory.

### The jump gate rejected ordinary GPS noise

`gnss.max_speed` bounded a fix at `max_speed * gap + margin`, entirely absolute metres. At 1 Hz with `max_speed: 2.0` and the default 5 m margin that is a 7 m bound, and a receiver whose own sigma is ~6 m trips it constantly. Measured: **157 of 500 good fixes rejected, loop closure 2.62 m to 7.27 m**, on a rover whose real top speed is 0.6 m/s, so no honest motion was ever involved.

The bound now adds `gnss.max_speed_sigma_k` (default 5.0) multiples of the receiver's reported sigma. With the gate enabled at 2.0 the same bag gives 0 of 500 rejected and a 2.62 m closure, identical to disabling it, while a 700 m spike is still rejected.

The noise term scales with the **receiver's** sigma and deliberately not with the filter's covariance. Chi-squared is already the covariance-scaled test, and this gate exists to catch what a coast-inflated chi-squared admits, so scaling it by `P` would reopen the hole it was built to plug. A test pins that property.

### GPS track heading overrode better heading sources

Track heading derives yaw from the GPS displacement bearing, which is course over ground. On a curved path that differs from body heading by a real bias, not just noise, so fusing it pulls the estimate wrong regardless of its covariance.

It ran even when a stronger absolute source was already active (dual antenna, magnetometer, or 9-axis IMU orientation), all of which the heading-source ladder already ranks above it. Separately, `gps_track_heading_min_speed` and `gps_track_heading_max_yaw_rate` were documented as guarding this fusion but only gated the `heading_validated_` flag, so a slow turning robot fused its turn radius as a heading.

Reported by a user whose Nav2 path was straight without GPS and a zig-zag with it, on a robot with a stable magnetometer. Both guards now apply to the fusion. Robots with no absolute heading source are unaffected, pinned by a regression test.

### GNSS rejections were reported as NOT_PROCESSED

The enum-to-string mapper feeding `/fusion/debug/gnss_status` had no case for `IMPLAUSIBLE_JUMP`, so it fell through to `NOT_PROCESSED`, the value meaning "update_gnss was never called". Fixes disappeared with no stated cause. The mapper is now exhaustive and covers the two new sigma reasons.

### Also

- Corrected the GPSFix driver list: `nmea_navsat_driver` publishes `NavSatFix`, not `gps_msgs/GPSFix` (verified against…

Share this resource


Discovered 2026-08-14 Source GitHub Archive 2026-08 →