Nav2 Integration¶
FusionCore is a drop-in odometry source for Nav2.
| FusionCore output | Nav2 uses it for |
|---|---|
/fusion/odom |
Set as odom_topic in nav2_params.yaml |
odom → base_link TF |
Costmaps and planners read this directly |
/fusion/pose |
AMCL initial pose, slam_toolbox pose input |
/diagnostics |
Nav2-compatible format |
Two things it does not publish, so you know where the boundary is:
- No
map → odomTF. FusionCore is an odometry source, not a global localizer. The bundlednav2_params.yamltherefore runs every costmap and planner withglobal_frame: odom, which is the correct setup for GPS navigation with no prior map. If you setglobal_frame: mapyou need something else publishingmap → odom: AMCL against a static map, slam_toolbox, or a static identity transform. - No map. There is no static map and no AMCL in the GPS path. Planning happens on the local rolling costmap with
allow_unknown: true.
Outdoor GPS navigation (the main path)¶
One command starts the entire stack: FusionCore + Nav2, lifecycle managed automatically:
The bundled nav2_params.yaml is pre-wired to /fusion/odom and configured for outdoor GPS navigation: NavFn planner with allow_unknown: true, Regulated Pure Pursuit controller, no AMCL, no static map required.
With an environment preset:
ros2 launch fusioncore_ros fusioncore_nav2.launch.py \
fusioncore_config:=your_robot.yaml \
env_config:=$(ros2 pkg prefix fusioncore_ros)/share/fusioncore_ros/config/env_urban.yaml
With your own nav2_params.yaml:
ros2 launch fusioncore_ros fusioncore_nav2.launch.py \
fusioncore_config:=your_robot.yaml \
nav2_params:=/path/to/your_nav2_params.yaml
GPS waypoint navigation¶
FusionCore advertises /fromLL, which converts lat/lon/alt into the local map frame. This is the service nav2_waypoint_follower calls when you send a FollowGPSWaypoints goal, so GPS waypoint navigation works against FusionCore with no bridge node and no robot_localization instance running.
Convert a single point by hand:
ros2 service call /fromLL robot_localization/srv/FromLL \
"{ll_point: {latitude: 43.2557, longitude: -79.8711, altitude: 0.0}}"
The service returns the point in the local ENU frame anchored at the first GPS fix (or at reference.x/y/z if you set an explicit origin). Until a fix arrives the reference is unset, so it returns zeros and logs a warning: wait for /fusion/odom before sending waypoints.
The service type is robot_localization/srv/FromLL, deliberately
nav2_waypoint_follower has that type compiled into its header, and ROS 2 matches services on name and type. A service with identical fields under a different type name is invisible to Nav2's client, which then waits forever without ever reporting an error.
So FusionCore advertises the type Nav2 expects. Only the interface definition is shared; no robot_localization code is linked or executed, and you do not run a robot_localization node. This is what makes FusionCore a drop-in replacement here rather than a migration that quietly breaks your waypoint following.
Versions before 0.3.5 advertised /fromLL as fusioncore_ros/srv/FromLL. Manual ros2 service call worked (you supply the type yourself) but followGpsWaypoints hung on waiting for service to appear. If you are on 0.3.4 or earlier, upgrade. fusioncore_ros/srv/FromLL still exists so old builds compile, but nothing serves it.
Only the single-point FromLL service is provided. FromLLArray, ToLL (map frame back to lat/lon) and SetDatum are not. Nav2's GPS waypoint follower uses FromLL only, so this covers that path; see the migration guide for the full service comparison.
Humble: Nav2 has no GPS waypoint following at all
FollowGPSWaypoints does not exist in Humble's Nav2. It was added in Jazzy, and Humble's nav2_waypoint_follower neither depends on robot_localization nor calls /fromLL. So on Humble this section does not apply: FusionCore still advertises /fromLL and you can call it from your own code, but there is no Nav2 GPS waypoint action to plug it into.
FusionCore itself builds and runs normally on Humble. robot_localization is released there (3.5.4-1) with an identical FromLL.srv, so the dependency resolves.
Collision monitor¶
Worth knowing what the bundled nav2_params.yaml does here, because it is deliberate and it is not what it looks like.
nav2_bringup's navigation_launch.py starts collision_monitor unconditionally (Jazzy has no launch argument to turn it off) and lifecycle-manages it, and that node refuses to configure without an observation_sources parameter:
[ERROR] [collision_monitor]: Error while getting parameters:
parameter 'observation_sources' is not initialized
That is not a cosmetic failure. The collision monitor sits in the command path: it subscribes to cmd_vel_smoothed and republishes cmd_vel, which is the topic your base controller listens to. If it never activates, planning and control run fine, produce velocities, and nothing reaches the wheels. This is what a user hit in issue #73, and before 0.3.5 the bundled config had no collision_monitor section at all.
The config now ships one, but read what it actually is:
It is a pass-through and it provides no obstacle protection. observation_sources and polygons are both mandatory and an empty list is rejected by the parameter parser, so there is no way to declare "no sources". Instead one polygon and one source are declared and then disabled, which lets the node activate and hand velocity through untouched. Verified: 0.42 m/s published on cmd_vel_smoothed comes out of cmd_vel unchanged.
That matches the rest of the bundled config, which targets GPS navigation with no map and no lidar. But do not mistake a running collision_monitor for a working one. If you have a laser, set both enabled: True in the collision_monitor section, point scan.topic at your real scan topic, and size the polygon radius to your robot. nav2_bringup's own nav2_params.yaml has a fuller example using a footprint-approach polygon.
What the launch file does under the hood¶
The lifecycle timing is important. fusioncore_nav2.launch.py:
- Starts
fusioncore_nodeas a lifecycle node - Waits 2 seconds, sends
configure - On
configuring → inactive, immediately sendsactivate - Waits 8 seconds from launch start, then starts Nav2
The 8-second delay gives FusionCore time to activate and publish odom → base_link, and gives Nav2's bt_navigator TF listener time to warm up before its configure step runs. Without this gap, bt_navigator can fail to configure because its TF buffer is empty on cold DDS startup.
Updating an existing nav2_params.yaml¶
If you have your own Nav2 config, find every odom_topic and update it:
# before
bt_navigator:
ros__parameters:
odom_topic: /odometry/filtered
velocity_smoother:
ros__parameters:
odom_topic: /odometry/filtered
# after
bt_navigator:
ros__parameters:
odom_topic: /fusion/odom
velocity_smoother:
ros__parameters:
odom_topic: /fusion/odom
Also remove AMCL if you are doing GPS-only outdoor navigation. AMCL publishes map → odom and needs a static map to localize against. Without a map it has nothing to do and will log constant warnings. The global_frame in bt_navigator, global_costmap, and behavior_server must be set to odom for GPS-only navigation.
If you are doing indoor navigation with a static map, keep AMCL. FusionCore publishes odom → base_link. AMCL publishes map → odom. These are different TF edges and are fully compatible: there is no conflict.
Indoor navigation (no GPS)¶
FusionCore works without GPS. Set reference.use_first_fix: false in your config, and the filter starts at the origin on IMU + wheel odometry alone.
For indoor navigation with a map, run slam_toolbox or AMCL alongside FusionCore. FusionCore handles odom → base_link. Your SLAM system handles map → odom.
# FusionCore for odometry
ros2 launch fusioncore_ros fusioncore.launch.py \
fusioncore_config:=your_robot.yaml
# slam_toolbox for map → odom
ros2 launch slam_toolbox online_async_launch.py
Coming from robot_localization¶
See the Migration Guide for a complete parameter mapping and step-by-step instructions.