Skip to content

Fix: Make robot_state_publisher the sole owner of the ridgeback_base_link transform in hangar_sim#756

Merged
griswaldbrooks merged 1 commit into
mainfrom
fix/18174-tf-single-owner-base-link
Jul 10, 2026
Merged

Fix: Make robot_state_publisher the sole owner of the ridgeback_base_link transform in hangar_sim#756
griswaldbrooks merged 1 commit into
mainfrom
fix/18174-tf-single-owner-base-link

Conversation

@griswaldbrooks

Copy link
Copy Markdown

[written by AI]

Fixes the remaining half of PickNikRobotics/moveit_pro#18174. Part of epic PickNikRobotics/moveit_pro#17431.

needs: moveit_pro/#20258

Depends on PickNikRobotics/moveit_pro#20258 (adds the base_link_name hardware parameter). Against a moveit_pro image without it, the new xacro param is silently ignored, the lidar-chain half of the conflict remains, and the new single-parent integration test fails — the needs: token above makes CI run against that PR's image. Do not merge until #20258 is merged and a fresh moveit_pro main image is published, or the first post-merge main CI run here will use an image without the parameter.

Motivation

ridgeback_base_link had three concurrent TF parents in hangar_sim: the MuJoCo lidar fill-in chain (base_platform →, 2×60 Hz), robot_state_publisher (virtual_rail_link_2 →, 20 Hz), and the MuJoCo odom TF (odom →, 150 Hz; fuse when enabled). tf2 and the web UI keep last-writer-wins, so the frame flipped between poses disagreeing by 48 mm at rest (a real model mismatch) and up to 61 mm in motion — the robot visibly oscillated below base_link whenever the UI's fixed frame sat at or below it. #18174 de-duplicated only the odom edge, and only in the opt-in fuse mode.

Brief description

Makes robot_state_publisher the sole owner of the live transform into ridgeback_base_link. Its virtual-rail chain is the one publisher that cannot be turned off per-edge (the joints are required for whole-body planning), and in sim it is already ground truth — the base's x/y/yaw are literal MuJoCo joints streamed through joint_state_broadcaster. Every other subsystem now expresses the base pose as data, not TF:

  • xacro: base_link_name: ridgeback_base_link stops the MuJoCo lidar fill-in chain at the base (lidar mounts hang off it as plain children); odom_publish_tf: false keeps /odom messages flowing for Nav2 but drops the competing TF edge; the dead odom_zero_z tag (never a recognized parameter — silently ignored) is replaced by the real odom_planar.
  • launch: the mj_world → world static becomes odom → world. The odom frame previously existed only because MuJoCo broadcast the competing edge; anchoring world under odom gives REP-105 semantics — beluga_amcl's live map → odom correction shifts the whole robot subtree, and its own odom → base lookups resolve through the static. The #18174 launch guard is removed: it enforced the old ownership model and, when fuse was on, forced publish_odom: false, which also killed the /odom messages Nav2 reads (odom_topic: /odom).
  • fuse: publish_tf: false. Nothing consumes odom_filtered today; the estimate stays on the topic. On hardware, the estimator feeds the virtual-rail joint states (odometry-to-joint-state bridge) rather than TF — ownership of the edge never moves.
  • URDF: the virtual-rail origin gets z = -0.048, matching the MJCF base_platform_rotation anchor (the sim base has no z DOF; that offset is what puts the wheels in floor contact). Previously the RSP chain rendered the robot 48 mm above where the physics and every sensor frame actually put it.
  • test: new integration test asserts ridgeback_base_link has exactly one TF parent (virtual_rail_link_2), so a re-enabled competing publisher fails CI instead of reappearing as UI jitter.
  • docs: transform-allocation table, frame-hierarchy diagram, and production-deployment notes in NAV2_AND_WHOLE_BODY_PLANNING_ARCHITECTURE.md updated to the new tree.

Known out-of-scope: phoebe_sim.urdf.xacro uses the same dead odom_zero_z tag (vendored dependency); the architecture doc's executive-summary data-flow diagram still shows the historical odometry bridge.

How it was tested

Live on a running hangar_sim stack (dev container, moveit_pro built from the paired branch):

  • /tf attribution over 4 s windows: exactly one parent for ridgeback_base_link (virtual_rail_link_2, ~20 Hz, co-published edges identify robot_state_publisher) — at rest and during motion. Before the change: three parents at ~150 flips/s.
  • Solution - Move Forward 2m objective SUCCEEDED (whole-body pipeline: MoveIt → JTC → chained mecanum controller); base x: −0.08 → 1.65 with z pinned at −0.048 throughout.
  • beluga_amcl bootstraps and publishes map → odom live on the new topology; odom → ridgeback_base_link resolves (z = −0.048, matching physics); local costmap publishes; /scan_front at 10 Hz; zero TF warnings (message filter, TF_OLD_DATA, TF_REPEATED_DATA) in the backend log.
  • Launch file and integration test pass ast.parse; colcon build of hangar_sim succeeds.

