Add fuse odometry localization for the hangar_sim mobile base - #790
Add fuse odometry localization for the hangar_sim mobile base#790bkanator wants to merge 30 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughSummary by CodeRabbit
WalkthroughChangesThe PR adds slip-aware odometry, AMCL transform gating, and dynamic Fuse localization integration
Possibly related PRs
Suggested reviewers: Caution Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional.
❌ Failed checks (1 error)
✅ Passed checks (3 passed)
Comment |
ecb0e20 to
98ef35f
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py`:
- Around line 319-335: Update the odom_world_drift and slip_aware_odom Node
definitions to pass the use_sim_time LaunchConfiguration as their ROS parameter,
ensuring both nodes use simulation time consistently when stamping outputs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6834a0a6-300e-4af5-bd44-d25a11fda468
📒 Files selected for processing (10)
src/hangar_sim/CMakeLists.txtsrc/hangar_sim/config/control/picknik_ur.ros2_control.yamlsrc/hangar_sim/config/fuse/fuse.yamlsrc/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacrosrc/hangar_sim/description/ur5e_ridgeback.xmlsrc/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.pysrc/hangar_sim/package.xmlsrc/hangar_sim/params/nav2_params.yamlsrc/hangar_sim/script/odom_world_drift.pysrc/hangar_sim/src/slip_aware_odom.cpp
98ef35f to
b3952a6
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py`:
- Around line 190-193: Update the state estimator launch configuration
associated with the use_fuse argument to pass the use_sim_time launch value as
the node parameter {"use_sim_time": use_sim_time} alongside the existing
fuse.yaml configuration, ensuring Fuse uses the simulation clock.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 137c1df9-a38e-4fc2-bd73-ece87806e72f
📒 Files selected for processing (10)
src/hangar_sim/CMakeLists.txtsrc/hangar_sim/config/control/picknik_ur.ros2_control.yamlsrc/hangar_sim/config/fuse/fuse.yamlsrc/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacrosrc/hangar_sim/description/ur5e_ridgeback.xmlsrc/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.pysrc/hangar_sim/package.xmlsrc/hangar_sim/params/nav2_params.yamlsrc/hangar_sim/script/odom_world_drift.pysrc/hangar_sim/src/slip_aware_odom.cpp
995a373 to
e9fa7da
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py (1)
401-411: 🗄️ Data Integrity & Integration | 🟠 Major
fuse_state_estimatorstill doesn't passuse_sim_time— this was already flagged in a prior review round and appears unaddressed here.
fuse_state_estimatoronly loadsfuse.yaml; withuse_fusenow defaulting totrue, Fuse stays on the wall clock while the rest of the sim stack (including the newodom_world_drift,slip_aware_odom, andamcl_odom_gatenodes just above, which now all correctly receive{"use_sim_time": use_sim_time}) runs on sim time. This is the same gap previously raised onstate_estimator.Suggested fix
fuse_state_estimator = Node( package="fuse_optimizers", executable="fixed_lag_smoother_node", name="state_estimator", parameters=[ - PathJoinSubstitution([hangar_sim_pkg, "config", "fuse", "fuse.yaml"]) + PathJoinSubstitution([hangar_sim_pkg, "config", "fuse", "fuse.yaml"]), + {"use_sim_time": use_sim_time}, ], output="screen", condition=IfCondition(LaunchConfiguration("use_fuse")), )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py` around lines 401 - 411, Update the fuse_state_estimator Node parameters to include the launch use_sim_time value alongside fuse.yaml, matching the {"use_sim_time": use_sim_time} configuration used by the surrounding simulation nodes. Preserve the existing package, executable, condition, and output settings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py`:
- Around line 340-349: The launch configuration must provide a map->odom
publisher when localization is enabled and use_fuse is false. Update the
amcl_odom_gate/AMCL launch conditions or add a fallback publisher so the
use_fuse=false localization path receives map->odom, while preserving the
existing static_tf_map_to_odom behavior for localization=false.
In `@src/hangar_sim/package.xml`:
- Around line 44-48: Add the missing test dependency declaration for
ament_cmake_gmock in src/hangar_sim/package.xml, alongside the existing
dependency entries, so the test_amcl_odom_gate.cpp gmock target configured by
ament_add_gmock has its required package dependency.
---
Outside diff comments:
In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py`:
- Around line 401-411: Update the fuse_state_estimator Node parameters to
include the launch use_sim_time value alongside fuse.yaml, matching the
{"use_sim_time": use_sim_time} configuration used by the surrounding simulation
nodes. Preserve the existing package, executable, condition, and output
settings.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b7ab5b22-211b-48ce-90f4-5c1623ea3a3c
📒 Files selected for processing (11)
src/hangar_sim/CMakeLists.txtsrc/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.pysrc/hangar_sim/objectives/navigate_to_clicked_point.xmlsrc/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xmlsrc/hangar_sim/package.xmlsrc/hangar_sim/params/nav2_params.yamlsrc/hangar_sim/script/odom_world_drift.pysrc/hangar_sim/src/amcl_odom_gate.cppsrc/hangar_sim/src/amcl_odom_gate_logic.hppsrc/hangar_sim/src/slip_aware_odom.cppsrc/hangar_sim/test/test_amcl_odom_gate.cpp
💤 Files with no reviewable changes (2)
- src/hangar_sim/objectives/navigate_to_clicked_point.xml
- src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
🚧 Files skipped from review as they are similar to previous changes (3)
- src/hangar_sim/params/nav2_params.yaml
- src/hangar_sim/script/odom_world_drift.py
- src/hangar_sim/src/slip_aware_odom.cpp
c57b768 to
72b4afe
Compare
|
[written by AI] Addressed the CodeRabbit review comments (force-pushed; the branch was also squashed to a single commit and rebased onto latest main):
@coderabbitai full review |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
src/hangar_sim/src/amcl_odom_gate.cpp (1)
115-115: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winWall timer paired with sim-time stamps.
create_wall_timeris unaffected by/clock, butpublish()stamps withnow()(ROS time underuse_sim_time). If the sim runs slower than realtime, consecutive broadcasts can carry identical or barely-advancing stamps; faster-than-realtime under-publishes.rclcpp::create_timer(this, get_clock(), rclcpp::Duration::from_seconds(kPubPeriod), cb)ties the period to the same clock the stamps come from.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hangar_sim/src/amcl_odom_gate.cpp` at line 115, Replace the wall-clock timer assigned to timer_ in the constructor with an rclcpp timer created using get_clock() and an rclcpp::Duration derived from kPubPeriod, while preserving the existing publish() callback.src/hangar_sim/CMakeLists.txt (1)
11-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSet
cxx_std_17onslip_aware_odomtoo.
src/slip_aware_odom.cppusesstd::clamp(C++17), but unlikeamcl_odom_gatethis target doesn't declare the standard and relies on whatever the toolchain/dependency defaults provide.♻️ Proposed change
add_executable(slip_aware_odom src/slip_aware_odom.cpp) +target_compile_features(slip_aware_odom PRIVATE cxx_std_17) ament_target_dependencies(slip_aware_odom rclcpp nav_msgs)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hangar_sim/CMakeLists.txt` around lines 11 - 12, Update the slip_aware_odom target declaration to require C++17, matching the existing amcl_odom_gate target configuration, so src/slip_aware_odom.cpp can use std::clamp consistently across toolchains.src/hangar_sim/src/slip_aware_odom.cpp (1)
66-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider exposing
kHoldGap/kHoldPeriodas node parameters.The hold threshold (15 ms) is sized against the controller's measured ~500 Hz rate while its config declares
publish_rate: 50.0. If the fork is ever fixed to honour that param, every inter-message interval (20 ms) exceedskHoldGapand the node starts injecting fabricated zero-velocity samples into fuse during normal driving. Making these declarable parameters (or deriving the gap from the observed inter-message interval) removes the hidden coupling.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hangar_sim/src/slip_aware_odom.cpp` around lines 66 - 69, Expose kHoldGap and kHoldPeriod as configurable node parameters, and use the resolved values in the silent-source hold logic instead of fixed constants. Preserve the current defaults (0.015 and 0.02 seconds) so existing behavior remains unchanged unless parameters are explicitly overridden.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py`:
- Around line 94-105: Update the localization include in the launch flow, rather
than only the nav2_container parameters, so localization_launch.py receives
params with the tf_broadcast rewrite from param_substitutions. Ensure
use_fuse:=false produces AMCL tf_broadcast=true and use_fuse:=true preserves
false, either by passing the rewritten YAML or applying the equivalent
RewrittenYaml transformation inside localization_launch.py.
In `@src/hangar_sim/script/odom_world_drift.py`:
- Around line 97-110: Update _joints so the incoming name list is cached even
when resolving RAIL_JOINTS fails, and guard the position lookup against self.idx
being None. Preserve the early return for messages missing rail joints while
ensuring repeated messages with the same names cannot iterate over a cleared
index cache.
In `@src/hangar_sim/test/test_amcl_odom_gate.cpp`:
- Around line 165-176: Update InterpolateOdomDuplicateStampsNoNan so it no
longer claims to exercise the unreachable zero-span interpolation guard: either
revise the test comment and assertions to verify the target-at-back clamp
behavior, or remove the test while retaining the span guard as defensive code.
---
Nitpick comments:
In `@src/hangar_sim/CMakeLists.txt`:
- Around line 11-12: Update the slip_aware_odom target declaration to require
C++17, matching the existing amcl_odom_gate target configuration, so
src/slip_aware_odom.cpp can use std::clamp consistently across toolchains.
In `@src/hangar_sim/src/amcl_odom_gate.cpp`:
- Line 115: Replace the wall-clock timer assigned to timer_ in the constructor
with an rclcpp timer created using get_clock() and an rclcpp::Duration derived
from kPubPeriod, while preserving the existing publish() callback.
In `@src/hangar_sim/src/slip_aware_odom.cpp`:
- Around line 66-69: Expose kHoldGap and kHoldPeriod as configurable node
parameters, and use the resolved values in the silent-source hold logic instead
of fixed constants. Preserve the current defaults (0.015 and 0.02 seconds) so
existing behavior remains unchanged unless parameters are explicitly overridden.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: d46e613a-c8d7-412f-b899-1395492dfccf
📒 Files selected for processing (16)
src/hangar_sim/CMakeLists.txtsrc/hangar_sim/config/control/picknik_ur.ros2_control.yamlsrc/hangar_sim/config/fuse/fuse.yamlsrc/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacrosrc/hangar_sim/description/ur5e_ridgeback.xmlsrc/hangar_sim/include/hangar_sim/amcl_odom_gate_logic.hppsrc/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.pysrc/hangar_sim/objectives/navigate_to_clicked_point.xmlsrc/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xmlsrc/hangar_sim/package.xmlsrc/hangar_sim/params/nav2_params.yamlsrc/hangar_sim/script/odom_world_drift.pysrc/hangar_sim/src/amcl_odom_gate.cppsrc/hangar_sim/src/amcl_odom_gate_logic.cppsrc/hangar_sim/src/slip_aware_odom.cppsrc/hangar_sim/test/test_amcl_odom_gate.cpp
💤 Files with no reviewable changes (2)
- src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
- src/hangar_sim/objectives/navigate_to_clicked_point.xml
3dd5f34 to
6a0255e
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/hangar_sim/description/ur5e_ridgeback.xml`:
- Around line 218-221: Update the base_gt site’s rgba value to use a visible
alpha while preserving its red color and existing position, size, and TF
behavior.
In `@src/hangar_sim/params/nav2_params.yaml`:
- Around line 56-58: Update the AMCL likelihood-field weights in the parameter
block so the active values z_hit and z_rand sum to 1.0, increasing one by 0.05;
keep z_short and z_max unchanged because they are inactive for this laser model.
In `@src/hangar_sim/src/slip_aware_odom.cpp`:
- Around line 91-108: Replace the wall-clock timer creation in SlipAwareOdom’s
constructor with a node-clock/ROS-time timer so the hold-gap logic follows
simulated time. Apply the same timer change in amcl_odom_gate.cpp for its 30 Hz
publishing and stale-check timers; update both affected sites, preserving their
existing periods and callbacks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c9118a06-f1cf-43b6-ad24-2a778c5bae8e
📒 Files selected for processing (16)
src/hangar_sim/CMakeLists.txtsrc/hangar_sim/config/control/picknik_ur.ros2_control.yamlsrc/hangar_sim/config/fuse/fuse.yamlsrc/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacrosrc/hangar_sim/description/ur5e_ridgeback.xmlsrc/hangar_sim/include/hangar_sim/amcl_odom_gate_logic.hppsrc/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.pysrc/hangar_sim/objectives/navigate_to_clicked_point.xmlsrc/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xmlsrc/hangar_sim/package.xmlsrc/hangar_sim/params/nav2_params.yamlsrc/hangar_sim/script/odom_world_drift.pysrc/hangar_sim/src/amcl_odom_gate.cppsrc/hangar_sim/src/amcl_odom_gate_logic.cppsrc/hangar_sim/src/slip_aware_odom.cppsrc/hangar_sim/test/test_amcl_odom_gate.cpp
💤 Files with no reviewable changes (2)
- src/hangar_sim/objectives/navigate_to_clicked_point.xml
- src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
c055aa7 to
1ea32dc
Compare
Merge v10.0 into main
…ring Each wheel approximated its rollers with 8 spheres on a 59.4 mm ring, so the effective rolling radius swung 4.52 mm (6%) eight times per revolution. That polygon drove the whole base: front_rocker swung 1.9 deg peak-to-peak against its own 0.11 deg range, and the camera — rigid on a chassis with no roll or pitch DOF — was shaken in-plane instead. Going to 16 rollers quarters the polygon error. Total roller mass per wheel is unchanged. Contact solref goes 0.002 -> 0.016 s, which is the 2*timestep MuJoCo wants at the 0.008 s step this scene runs. front_rocker's range/stiffness/damping clamp is reverted: it was added in 98a1f5f to suppress this rocking and is unnecessary once the wheels are round. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
16 rollers removed the judder but doubled the ground-contact count (ncon 64 -> 135), and the extra contact-solve time starves controller mode-switching: 6 surface-following objectives died on "Current goal cancelled during deactivate transition". 12 rollers with condim=3 costs less (ncon 115) and keeps most of the benefit, but still fails 5 of those objectives against a clean 98/98 baseline. Recorded so the trade is visible; this is not yet shippable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The earlier 12- and 16-roller attempts kept the original 16.5 mm sphere
on the original 59.4 mm ring, so the rollers overlapped and every one
collided with its neighbours: 48 spurious roller-roller contacts per
robot, contact count 64 -> 115, and the extra solve time starved
controller mode-switching (5-6 objectives failed).
Sizing the sphere to sit tangent to its neighbours instead
(r = a*sin(pi/N), outer radius held at 75.9 mm) removes that entirely.
20 rollers now costs 9 extra contacts, not 51:
contacts rocker p2p base yaw std camera jitter
8 (main) 64 1.729 deg 1.490 deg/s 17.60 mm
20 tangent 73 0.042 deg 0.084 deg/s 5.46 mm
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The 3% non-overlap clearance was applied to the sphere radius after the ring radius had been solved, so a + r came out at 75.5918 mm — 0.31 mm (0.41%) under the original wheel, an unintended change to ride height and rolling radius. Folding the clearance into the tangency condition instead (a = OUT / (1 + 0.97*sin(pi/N))) holds a + r at 75.9000 mm exactly and keeps the same 3% margin. Behaviour is unchanged: yaw std 0.071 deg/s and camera jitter 6.02 mm, against 1.490 deg/s and 17.60 mm on main. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The comment said only that overlapping rollers self-collide, which read as if the shipped 20-roller layout overlapped. Tangency is the boundary between two failure modes, so name both: bigger spheres self-collide, smaller ones leave gaps that vary the rolling radius. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…submodule Update Phoebe dependency after licensing remediation
…subtree Running "ML Move Boxes to Loading Zone" emitted two deprecation warnings on every cycle, both from the subtree it calls. Swap AddPoseStampedToVector for PushBackVector, remapping `input` to `element` and splitting `vector` into `input_vector`/`output_vector` on the same blackboard key so the ForEach still accumulates. ResetPoseStampedVector already clears the vector ahead of the loop. On PlanCartesianPath, fold the deprecated velocity_scale_factor, acceleration_scale_factor, and trajectory_sampling_rate ports into the trajectory_timing port. All three values carry over unchanged, so the planned lift is timed exactly as before. Fixes PickNikRobotics/BCR_platform_mirror#27 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merge v10.0 into main
…-judder fix(hangar_sim): round out the mecanum wheels so the base stops juddering
Drops ~520 MB from a `--recurse-submodules` clone. Nothing in this workspace consumed the package: no `exec_depend`, no `model_package="moveit_pro_clipseg"` port. Text-prompt segmentation moved to SAM3 (`GetMasks2DFromExemplar`), and the `GetMasks2DFromTextQuery` Behavior that used the CLIPSeg models no longer exists in MoveIt Pro. Also corrects two stale CLIPSeg references left behind by the SAM3 move. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Merge v10.0 into main
Give hangar_sim's mobile base a realistic localization stack so beluga_amcl is exercised against fuse-fused odometry (wheel + IMU) with real drift, including where the scene is degenerate for scan matching (the smooth fuselage, the unmapped picking boxes). - Fuse on by default (use_fuse=true) -> /odom_filtered; odom_world_drift injects a live odom->world from the fuse estimate so AMCL sees drift to correct while world->base stays ground truth for whole-body planning. - slip_aware_odom (new C++ node): grows wheel-yaw covariance during sustained in-place spin (mecanum roller slip) so fuse defers to the IMU while spinning and trusts the wheels driving straight; also re-anchors odom across the whole-body<->nav controller handoff. - AMCL tuning: OmniMotionModel; alpha1 0.1->0.4; update_min_a 0.1 with resample_interval 3; likelihood relaxed (sigma_hit 0.1->0.25, z_hit 0.9->0.65, z_rand 0.1->0.3) so unmapped boxes read as outliers instead of yanking the pose. - fuse lag_duration 0.5->0.3: shorter smoother window cuts output latency (moving-yaw 0.70->0.465deg) while still smoothing transient spikes. - amcl_odom_gate (new C++ node): sole map->odom publisher (AMCL tf_broadcast false). Holds the last good map->odom and coasts on fuse odom where AMCL is degenerate, blending back when trustworthy. A large correction is accepted only if it persists over a sliding window (gated on position AND yaw) with particle-spread hysteresis; spread_accept_max additionally rejects a confident-but-WRONG lock (which persists yet stays spread) so a scan-slide divergence is coasted through while a real recovery (which converges) is still adopted. latency_compensation_sec composes the correction with odom->base from one estimator-lag ago (referenced to the cloud stamp), forward-projecting it with real buffered odometry -> moving-pose error 13.8->8.2cm, moving-yaw 1.2->0.9deg, no overshoot on turns; 0 disables, hangar_sim uses 0.30. The pure decision logic (detail::updateGate, interpolateOdom, appendOdomSample) has no ROS/TF deps and is fully unit-tested. - Removed the per-objective SetInitialPose reseed from the clicked-point Objectives (superseded by slip_aware_odom re-anchoring; the unconditional reseed could cement a drifting estimate). - wz_max 0.6 (below the velocity_smoother cap so it binds) keeps spins within AMCL's correction bandwidth; odom_rate 50Hz / tf_publish_rate 30Hz and the broadcaster rates decoupled from the control loop give the stack CPU headroom. Closes #19667. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eness guards Extracts slip_aware_odom's re-anchor/covariance-ramp logic into a pure, unit-tested header (mirroring amcl_odom_gate's existing pattern), and adds staleness guards flagged by review: odom_world_drift now withholds odom->world when /odom_filtered goes stale instead of broadcasting a frozen estimate with a fresh timestamp; amcl_odom_gate now treats a stale odom buffer as unavailable (with a dedicated warning) instead of silently clamping to an old sample; and slip_aware_odom's hold() now stops (and warns) after a bounded gap instead of fabricating "parked" odometry forever if a controller dies. Routes the three new nodes' logs to console (output=both) so their watchdog warnings are actually visible, and drops internal issue-number references from code comments per .claude/rules/cpp-style.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… site The base_gt site reported a true world pose only because MuJoCo parented sites to the scene root. Sensor frames now descend from the robot's own links, so the site reads as identity against the base and no longer carries ground truth. world -> ridgeback_base_link already is ground truth: robot_state_publisher builds it from the MJCF's own slide and hinge joints, in absolute simulation coordinates and independent of fuse/AMCL.
odom_qos_relay.py published /odom on /odom_reliable with RELIABLE QoS so fuse could consume it. Nothing in the tree subscribes to /odom_reliable (verified by a repo-wide grep: the publisher on line 74 was the only reference), and since moveit_pro#21948 the upstream /odom publisher is RELIABLE anyway, so the QoS bridge it provided is redundant as well as unused. Removes only the odometry half. The IMU and cmd_vel relays in the same node are still live and are left untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019N5Dz4fsgVHcrHHXrovMfz
kinematics.wheels_radius was 0.0666 in both the whole-body and the body-frame mecanum controllers, ~11.9% below the wheels' true rolling radius. The tread is a ring of 20 spheres of r=0.010 centred at a=0.0659002081. Rolling on that ring traces the convex hull of the spheres — 20 tangent segments of 2*a*sin(pi/20) plus arcs summing to one circle of radius r — giving a rolling radius of (20*2*a*sin(pi/20) + 2*pi*r) / (2*pi) = 0.07563. This is deliberately not the 0.0759 outer radius a+r, which is the static ride height PR #882 uses. The controller divides commanded body velocity by this constant, so the base drove ~13.6% faster than commanded; wheel odometry multiplies by it, so it under-reported distance by ~11.9% — about 0.72 m over a 6 m leg, the dominant source of the localization drift this stack was built to absorb. Valid only against main's rebuilt 20-roller wheels; the older 8-roller geometry had a different answer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0124EJ6ebbmvMJMjyMYPnWHD
…ift values The alphas were inflated ~2x in this PR to spread the particle cloud wide enough to cover odometry that under-reported distance by ~11.9%, with alpha1 pushed to 0.4 specifically to hold yaw lock through spins. Both were compensation for the wheel rolling radius being 11.9% low, which the previous commit corrects. With the cause fixed the compensation is no longer earned, so alpha1..alpha5 go back to main's 0.05/0.05/0.1/0.05/0.05. They should be re-checked once the base has actually been driven at its corrected speed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0124EJ6ebbmvMJMjyMYPnWHD
f2bea8b to
0662c5b
Compare
The hangar_sim mecanum wheels are sphere rings, so ride height and rolling radius are different numbers derived from the same geometry, and using one where the other belongs shows up as localization drift rather than as an obvious error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0124EJ6ebbmvMJMjyMYPnWHD
|
|
|
[written by AI]
Closes #19667.
needs: moveit_pro/#21443
Sensor frames in
hangar_simare declared as MJCF sites, and MuJoCo parented every siteto the scene root. That routes a lookup of a camera frame through the localization chain
this PR makes live, so the wrist camera inherited the localization error while the robot's
own links stayed at their simulated pose — the cause of the two
Plan Path Along Surfacefailures. moveit_pro#21443 parents sites to the robot link that carries them.
Problem
hangar_sim's mobile base drove on raw MuJoCo odometry — too clean to exercise the real localization stack. To represent a real Ridgeback, the base needs fuse-fused odometry (wheel + IMU) with realistic drift, and beluga_amcl must localize reliably against it during Navigate to Clicked Point — including where the scene is degenerate for scan matching (the smooth fuselage, the unmapped picking boxes).Approach
use_fuse=true): fuse fuses wheel odometry + IMU into/odom_filtered.odom→worlddrift injection: with fuse on,odom_world_driftpublishes a liveodom→worldtransform from the fuse estimate, so AMCL sees real drift to correct whileworld→basestays ground truth for whole-body planning.odom_topicstays on raw/odom— no manual swap.slip_aware_odom, new C++ node): republishes wheel odometry with a yaw covariance that grows during sustained in-place spin (mecanum rollers slip), so fuse defers to the IMU while spinning and trusts the wheels when driving straight.OmniMotionModel;alpha10.1→0.4 to stop yaw lock-loss during spins;update_min_a0.1 withresample_interval3 to correct often without particle depletion.sigma_hit0.1→0.25,z_hit0.9→0.65,z_rand0.1→0.3 so the unmapped picking boxes read as outliers instead of yanking the whole estimate — the box-divergence axis the fuse fix alone didn't touch.fusesmoother window (lag_duration0.5→0.3): a shorter fixed-lag window cuts the estimator's output latency so the fused pose tracks the truth more tightly during motion — moving-yaw error 0.70°→0.465° — while staying long enough to smooth transient spikes (0.25 reached a marginally lower median but reintroduced a rare ~125° transient).amcl_odom_gate, new C++ node): where AMCL's scan-match goes degenerate — the base hugging the smooth fuselage (slide-along ambiguity) or crossing the transient unmapped boxes — it holds the last goodmap→odomand coasts on fuse's odometry through the zone, then blends back once AMCL is trustworthy again. A large correction is neither hard-accepted nor hard-rejected: it is accepted only if it persists over a sliding window (a real fix persists at one pose; an ambiguity teleport thrashes), gated on both position and yaw, with particle-spread hysteresis as a second, independent trigger — so a valid large correction such as recovering from a bad initial-pose seed still gets through. A confident-but-wrong lock is the hard case the sliding window alone cannot catch: sliding along the smooth fuselage, AMCL settles at a wrong pose that also persists, with a severely spread cloud.spread_accept_maxcloses that gap — a persisted correction is accepted only while the cloud is tight enough, so a real recovery (which converges) is still adopted while a scan-slide divergence (which stays spread) is coasted through instead. AMCL runstf_broadcast:=false; the gate is the solemap→odompublisher. The pure decision logic (detail::updateGate) has no ROS/TF deps and is unit-tested for every case (transparent tracking, thrashing/yaw teleports held, persistent correction accepted, severe-spread wrong-lock rejected, spread hysteresis, SE(2) math).latency_compensation_seconamcl_odom_gate): during motion themap→baseestimate trailed the truth by ~14 cm — not the gate's smoothing but AMCL's own scan-processing lag (~300 ms), which its message stamp under-reports. The gate now buffersodom→basehistory and composes AMCL's correction with the odometry from one lag ago (referenced to the cloud's own stamp), forward-projecting it to the present using real buffered odometry (so it cannot overshoot on sharp turns).0reproduces the old compose-at-latest behaviour exactly;hangar_simsets 0.30, tuned to the measured lag (re-measure per platform). A backward sim-clock jump flushes the buffer so a reset cannot poison it.SetInitialPosereseed from the clicked-point Objectives: superseded byslip_aware_odom's cross-controller re-anchoring (which keepsodomcontinuous across the whole-body↔nav handoff it was added for), and the unconditional reseed could cement a drifting estimate.wz_max0.6 — below the velocity_smoother's 1.0 cap so it actually binds — keeps commanded spins within AMCL's correction bandwidth so the map tracks instead of lagging.odom_rate50 Hz,tf_publish_rate30 Hz, and the broadcasterupdate_rates decoupled from the high-rate control loop, giving the localization stack headroom.Results
On a fresh sim with adequate CPU, a 40-goal aggressive-turning route reaches 40/40 goals at ~0.12 m ATE with no divergence, reproducibly across runs. The map still lags slightly during fast turns (inherent AMCL correction latency), but Navigate to Clicked Point reaches every goal.
The degeneracy gate targets the two failure modes that remained: on a stress test that drives a goal 0.3 m from the smooth fuselage (deep in costmap inflation), bare
likelihood_fielddiverges and never recovers on ~75% of attempts; with the gate that drops to ~10% transient failures that self-recover within 1–2 attempts. The box cluster is contained with no strand. The gate assumes odometry is trustworthy for the (transient) duration of a degenerate zone — a bound documented in the node.Latency compensation cut the residual in-motion error further: on the fuselage route, moving-pose error dropped 13.8→8.2 cm and moving-yaw 1.2→0.9° (3-run medians, tuning
latency_compensation_sec), with at-rest error and objective success unchanged and the wrong-lock protection intact. A long soak (252 goals across the hangar) ran with zero failures.Localization quality is CPU-bound: a co-scheduled second sim can starve AMCL into divergence even with this config, so run one sim per host when benchmarking.
Docs
Paired documentation PR: PickNikRobotics/moveit_pro#20578 (Localization Tuning + Whole-Body Mobile Architecture guides).
Manual verification
ros2 launchhangar_simand run Navigate to Clicked Point to a goal that requires a large heading change; the robot reaches the clicked point.use_fuse:=falsefalls back to raw odometry.Release notes
Enhancement:
hangar_simnow localizes its mobile base with fuse odometry (wheel + IMU) and tuned beluga_amcl, with a degeneracy-aware gate that keeps localization stable where the scan match is ambiguous (the smooth fuselage and unmapped boxes) and compensates the estimator's motion latency so the map tracks the base more tightly while driving, so Navigate to Clicked Point reaches goals reliably during simulated navigation.