Skip to content

feat: hangar_sim, add beluga_amcl localization with hardcoded initial…#605

Open
bkanator wants to merge 1 commit into
mainfrom
add_beluga_amcl
Open

feat: hangar_sim, add beluga_amcl localization with hardcoded initial…#605
bkanator wants to merge 1 commit into
mainfrom
add_beluga_amcl

Conversation

@bkanator

@bkanator bkanator commented Apr 30, 2026

Copy link
Copy Markdown

needs: moveit_pro/#19710

Closes PickNikRobotics/moveit_pro#18065

Adds beluga_amcl map-based localization to hangar_sim, replacing the static identity map → odom TF with laser-scan localization.

What

  • Enables beluga_amcl as the localizer when slam:=False (default localization:=True). dual_laser_merger + map_server + beluga_amcl + lifecycle_manager load into the existing nav2_container.
  • dual_laser_merger fuses the front and rear lidar scans into a single 360° /scan_merged for AMCL — with the front lidar alone, the airplane's long featureless fuselage gave the particle filter no position lock and it could diverge catastrophically.
  • AMCL uses an omnidirectional motion model (the Ridgeback is mecanum) and self-seeds at the spawn pose via the native set_initial_pose param, so the filter starts without a manual 2D pose estimate.
  • The SetInitialPose Behavior is added at the start of the clicked-point nav Objectives, re-seeding the filter at the robot's current pose each run.

Why

The old setup had no real localization (a static identity TF stood in for map → odom). beluga estimates the pose from laser scans; merging both lidars is what keeps it stable near the fuselage (validated: max particle spread 149 m front-only → 0.69 m merged, zero divergence on the same route).

Depends on

PickNikRobotics/moveit_pro#19710 — adds the SetInitialPose Behavior these Objectives use. Merge #19710 first; this PR's CI tests against #19710's image via the needs: token above.

Release notes

Enhancement: Added beluga_amcl localization to hangar_sim, replacing the static map → odom identity TF with laser-scan-based localization. AMCL consumes a merged front+rear 360° scan so the particle filter stays stable near the hangar airplane.

Claude agent checks

  • code-reviewer
  • test-runner
  • platform-architect-bot

Reviewed via the rubrics in moveit_pro's .claude/agents/. Findings applied: code-reviewer (unused log_level removed; map-existence check made unconditional), platform-architect-bot (dead launch args removed; not <bareword> launch conditions replaced with UnlessCondition/string compares so lowercase true/false no longer crash), roboticist-bot (merge geometry/frames confirmed; scan_time/laser_max_range corrected; sync queue_size raised for headroom). Copilot findings applied (same gating fixes). test-runner: example_ws integration tests run against #19710's image via the needs: token.

@bkanator bkanator force-pushed the add_beluga_amcl branch from af4465b to 23bb875 Compare May 5, 2026 12:12
@bkanator bkanator marked this pull request as draft May 5, 2026 14:17
@bkanator bkanator marked this pull request as draft May 5, 2026 14:17
@bkanator bkanator force-pushed the add_beluga_amcl branch 2 times, most recently from 9a9d7a0 to 308ed11 Compare May 12, 2026 17:07
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR enables beluga_amcl-based localization in simulation. It updates the localization launch, parent launch wiring, AMCL parameters, runtime dependencies, and navigation objectives to set an initial pose in ridgeback_base_link.

Changes

Localization feature enablement

Layer / File(s) Summary
Localization launch composition
src/hangar_sim/launch/sim/localization_launch.py
Removes non-composable launch paths, adds map file validation, and loads nav2_map_server and beluga_amcl as composable nodes with lifecycle management based on localization.
Parent launch wiring
src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
Adds the localization launch argument, passes it into the localization launch, and changes the static map->odom fallback to require both slam and localization to be false.
AMCL config and dependency
src/hangar_sim/params/nav2_params.yaml, src/hangar_sim/package.xml
Activates the AMCL parameters and updates localization-specific fields, while adding runtime dependencies for beluga_amcl and dual_laser_merger.
Initial pose in navigation objectives
src/hangar_sim/objectives/navigate_to_clicked_point.xml, src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
Adds a SetInitialPose action at the start of both navigation objective sequences.

Possibly related issues

  • #16487 — Directly matches the beluga_amcl localization integration, launch wiring, and nav2 configuration changes in this PR.

Suggested reviewers

  • griswaldbrooks

Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Human Review Check ❌ Error This is a cross-cutting localization feature: launch files, Nav2 params, package deps, and BT objectives all change. This PR requires review by a requested human reviewer. After review, a non-author requested reviewer should override this pre-merge check.
Linked Issues check ⚠️ Warning The PR implements beluga_amcl localization, but it appears to miss the required workspace dependency management for beluga from #18065. Add beluga to workspace dependency management (.repos or rosdep) and confirm the launch still resolves beluga_amcl end-to-end.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly matches the localization changes and objective of adding beluga_amcl to hangar_sim.
Out of Scope Changes check ✅ Passed The changes stay focused on localization, Nav2 params, TF handling, and related objectives with no clear unrelated edits.

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