Release notes

  • Bug Fix: Fixed the robot model oscillating in the web UI 3D view in hangar_sim when the fixed frame was base_link or below, caused by multiple simulation publishers competing over the mobile base's transform.

Agent checks (this repo has no PR template; statuses mirror the moveit_pro convention): code-reviewer run (cross-repo dependency called out above; stale comments, doc sweeps, and a regression test applied from its findings) · documentation-bot run (3 stale-corrections in NAV2_AND_WHOLE_BODY_PLANNING_ARCHITECTURE.md applied) · platform-architect-bot run (CI ordering finding addressed by the needs: token + merge-order note; stale xacro comment fixed; /odom planar assertion added to the integration test) · roboticist-bot run (no required changes; −0.048 z verified kinematically safe: offset sits above every IK chain, waypoints are joint-space) · test-runner: hangar_sim builds; launch file and test pass syntax checks; the integration test runs in CI against the paired image; full TF behavior live-verified on a running stack (see "How it was tested").

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Prevented conflicting odometry transforms and ensured a single authoritative transform chain for the robot base.
    • Corrected planar odometry behavior so vertical position remains at zero.
    • Improved robot and sensor frame placement in simulation.
    • Added a static odom → world transform for more consistent navigation and planning behavior.
  • Documentation

    • Expanded navigation, localization, frame hierarchy, deployment, and troubleshooting guidance.
  • Tests

    • Added integration coverage for single-parent base transforms and planar odometry output.

Walkthrough

This PR reworks simulated Ridgeback odom/TF ownership, configures planar MuJoCo odometry, replaces the static world transform, removes launch validation, aligns a robot joint offset, updates architecture documentation, and adds TF/odom integration assertions.

Changes

Odom/TF ownership refactor

Layer / File(s) Summary
MuJoCo and fuse odom/TF configuration
src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro, src/hangar_sim/config/config.yaml, src/hangar_sim/description/ur5e_ridgeback.xacro, src/hangar_sim/config/fuse/fuse.yaml
Configures planar MuJoCo odometry without competing TF publication, limits its TF chain at ridgeback_base_link, disables fuse TF publication, and documents TF ownership.
Robot description frame alignment
src/hangar_sim/description/ur5e_ridgeback.xacro
Shifts the linear_x_joint origin by -0.048 on z to align the robot-state-publisher chain and sensor frames.
Launch transform wiring and validation removal
src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
Removes the fuse odom/TF preflight check and replaces static_tf_mj_world_to_world with static_tf_odom_to_world.
Architecture documentation and integration validation
src/hangar_sim/docs/NAV2_AND_WHOLE_BODY_PLANNING_ARCHITECTURE.md, src/hangar_sim/test/objectives_integration_test.py
Updates frame hierarchy and TF ownership documentation, and adds assertions for a single ridgeback_base_link TF parent and near-zero odometry z values.

Possibly related PRs

Suggested reviewers: JWhitleyWork, marioprats

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly matches the TF ownership, xacro, launch, fuse, URDF, test, and docs changes in the patch.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Human Review Check ✅ Passed Simulation-only hangar_sim config/launch/xacro/docs/tests; no auth, public API, core behavior, or bin/scripts changes.

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

  • lab_sim
    • jazzy: no report produced — see run logs
  • hangar_sim
    • jazzy: no report produced — see run logs

@bkanator

bkanator commented Jul 8, 2026

Copy link
Copy Markdown

[written by AI]

Tested with moveit_pro#20258, fuse on. frames.gv: single parent for ridgeback_base_link (RSP virtual_rail_link_2, 20 Hz), lidars now children, no odom→base. Kills the use_fuse:=truemap→odom 1e27 runaway seen on main — stayed sane through a 40-goal drive + idle. Localization ATE max 1.39 m → 0.14 m, zero divergence.

@griswaldbrooks
griswaldbrooks force-pushed the fix/18174-tf-single-owner-base-link branch from a1c5d0f to b9e98e1 Compare July 8, 2026 18:20
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@griswaldbrooks
griswaldbrooks marked this pull request as ready for review July 8, 2026 22:59
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

1 similar comment
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report



BASE_LINK_FRAME = "ridgeback_base_link"
TF_PARENT_SAMPLE_DURATION_S = 4.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would this create a future CI flake if not high enough?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unsure is a way to wait for the first transform...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[written by AI]

Fair concern — the 4 s window was doing double duty as both "wait for the stack" and "sample the parent set", and only the second job is time-calibrated. Fixed in ffdb9da2 exactly along the lines of your second comment: the test is now two-phase. Phase 1 spins until the FIRST transform into ridgeback_base_link (and the first /odom message) arrives, bounded by a generous TF_FIRST_SIGHTING_TIMEOUT_S = 60 s that only limits startup latency on a loaded runner. Phase 2 then samples for the original 4 s to catch conflicting parents — competing publishers broadcast at 10–120 Hz, so a second parent would land hundreds of times in that window. Slow bring-up can no longer eat the sampling budget or masquerade as "wrong publishers".

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[written by AI]

