diff --git a/src/hangar_sim/CMakeLists.txt b/src/hangar_sim/CMakeLists.txt
index 0d49770e5..61482d938 100644
--- a/src/hangar_sim/CMakeLists.txt
+++ b/src/hangar_sim/CMakeLists.txt
@@ -3,6 +3,31 @@ project(hangar_sim)
find_package(ament_cmake REQUIRED)
find_package(picknik_accessories REQUIRED)
+find_package(rclcpp REQUIRED)
+find_package(nav_msgs REQUIRED)
+find_package(geometry_msgs REQUIRED)
+find_package(sensor_msgs REQUIRED)
+find_package(tf2 REQUIRED)
+find_package(tf2_geometry_msgs REQUIRED)
+find_package(tf2_ros REQUIRED)
+
+add_executable(slip_aware_odom src/slip_aware_odom.cpp)
+target_include_directories(slip_aware_odom PRIVATE include)
+target_compile_features(slip_aware_odom PRIVATE cxx_std_20)
+ament_target_dependencies(slip_aware_odom rclcpp nav_msgs tf2 tf2_geometry_msgs)
+
+add_executable(amcl_odom_gate src/amcl_odom_gate.cpp src/amcl_odom_gate_logic.cpp)
+target_include_directories(amcl_odom_gate PRIVATE include)
+target_compile_features(amcl_odom_gate PRIVATE cxx_std_20)
+ament_target_dependencies(amcl_odom_gate rclcpp geometry_msgs tf2 tf2_geometry_msgs tf2_ros)
+
+add_executable(odom_world_drift src/odom_world_drift.cpp)
+target_include_directories(odom_world_drift PRIVATE include)
+target_compile_features(odom_world_drift PRIVATE cxx_std_20)
+ament_target_dependencies(odom_world_drift rclcpp nav_msgs sensor_msgs geometry_msgs tf2 tf2_geometry_msgs
+ tf2_ros)
+
+install(TARGETS slip_aware_odom amcl_odom_gate odom_world_drift DESTINATION lib/${PROJECT_NAME})
install(
DIRECTORY
@@ -47,6 +72,22 @@ if(BUILD_TESTING)
ENV MOVEIT_CONFIG_PACKAGE=hangar_sim
MOVEIT_HOST_USER_WORKSPACE=${CMAKE_SOURCE_DIR}
ROS_LOG_DIR=${CMAKE_CURRENT_BINARY_DIR}/test_results/${PROJECT_NAME}/ros_logs)
+
+ # Unit tests for the pure amcl_odom_gate decision logic (no ROS/TF).
+ find_package(ament_cmake_gmock REQUIRED)
+ ament_add_gmock(test_amcl_odom_gate test/test_amcl_odom_gate.cpp src/amcl_odom_gate_logic.cpp)
+ target_include_directories(test_amcl_odom_gate PRIVATE include)
+ target_compile_features(test_amcl_odom_gate PRIVATE cxx_std_20)
+
+ # Unit tests for the pure odom_world_drift rail-joint index resolver (no ROS).
+ ament_add_gmock(test_odom_world_drift test/test_odom_world_drift.cpp)
+ target_include_directories(test_odom_world_drift PRIVATE include)
+ target_compile_features(test_odom_world_drift PRIVATE cxx_std_20)
+
+ # Unit tests for the pure slip_aware_odom re-anchor/stitch and covariance-ramp logic (no ROS).
+ ament_add_gmock(test_slip_aware_odom test/test_slip_aware_odom.cpp)
+ target_include_directories(test_slip_aware_odom PRIVATE include)
+ target_compile_features(test_slip_aware_odom PRIVATE cxx_std_20)
endif()
ament_package()
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 ab6e0afb3..eea8e6e45 100644
--- a/src/hangar_sim/config/control/picknik_ur.ros2_control.yaml
+++ b/src/hangar_sim/config/control/picknik_ur.ros2_control.yaml
@@ -160,6 +160,7 @@ platform_velocity_controller_nav2:
joint_state_broadcaster:
ros__parameters:
use_local_topics: false
+ update_rate: 50 # publish /joint_states at 50 Hz, not the 600 Hz control loop
joints:
- shoulder_pan_joint
- shoulder_lift_joint
@@ -211,19 +212,19 @@ joint_trajectory_controller:
action_monitor_rate: 20.0
allow_partial_joints_goal: true
open_loop_control: false # this is closed loop
- # Workaround for PickNikRobotics/moveit_pro#20766: trajectory generation
- # leaves sub-epsilon terminal-velocity residuals that Jazzy's JTC rejects
- # by default — measured ~1e-4 on `linear_x_joint` from the MTC Cartesian
- # approach in "ML Move Boxes to Loading Zone", and ~4e-7 from joint
- # interpolation in "Point-to-Point Trajectory" (previously skipped in
- # test/objectives_integration_test.py for exactly this rejection).
+ # Workaround: trajectory generation leaves sub-epsilon terminal-velocity
+ # residuals that Jazzy's JTC rejects by default — measured ~1e-4 on
+ # `linear_x_joint` from the MTC Cartesian approach in "ML Move Boxes to
+ # Loading Zone", and ~4e-7 from joint interpolation in "Point-to-Point
+ # Trajectory" (previously skipped in test/objectives_integration_test.py
+ # for exactly this rejection).
# CAUTION: this flag is binary and controller-wide — it disables the
# endpoint-still-moving check for every magnitude on every joint,
# including the mecanum base, where a genuinely nonzero terminal velocity
# means the base is still translating at trajectory end and then snaps to
# a position hold. Acceptable for this sim config; re-evaluate before
- # copying to real hardware. Remove when moveit_pro#20766 clamps the
- # residuals at trajectory generation.
+ # copying to real hardware. Remove once trajectory generation clamps the
+ # residuals upstream.
allow_nonzero_velocity_at_trajectory_end: true
gains:
shoulder_pan_joint:
@@ -297,6 +298,7 @@ force_torque_sensor_broadcaster:
imu_sensor_broadcaster:
ros__parameters:
+ update_rate: 100 # 100 Hz IMU is plenty for fuse (optimizes at 10 Hz); was inheriting the 600 Hz control loop
sensor_name: imu_site
frame_id: ridgeback_base_link
# Static covariance values (row-major 3x3 matrices)
diff --git a/src/hangar_sim/config/fuse/fuse.yaml b/src/hangar_sim/config/fuse/fuse.yaml
index 4ad1039f5..22f5135b6 100644
--- a/src/hangar_sim/config/fuse/fuse.yaml
+++ b/src/hangar_sim/config/fuse/fuse.yaml
@@ -3,9 +3,9 @@
state_estimator:
ros__parameters:
# Fixed-lag smoother configuration
- optimization_frequency: 20.0
+ optimization_frequency: 10.0 # match publish_frequency (10 Hz); optimizing 2x faster than we publish was wasted work
transaction_timeout: 0.01
- lag_duration: 0.5
+ lag_duration: 0.3 # chosen: median 0.465deg (<0.5 goal), clean transient tail vs 0.25
# Motion model for mobile base (3D omnidirectional)
motion_models:
@@ -51,7 +51,7 @@ state_estimator:
# only the relative change between consecutive messages, preventing
# accumulated drift from corrupting the estimate.
wheel_odom_sensor:
- topic: /platform_velocity_controller_nav2/odom
+ topic: /odom_slip_aware # slip_aware_odom republishes with spin-aware yaw covariance
queue_size: 10
pose_loss:
type: fuse_loss::HuberLoss
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 42241108e..05ba84c3f 100644
--- a/src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro
+++ b/src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro
@@ -31,11 +31,11 @@
${mujoco_model}
hangar_sim
20
- 60
+ 30
10
${publish_odom}
ridgeback_base_link
- 150
+ 50
diff --git a/src/hangar_sim/description/ur5e_ridgeback.xml b/src/hangar_sim/description/ur5e_ridgeback.xml
index 2c18a2d13..5fe0538e3 100644
--- a/src/hangar_sim/description/ur5e_ridgeback.xml
+++ b/src/hangar_sim/description/ur5e_ridgeback.xml
@@ -313,6 +313,10 @@
conaffinity="0"
group="2"
/>
+
+