@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.

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)

329-337: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix duplicate condition keyword in static_tf_map_to_odom Node call (parse-time failure).

static_tf_map_to_odom = Node(...) passes condition= twice (lines 329-337), which prevents the launch file from compiling/executing. Remove the second condition.

Suggested fix
     static_tf_map_to_odom = Node(
         condition=IfCondition(
             PythonExpression(["not ", slam, " and not ", localization])
         ),
         package="tf2_ros",
         executable="static_transform_publisher",
         name="static_tf_map_to_odom",
         output="log",
         arguments=["0.0", "0.0", "0.0", "0.0", "0.0", "0.0", "map", "odom"],
-        condition=IfCondition(PythonExpression(["not ", slam])),
     )
🤖 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 329 - 337, The Node call that creates static_tf_map_to_odom passes the
condition= keyword twice causing a parse error; edit the Node(...) for
static_tf_map_to_odom to remove the duplicate condition argument (keep the
intended condition IfCondition(PythonExpression(["not ", slam, " and not ",
localization])) and delete the later
condition=IfCondition(PythonExpression(["not ", slam]))). Ensure only one
condition kwarg exists on the static_tf_map_to_odom Node to restore valid launch
file syntax.
🤖 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/localization_launch.py`:
- Line 60: Remove the unused LaunchConfiguration assignment by deleting the
log_level = LaunchConfiguration("log_level") line (remove the symbol log_level
and its LaunchConfiguration call) so the unused variable no longer triggers
F841; ensure no other references to log_level remain and run lint to confirm the
warning is resolved.
- Around line 82-83: The gate compares
context.perform_substitution(localization) to the exact string "True", which
fails for "true"/" TRUE"/etc.; normalize the substituted localization value
before comparing (e.g., call .strip().lower() on the result) so the check uses a
boolean-safe comparison in the if statement that currently references
localization; update that condition in localization_launch.py to compare the
normalized string to "true".

---

Outside diff comments:
In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py`:
- Around line 329-337: The Node call that creates static_tf_map_to_odom passes
the condition= keyword twice causing a parse error; edit the Node(...) for
static_tf_map_to_odom to remove the duplicate condition argument (keep the
intended condition IfCondition(PythonExpression(["not ", slam, " and not ",
localization])) and delete the later
condition=IfCondition(PythonExpression(["not ", slam]))). Ensure only one
condition kwarg exists on the static_tf_map_to_odom Node to restore valid launch
file syntax.
🪄 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: 00862f77-0187-4d32-8292-dec8e39708d2

📥 Commits

Reviewing files that changed from the base of the PR and between 87f07b8 and b18a6a3.

📒 Files selected for processing (4)
  • src/hangar_sim/launch/sim/localization_launch.py
  • src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
  • src/hangar_sim/package.xml
  • src/hangar_sim/params/nav2_params.yaml

Comment thread src/hangar_sim/launch/sim/localization_launch.py Outdated
Comment thread src/hangar_sim/launch/sim/localization_launch.py Outdated
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@bkanator

bkanator commented Jun 9, 2026

Copy link
Copy Markdown
Author

here are initial videos with beluga integrated with ground truth odom but not tuning.

first is a successful run, and the second is showing failure with getting near the plane.

particle_first-2026-06-09_09.52.29.mp4
particle_fail-2026-06-09_09.47.02.mp4

@bkanator

Copy link
Copy Markdown
Author

Todo: fix reset of particles after failure, tune resampling weights.

@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@bkanator

Copy link
Copy Markdown
Author

Here is an updated working video:

good_particles-2026-06-24_10.31.26.mp4

@bkanator

Copy link
Copy Markdown
Author

[written by AI]

Stress-testing localization stability. beluga_amcl stays converged in normal navigation (mean particle spread ~0.3 m, zero divergence across multiple multi-minute runs). We hit one intermittent catastrophic divergence (map→odom jumped to ~1e18 m) navigating near the hangar airplane, where the front-only lidar sees a long featureless fuselage. Now running automated nav stress loops with particle-spread monitoring to reproduce; the likely fix is merging the front+rear lidar scans for AMCL.

@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@bkanator

Copy link
Copy Markdown
Author

and after found stress-test failure, then started using front and back merged lidar, some runs near fuselage.

dual_good_particles-2026-06-25_12.52.30.mp4

@bkanator

Copy link
Copy Markdown
Author

[written by AI]

Dual-lidar fix — near-airplane localization

AMCL now consumes a merged front+rear 360deg scan (dual_laser_merger) instead of the front lidar alone. Validated on a fuselage-hugging route that previously diverged:

AMCL scan source Max particle spread
Front lidar only 149 m (diverged)
Front + rear merged 0.69 m (stable)

@bkanator bkanator added this to the 10.0.0 milestone Jun 25, 2026
@bkanator bkanator force-pushed the add_beluga_amcl branch 3 times, most recently from 241e7c7 to 261a4be Compare June 25, 2026 17:59
@bkanator bkanator requested a review from Copilot June 25, 2026 18:00
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 25, 2026
@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

Copilot AI 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.

Pull request overview

Adds map-based localization to hangar_sim by integrating beluga_amcl (optionally enabled via a new localization launch argument) and wiring it into the existing Nav2 container setup, along with initial seeding support from Objectives.

Changes:

  • Enabled AMCL configuration in nav2_params.yaml (beluga) with initial pose seeding and updated scan topic.
  • Added beluga_amcl + dual_laser_merger to the localization launch path, with a localization toggle and a static map->odom fallback when disabled.
  • Added SetInitialPose at the start of the clicked-point navigation Objectives.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/hangar_sim/params/nav2_params.yaml Introduces beluga AMCL parameters (including initial pose seeding) and points AMCL at /scan_merged.
src/hangar_sim/package.xml Adds runtime dependencies needed for beluga AMCL and scan merging.
src/hangar_sim/objectives/navigate_to_clicked_point.xml Adds SetInitialPose at the start of the clicked-point navigation Objective.
src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml Adds SetInitialPose at the start of the replanning variant of the clicked-point navigation Objective.
src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py Adds localization argument, forwards it to localization launch, and gates the static map->odom TF fallback.
src/hangar_sim/launch/sim/localization_launch.py Loads dual_laser_merger, map_server, beluga_amcl, and lifecycle manager into nav2_container when localization is enabled; otherwise loads map_server only.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +78 to +89
def check_map_exists(context):
# Match IfCondition's accepted truthy values (true/1) so this guard
# stays consistent with the nodes gated on `localization` below.
if context.perform_substitution(localization).lower() not in ("true", "1"):
return []
map_file = context.perform_substitution(map_yaml_file)
if not os.path.exists(map_file):
raise RuntimeError(
f"Map file not found: {map_file}\n"
"Run SLAM first (slam:=True) to build a map, or provide a map path via map:=<path>."
)
return []
# Load map_server only (no AMCL) when localization is disabled.
# A static map->odom TF is expected from the parent launch in this case.
load_map_server_only = LoadComposableNodes(
condition=IfCondition(PythonExpression(["not ", localization])),
# Static map->odom TF fallback: only used when neither SLAM nor AMCL is publishing it.
static_tf_map_to_odom = Node(
condition=IfCondition(
PythonExpression(["not ", slam, " and not ", localization])
_favorite="true"
>
<Control ID="Sequence" name="TopLevelSequence">
<Action ID="SetInitialPose" robot_frame_id="ridgeback_base_link" />
_favorite="true"
>
<Control ID="Sequence" name="TopLevelSequence">
<Action ID="SetInitialPose" robot_frame_id="ridgeback_base_link" />
@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@bkanator bkanator marked this pull request as ready for review June 25, 2026 18:24

@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.

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/objectives/navigate_to_clicked_point.xml`:
- Line 10: The SetInitialPose action in navigate_to_clicked_point.xml should
only run when AMCL is active, since robot_drivers_to_persist_sim.launch.py and
localization_launch.py do not always start beluga_amcl. Guard or scope the
SetInitialPose step in the navigate_to_clicked_point objective so it is skipped
for slam:=True or localization:=False runs, and keep the change aligned with the
existing localization configuration flow.
🪄 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: 7fccad2a-ba3b-4659-9d17-1b43765a6b21

📥 Commits

Reviewing files that changed from the base of the PR and between b18a6a3 and ad53140.

📒 Files selected for processing (6)
  • src/hangar_sim/launch/sim/localization_launch.py
  • src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
  • src/hangar_sim/objectives/navigate_to_clicked_point.xml
  • src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
  • src/hangar_sim/package.xml
  • src/hangar_sim/params/nav2_params.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py

Comment thread src/hangar_sim/objectives/navigate_to_clicked_point.xml
@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@griswaldbrooks

Copy link
Copy Markdown

Approve but waiting for https://github.com/PickNikRobotics/moveit_pro/pull/19710

Adds beluga_amcl for map-based localization on the Ridgeback mecanum base.
AMCL consumes a merged front+rear 360-degree scan (dual_laser_merger): with the
front lidar alone, the hangar airplane's long featureless fuselage gives the
particle filter no position lock and it can diverge catastrophically; the rear
lidar supplies the structure the front cannot see. AMCL uses an omnidirectional
motion model (diff-drive mis-models strafing) and self-seeds at the spawn pose
via set_initial_pose. The SetInitialPose Behavior re-seeds at the robot's
current pose at the start of each navigation Objective.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@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.

3 participants