There is — resolved together with the thread above: ffdb9da2 adds a first-sighting wait phase (spin until the frame appears on /tf, 60 s bound) before the 4 s parent-sampling phase.

# the estimate into the virtual-rail joint states instead of TF.
world_frame_id: 'odom'
publish_tf: true
publish_tf: false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so effectively... we would never use this in sim? but maybe on a real robot that didn't need the whole-body virtual joint paths...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[written by AI]

Right on both halves. In this config — sim or hardware — fuse's TF output stays off permanently: the whole-body virtual-rail joint chain means robot_state_publisher already owns the edge into ridgeback_base_link, and a second TF parent from fuse is exactly the conflict this PR removes. Fuse still runs and its estimate stays consumed via the odom_filtered topic; on hardware the estimate would feed the virtual-rail joint states (per the comment above this line) rather than TF.

A robot that does NOT use the whole-body virtual-joint approach — no joint path from odom to the base, so nothing else publishes that edge — is the standard fuse deployment, and there you'd set publish_tf: true and let fuse own odom → base_link as usual. So: not "never use fuse in sim", but "never let two things own the same TF edge"; which one owns it follows from whether the base pose is in the joint state.

@bkanator

Copy link
Copy Markdown

[written by AI]

Ran a fuse-on before/after with this PR + moveit_pro#20258:

ridgeback_base_link before (main) after (both fixes)
TF parents 3base_platform, odom, virtual_rail_link_2 (flipping/last-writer-wins) 1virtual_rail_link_2
map→odom broken (no path) sane

Three-publisher conflict → single clean owner with fuse enabled, no runaway.

bkanator
bkanator previously approved these changes Jul 10, 2026

@bkanator bkanator left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ran your test harness, and re-ran the failure case I had with fuse on to confirm fixes still working.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/docs/NAV2_AND_WHOLE_BODY_PLANNING_ARCHITECTURE.md (1)

893-904: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Reword this as a parent-frame conflict, not just duplicate TF publication.
robot_state_publisher owns world → … → ridgeback_base_link, while the controllers would publish odom → ridgeback_base_link; with the static odom → world link, the real problem is competing parents for ridgeback_base_link. If both controllers enable TF, they also duplicate the same odom → ridgeback_base_link edge, so keep those two cases distinct.

🤖 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/docs/NAV2_AND_WHOLE_BODY_PLANNING_ARCHITECTURE.md` around
lines 893 - 904, The architecture documentation should describe the primary
issue as competing parent frames for ridgeback_base_link: robot_state_publisher
provides world → … → ridgeback_base_link, while controllers provide odom →
ridgeback_base_link through the static odom → world link. In the Consequence of
Misconfiguration section, distinguish this parent-frame conflict from the
separate case where both platform_velocity_controller and
platform_velocity_controller_nav2 enable TF and duplicate the same odom →
ridgeback_base_link edge.
🤖 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.

Outside diff comments:
In `@src/hangar_sim/docs/NAV2_AND_WHOLE_BODY_PLANNING_ARCHITECTURE.md`:
- Around line 893-904: The architecture documentation should describe the
primary issue as competing parent frames for ridgeback_base_link:
robot_state_publisher provides world → … → ridgeback_base_link, while
controllers provide odom → ridgeback_base_link through the static odom → world
link. In the Consequence of Misconfiguration section, distinguish this
parent-frame conflict from the separate case where both
platform_velocity_controller and platform_velocity_controller_nav2 enable TF and
duplicate the same odom → ridgeback_base_link edge.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ac4cea6-7ad9-49e2-adaa-9b152f3041a0

📥 Commits

Reviewing files that changed from the base of the PR and between b9e98e1 and ffdb9da.

📒 Files selected for processing (7)
  • src/hangar_sim/config/config.yaml
  • src/hangar_sim/config/fuse/fuse.yaml
  • src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro
  • src/hangar_sim/description/ur5e_ridgeback.xacro
  • src/hangar_sim/docs/NAV2_AND_WHOLE_BODY_PLANNING_ARCHITECTURE.md
  • src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
  • src/hangar_sim/test/objectives_integration_test.py
✅ Files skipped from review due to trivial changes (1)
  • src/hangar_sim/config/config.yaml
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/hangar_sim/config/fuse/fuse.yaml
  • src/hangar_sim/description/ur5e_ridgeback.xacro
  • src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro
  • src/hangar_sim/test/objectives_integration_test.py
  • src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py

@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@griswaldbrooks
griswaldbrooks enabled auto-merge July 10, 2026 15:05
@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@griswaldbrooks
griswaldbrooks merged commit 59d74ff into main Jul 10, 2026
33 of 38 checks passed
@griswaldbrooks
griswaldbrooks deleted the fix/18174-tf-single-owner-base-link branch July 10, 2026 15:33
@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants