Skip to content

Experimental normal Nav2 observation

This optional, source-tree-only example is the first navigation increment inside M4. It links the real robot_harness_core to Humble's nav2_msgs/action/NavigateToPose. It is not an installed ROS SDK, a reusable navigation Host, or a completed M4 adapter.

The single fixed simulation goal is (-2.0, -0.5) → (0.7, -0.5). A trusted launcher must create a fresh, exclusively owned, network-isolated simulator, supply its session identity and writable /output, and mount the binary under /harness. The simulation package builds the settlement Owner and its exclusive fixture from this repository. This directory also supports the standalone optional C++ build below. The earlier observation-only binary retains its narrower contract.

Build

In Ubuntu 22.04 with ROS 2 Humble and ros-humble-nav2-msgs installed, from the repository root:

source /opt/ros/humble/setup.bash
cmake -S integrations/ros2/nav2_observation -B build-nav2 -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON
cmake --build build-nav2 --target robot_harness_nav2_observation nav2_motion_window_test --parallel 2
ctest --test-dir build-nav2 -R '^nav2_(requires_fresh_deployment|motion_window)$' --output-on-failure --no-tests=error

The separate ROS workflow compiles this target and checks the finite motion-window predicate and rejection without the deployment premise. These checks do not run Gazebo, test navigation, or establish cancellation/settlement. The normal root CMake build remains ROS-independent.

Execution boundary

  • M4_ISOLATED_SIMULATION=1, M4_FRESH_NAV2_RUN, a disposable Linux container and a one-use /output/harness-navigation.claim identify the intended test setup. These are trusted launcher assertions and an accidental-repeat guard, not authentication or an independently verified remote-server identity.
  • Readiness requires advancing clock/odometry, localization near the declared spawn, active AMCL and navigator, and the Action server. Discovery alone does not prove the absence of old work. Fresh/exclusive deployment is essential; never run against an already-used or shared Nav2 instance.
  • Core receives the declared startup/profile facts, admits one operation and claims dispatch immediately before async_send_goal on one owning thread.
  • The accepted ROS UUID is checked on feedback and result. Each callback uses the same instance-retained operation authority. Result success and a fresh map-frame pose within 0.35 m are necessary for output delivery.
  • The local result is staged, time is observed, and Core's delivery permission is checked immediately before the local in-memory commit, without callback spin or re-entrant caller code. The resulting receipt records output acceptance.
  • After output acceptance the same owner, Gate, authority, node and executor remain alive for a bounded post-result motion observation. It waits up to eight wall seconds in total for the first low-speed sample and one continuous quiet window. At least ten fresh samples must cover one simulation second; drift is at most 0.01 m, yaw change 0.02 rad, speed 0.01 m/s and angular speed 0.02 rad/s. Time gaps must not exceed 0.25 s; odometry must track the advancing clock within 0.25 s, whose last advancement must be within one wall second. Old pre-window data is ignored. Missing/invalid new data cannot pass. Once the quiet window starts, any violation rejects it without restarting. This is a finite planar observation, not ongoing supervision or a device stop guarantee.
  • No settlement evidence is supplied. The actual second admission attempt must return kDomainOccupied. Admission is closed before the example exits. The result can be accepted while settlement remains pending.

The narrow profile is nav2-normal-observation-only-v1: one fixed navigation request, not a general workload/cancellation capability. No ROS types or policy state machine were added to Core. The local result is an observation, not a promise that the robot has stopped or the controller has no remaining work.

Scenarios and limits

The trusted launcher can select normal, deny-startup, cancel-before-dispatch, or withhold-odometry. The two pre-dispatch cases exercise real Core denial and require zero native submissions; the experiment also checks negligible Gazebo movement. Missing startup evidence and revoked pre-dispatch authority must not be bypassed. The withhold-odometry case completes navigation and accepts its output, then removes the owner's odometry subscription: motion observation must fail and the domain must remain occupied. The default is one normal goal, with matching feedback/result, a successful finite motion observation and a blocked second admission. Each invocation needs its own fresh deployment/output directory.

The launcher independently reads Gazebo position before and after the invocation; the C++ result alone is not evidence of physical displacement. Timeout, native rejection/failure, invalid observations and exceptions produce an incomplete observation. The observation budget is not an operation deadline or stop bound. Destroying the client/executor does not cancel remote work. Container teardown owns the test environment; it cannot retroactively mark the Core receipt settled.

The owner is retained through this observation and then closes admission/exits; it is not yet a persistent multi-task navigation Host. Safe native rearming and old-command isolation remain prerequisites for settlement and a second explicit goal.

Motion-time cancellation, goal replacement and native settlement remain outside the default observation binary. The optional scenarios below add these bounded paths; provider loss/rebinding and restart recovery remain future work. Keep the normal observation scenario as the reference comparison.

Optional sequential settlement example

The separately named robot_harness_nav2_settlement binary extends the same Owner with the bounded A-to-B contract. The default observation binary keeps its pending receipt and refused second goal. Build the optional profile on Ubuntu 22.04/Humble with nlohmann-json3-dev and ros-humble-std-srvs / ros-humble-rosidl-default-generators in addition to the dependencies above:

source /opt/ros/humble/setup.bash
cmake -S integrations/ros2/fixture_interfaces -B build-nav2-interfaces -DCMAKE_INSTALL_PREFIX="$PWD/install-nav2-interfaces"
cmake --build build-nav2-interfaces --parallel 2
cmake --install build-nav2-interfaces
source install-nav2-interfaces/share/m4_drive_probe/local_setup.bash
cmake -S integrations/ros2/nav2_observation -B build-nav2 -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON -DROBOT_HARNESS_NAV2_SETTLEMENT=ON -DCMAKE_PREFIX_PATH="$PWD/install-nav2-interfaces"
cmake --build build-nav2 --parallel 2
ctest --test-dir build-nav2 -R '^nav2_' --output-on-failure --no-tests=error

The message-only m4_drive_probe package allows a clean compile without Gazebo. It supplies no native server, driver, simulator or settlement guarantees; do not install it alongside a different package of the same name. Licensing remains undecided for the project. These experimental fixture interfaces are not a stable public robot API. The isolated research deployment supplies matching service implementations, fixed producer bindings and one-use native task contexts.

At runtime the launcher supplies /output/producer-context.json with distinct 32-character lowercase hexadecimal a_scope and b_scope, task-bound bridges, and /prepare_next_context. B uses namespace /m4_task/s_<b_scope>; creating it must not send a goal or open the drive. The Owner independently verifies readiness and owns every Core admission, drive open and native goal send. Supported cases are normal, withhold-planner-ack, withhold-drive-ack, withhold-odometry, missing-map and missing-controller. The last two require the launcher to omit the corresponding B dependency. Normal must emit owner_handoff_result with A settled, B admitted/native closed, B unsettled and third admission blocked. Expected closure/readiness fault cases emit owner_handoff_blocked with one native send. withhold-feedback additionally exercises a result-observation failure: it exits 1 with incomplete, without A settlement or B admission. The Owner reports the error outside ROS callbacks instead of throwing through an already fulfilled Action promise.

CI compiles both binaries and checks the deployment guard, motion window and native evidence matcher. Actual navigation and closure require the separate simulation evidence described in Testing. The public repository does not yet provide an end-to-end simulator launcher.

Cancel an accepted goal while moving

The optional binary accepts cancel-moving and the three cancel-moving-withhold-planner-ack, cancel-moving-withhold-drive-ack, cancel-moving-withhold-odometry fault cases. Run them only through a fresh exclusive simulator launcher with the same fixture mounts as the sequential profile. The native FollowPath leaf must publish the accepted child identity while running; older success-only fixtures cannot establish cancellation closure.

The trigger is fresh observed movement of at least 0.5 m and speed 0.05 m/s. The Owner makes one targeted cancellation, rejects result delivery after revocation, waits for the separate native terminal/closure observations and checks a fresh quiet window. Output logs include owner_cancel_requested, owner_cancel_response, native_result, native observations and owner_cancellation_result. The final event distinguishes native_closed and motion_observed; passed in a fault case means the expected evidence was withheld, not that the robot was proven stopped.

The receipt stays pending and second admission is blocked in all four cases: this cancellation-only scenario prepares no replacement context. It does not implement cancellation during pending acceptance, runtime loss supervision or a hard stopping bound. Details are in the cancellation contract. The Humble workflow also runs nav2_cancel_response, checking exact target UUID, empty/multiple response entries, refusal and unknown/terminated-goal responses.

Replace an accepted goal while moving

replace-moving cancels A after the same motion trigger, then uses the same Owner's closure, fresh quiet, B-context readiness, Core settlement/admission and generation reopening flow. B targets (0.0, -0.5), so it must actually navigate from the cancellation position. Only A triggers automatic cancellation; B's result is delivered normally and B stays pending without a C context.

The five replace-moving- fault suffixes are withhold-planner-ack, withhold-drive-ack, withhold-odometry, missing-map and missing-controller. Each must block B admission/open/send. Normal output includes the cancellation events, owner_core_handoff (with A cancellation/output facts) and owner_handoff_result; faults end with owner_handoff_blocked.

Use the same fresh research launcher and pinned native fixture. The replacement check injects late A commands through A's raw input and retained smoother while B moves; their generation remains 1 and the drive rejects them. See the replacement contract and validation.

Lose fresh observations while navigating

The optional binary checks clock/odometry progress during each native goal and before result delivery. Two seconds without advancing valid stamps withdraws the Core binding, denies output/new admission and requests the exact accepted goal's native stop once. This uses monotonic Owner time; repeated old messages do not renew the observation. It is a fixture threshold, not a stopping bound.

The four runtime- cases are runtime-odometry-loss, runtime-odometry-replay, runtime-odometry-resume and runtime-clock-loss. Their research relay remaps only Owner inputs (/owner_odom, /owner_clock), starting the fault after real motion. All retain a withdrawn binding and pending settlement. Restored data can support quiet observation, but cannot authorize B or revive result delivery. Logs distinguish owner_runtime_observation_lost, the real native observations and owner_runtime_loss_result.

Build nav2_runtime_observation_watch_test alongside the existing targets and select ^nav2_ CTests (five checks). Humble CI includes that target. The test checks wall-time expiry, frozen/backward stamps, invalid odometry and fresh observation recovery; actual robot behavior requires the separate simulator. The native stop/closure path remains reachable in these tests. See the contract and exclusions and runtime validation.

Isolate motion when the navigator cannot answer

Use the separate research modes runtime-stop-unreachable and runtime-stop-unreachable-withhold-drive-ack. They freeze the real navigator executor after motion starts, keep downstream producers and drive alive, and restore the Owner's interrupted odometry after six wall seconds. Run through the exclusive research launcher; the product executable does not inject the fault.

At withdrawal, the Owner independently requests the current drive generation's closure. owner_runtime_loss_result distinguishes drive_isolated, motion_observed and native_closed; missing native results remain pending. Fresh quiet and driver isolation cannot authorize another task. The withheld-ACK case must report no confirmed isolation/quiet even if the simulator stops.

The existing nav2_closure_observations CTest now also checks early drive ACKs, partial closure and producer failure without conflating those facts. CI already runs this target; no new test selector is needed for these assertions. See the consumer isolation boundary.