diff --git a/src/hangar_sim/config/control/picknik_ur.ros2_control.yaml b/src/hangar_sim/config/control/picknik_ur.ros2_control.yaml
index 995815a5f..c03b2cccd 100644
--- a/src/hangar_sim/config/control/picknik_ur.ros2_control.yaml
+++ b/src/hangar_sim/config/control/picknik_ur.ros2_control.yaml
@@ -53,8 +53,8 @@ platform_velocity_controller:
reference_timeout: 0.1
use_stamped_vel: False
- pose_covariance_diagonal: [0.001, 0.001, 1000000.0, 1000000.0, 1000000.0, 0.03]
- twist_covariance_diagonal: [0.001, 0.001, 0.001, 1000000.0, 1000000.0, 0.03]
+ pose_covariance_diagonal: [0.1, 0.1, 1000000.0, 1000000.0, 1000000.0, 0.5]
+ twist_covariance_diagonal: [0.1, 0.1, 0.1, 1000000.0, 1000000.0, 0.5]
cmd_vel_timeout: 0.5
@@ -282,7 +282,14 @@ force_torque_sensor_broadcaster:
imu_sensor_broadcaster:
ros__parameters:
sensor_name: imu_site
- frame_id: imu_link
+ frame_id: ridgeback_base_link
+ # Static covariance values (row-major 3x3 matrices)
+ # Orientation covariance (rad^2)
+ static_covariance_orientation: [0.001, 0.0, 0.0, 0.0, 0.001, 0.0, 0.0, 0.0, 0.001]
+ # Angular velocity covariance (rad/s)^2
+ static_covariance_angular_velocity: [0.01, 0.0, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0, 0.01]
+ # Linear acceleration covariance (m/s^2)^2
+ static_covariance_linear_acceleration: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
velocity_force_controller:
diff --git a/src/hangar_sim/config/fuse/fuse.yaml b/src/hangar_sim/config/fuse/fuse.yaml
index db8a0d8a1..fbc429d41 100644
--- a/src/hangar_sim/config/fuse/fuse.yaml
+++ b/src/hangar_sim/config/fuse/fuse.yaml
@@ -1,5 +1,5 @@
# Mobile base localization configuration using fuse
-# Using 3D odometry model for wheel odometry-based pose estimation
+# Ground vehicle state estimation: wheel odometry + IMU
state_estimator:
ros__parameters:
# Fixed-lag smoother configuration
@@ -15,7 +15,10 @@ state_estimator:
mobile_base_motion:
# Process noise for state variables
# Order: x, y, z, roll, pitch, yaw, vx, vy, vz, vroll, vpitch, vyaw, ax, ay, az
- process_noise_diagonal: [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 1.0, 1.0, 1.0]
+ # z, roll, pitch and their derivatives are near-zero for a ground robot.
+ #process_noise_diagonal: [0.25, 0.25, 0.00001, 0.00001, 0.00001, 0.25
+ # -, 0.1, 0.1, 0.00001, 0.00001, 0.0001, 0.1, 0.0001, 0.0001, 0.00001]
+ process_noise_diagonal: [1.0, 1.0, 0.0001, 0.0001, 0.0001, 0.5, 0.5, 0.5, 0.0001, 0.0001, 0.0001, 0.25, 0.00001, 0.00001, 0.00001]
sensor_models:
initial_localization_sensor:
@@ -25,32 +28,48 @@ state_estimator:
wheel_odom_sensor:
type: fuse_models::Odometry3D
motion_models: [mobile_base_motion]
+ imu_sensor:
+ type: fuse_models::Imu3D
+ motion_models: [mobile_base_motion]
initial_localization_sensor:
publish_on_startup: true
# Order: x, y, z, roll, pitch, yaw, vx, vy, vz, vroll, vpitch, vyaw, ax, ay, az
initial_state: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
- initial_sigma: [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
+ # Order: x, y, z, roll, pitch, yaw, vx, vy, vz, vroll, vpitch, vyaw, ax, ay, az
+ # z and its derivatives start with very tight sigma — ground robot stays on the plane.
+ initial_sigma: [0.1, 0.1, 0.001, 0.01, 0.01, 0.1, 0.1, 0.1, 0.001, 0.01, 0.01, 0.1, 0.001, 0.001, 0.001]
+ # Wheel odometry — provides translation (x, y) and yaw
+ # Using differential mode because platform_velocity_controller/odom is
+ # wheel-integrated odometry that drifts over time. Differential mode fuses
+ # only the relative change between consecutive messages, preventing
+ # accumulated drift from corrupting the estimate.
wheel_odom_sensor:
- # Using /odom_reliable from QoS relay (bridges BEST_EFFORT to RELIABLE)
- topic: /odom_reliable
+ topic: /platform_velocity_controller/odom
queue_size: 10
- # 2D position from wheels (no z)
- position_dimensions: ['x', 'y']
- # Heading only
+ pose_loss:
+ type: fuse_loss::HuberLoss
+ a: 1.0
+ linear_velocity_loss:
+ type: fuse_loss::HuberLoss
+ a: 1.0
+ position_dimensions: ['x', 'y', 'z']
orientation_dimensions: ['yaw']
- # Forward velocity
- linear_velocity_dimensions: ['x']
- # Turn rate
+ linear_velocity_dimensions: ['x', 'y']
angular_velocity_dimensions: ['yaw']
- # Use relative constraints (pose changes rather than absolute poses)
differential: true
- # Covariances - use large values for unused dimensions
- # Order: x, y, z, roll, pitch, yaw
- pose_covariance_diagonal: [0.05, 0.05, 1e9, 1e9, 1e9, 0.05]
- # Order: vx, vy, vz, vroll, vpitch, vyaw
- twist_covariance_diagonal: [0.05, 1e9, 1e9, 1e9, 1e9, 0.05]
+ independent: false
+ use_twist_covariance: true
+
+ # IMU sensor — provides orientation and yaw rate
+ # - Roll/pitch/yaw orientation (absolute, covariance 0.001 vs wheel odom 0.5)
+ # - Yaw angular velocity from gyroscope
+ imu_sensor:
+ topic: /imu_sensor_broadcaster/imu_reliable
+ orientation_dimensions: ['roll', 'pitch', 'yaw']
+ angular_velocity_dimensions: ['yaw']
+ queue_size: 10
# Publish filtered odometry
publishers:
@@ -59,8 +78,8 @@ state_estimator:
filtered_publisher:
topic: 'odom_filtered'
- base_link_frame_id: 'base_footprint'
- base_link_output_frame_id: 'base_footprint'
+ base_link_frame_id: 'ridgeback_base_link'
+ base_link_output_frame_id: 'ridgeback_base_link'
odom_frame_id: 'odom'
map_frame_id: 'map'
world_frame_id: 'odom'
diff --git a/src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro b/src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro
index 85c866dd2..dd0b93e96 100644
--- a/src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro
+++ b/src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro
@@ -16,6 +16,18 @@
${mujoco_viewer}
default
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/hangar_sim/launch/agent_bridge.launch.xml b/src/hangar_sim/launch/agent_bridge.launch.xml
index 3cc960c03..b2aa75434 100644
--- a/src/hangar_sim/launch/agent_bridge.launch.xml
+++ b/src/hangar_sim/launch/agent_bridge.launch.xml
@@ -3,5 +3,4 @@
-
diff --git a/src/hangar_sim/launch/fuse.launch.py b/src/hangar_sim/launch/fuse.launch.py
deleted file mode 100644
index 089f93f53..000000000
--- a/src/hangar_sim/launch/fuse.launch.py
+++ /dev/null
@@ -1,38 +0,0 @@
-#! /usr/bin/env python3
-
-# Copyright 2024 PickNik Robotics
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from launch import LaunchDescription
-from launch.substitutions import PathJoinSubstitution
-from launch_ros.actions import Node
-from launch_ros.substitutions import FindPackageShare
-
-
-def generate_launch_description():
- pkg_dir = FindPackageShare("hangar_sim")
-
- return LaunchDescription(
- [
- # Run the fuse fixed-lag smoother for mobile base state estimation
- Node(
- package="fuse_optimizers",
- executable="fixed_lag_smoother_node",
- name="state_estimator",
- parameters=[
- PathJoinSubstitution([pkg_dir, "config", "fuse", "fuse.yaml"])
- ],
- ),
- ]
- )
diff --git a/src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py b/src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
index 92d81f6c8..c4961a50a 100644
--- a/src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
+++ b/src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
@@ -268,34 +268,14 @@ def generate_launch_description():
arguments=["0.0", "0.0", "0.0", "0.0", "0.0", "0.0", "map", "odom"],
)
- # Publish odometry as joint state messages
- odom_to_joint_state_repub = Node(
- package="hangar_sim",
- executable="odometry_joint_state_publisher.py",
- name="odometry_joint_state_publisher",
- output="log",
- )
-
- # QoS relay to bridge BEST_EFFORT odom to RELIABLE for fuse
- odom_qos_relay = Node(
+ # QoS relay to bridge BEST_EFFORT odom and IMU to RELIABLE for fuse
+ sensor_qos_relay = Node(
package="hangar_sim",
executable="odom_qos_relay.py",
- name="odom_qos_relay",
+ name="sensor_qos_relay",
output="log",
)
- # Fuse state estimator for mobile base localization
- hangar_sim_pkg = FindPackageShare("hangar_sim")
- fuse_state_estimator = Node(
- package="fuse_optimizers",
- executable="fixed_lag_smoother_node",
- name="state_estimator",
- parameters=[
- PathJoinSubstitution([hangar_sim_pkg, "config", "fuse", "fuse.yaml"])
- ],
- output="screen",
- )
-
# Create the launch description and populate
ld = LaunchDescription()
@@ -324,8 +304,19 @@ def generate_launch_description():
ld.add_action(static_tf_world_to_map)
ld.add_action(static_tf_map_to_odom)
- ld.add_action(odom_to_joint_state_repub)
- ld.add_action(odom_qos_relay)
+ ld.add_action(sensor_qos_relay)
+
+ # Fuse state estimator for mobile base localization
+ hangar_sim_pkg = FindPackageShare("hangar_sim")
+ fuse_state_estimator = Node(
+ package="fuse_optimizers",
+ executable="fixed_lag_smoother_node",
+ name="state_estimator",
+ parameters=[
+ PathJoinSubstitution([hangar_sim_pkg, "config", "fuse", "fuse.yaml"])
+ ],
+ output="screen",
+ )
ld.add_action(fuse_state_estimator)
return ld
diff --git a/src/hangar_sim/script/odom_qos_relay.py b/src/hangar_sim/script/odom_qos_relay.py
index 11c588922..e0bbdada0 100755
--- a/src/hangar_sim/script/odom_qos_relay.py
+++ b/src/hangar_sim/script/odom_qos_relay.py
@@ -28,12 +28,13 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
-"""Relay node to bridge BEST_EFFORT odometry to RELIABLE QoS for fuse."""
+"""Relay node to bridge BEST_EFFORT sensor topics to RELIABLE QoS for fuse."""
import rclpy
from rclpy.node import Node
from nav_msgs.msg import Odometry
+from sensor_msgs.msg import Imu
from rclpy.qos import (
QoSProfile,
@@ -43,11 +44,11 @@
)
-class OdomQoSRelay(Node):
- """Relay node that subscribes to odometry with BEST_EFFORT and republishes with RELIABLE QoS."""
+class SensorQoSRelay(Node):
+ """Relay node that subscribes to sensor topics with BEST_EFFORT and republishes with RELIABLE QoS."""
def __init__(self):
- super().__init__("odom_qos_relay")
+ super().__init__("sensor_qos_relay")
# Subscribe with BEST_EFFORT to match mujoco_system publisher
qos_sub = QoSProfile(
@@ -65,18 +66,30 @@ def __init__(self):
depth=10,
)
- self.sub = self.create_subscription(
+ # Odometry relay
+ self.odom_sub = self.create_subscription(
Odometry, "/odom", self.odom_callback, qos_sub
)
- self.pub = self.create_publisher(Odometry, "/odom_reliable", qos_pub)
+ self.odom_pub = self.create_publisher(Odometry, "/odom_reliable", qos_pub)
+
+ # IMU relay
+ self.imu_sub = self.create_subscription(
+ Imu, "/imu_sensor_broadcaster/imu", self.imu_callback, qos_sub
+ )
+ self.imu_pub = self.create_publisher(
+ Imu, "/imu_sensor_broadcaster/imu_reliable", qos_pub
+ )
def odom_callback(self, msg):
- self.pub.publish(msg)
+ self.odom_pub.publish(msg)
+
+ def imu_callback(self, msg):
+ self.imu_pub.publish(msg)
def main(args=None):
rclpy.init(args=args)
- node = OdomQoSRelay()
+ node = SensorQoSRelay()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()