Add GoToZone feature#516
Conversation
Co-authored-by: Loke Ji Xian <loke_ji_xian@cgh.com.sg> Co-authored-by: Tey Leong Teck <leong_teck_tey@cgh.com.sg> Signed-off-by: Loke Ji Xian <loke_ji_xian@cgh.com.sg> Signed-off-by: Tey Leong Teck <leong_teck_tey@cgh.com.sg> Signed-off-by: kjchee <keai_jiang_chee@cgh.com.sg>
Signed-off-by: kjchee <keai_jiang_chee@cgh.com.sg>
|
Hi @mxgrey, noting down our planned responses to your comments from yesterday's Interoperability Meeting. Comment 1: Remove the same-vertex entry/exit lane restriction
Comment 2: Remove the zone guard model
Comment 3: Opt-in / opt-out of zone stubbornness
|
mxgrey
left a comment
There was a problem hiding this comment.
Thanks for all your patience while I sought time to review this PR.
Before getting too much into line-by-line feedback, I have some high-level methodology recommendations. First some observations:
- The current implementation leans heavily on
RobotContextto track whether the current task is related to going into a zone. Shoving data intoRobotContextis an anti-pattern that we've increasingly resorted to for lack of better options, but it deserves heavy scrutiny before accepting it. I think this is a case where it's not needed, and I'll explain why later. - The current implementation contaminates
GoToPlacewith implementation logic that's specific toGoToZone. This suggests a leaky abstraction and needs to be scrutinized. - The execution of a
ZoneEntryorZoneExitis currently based on the nav graph topology and on what kind of task is being executed. This is another indication of a leaky abstraction.
I believe all of the above issues can be resolved in a relatively simple way: Instead of pushing zone-related logic down into GoToPlace, you should implement GoToZone with a sequence, much like GoToPlace is. That sequence would look like this:
GoToZone
├─ GoToPlace (Entry Point)
├─ BookZoneWaypoint
├─ ReleaseReservation (Entry Point)
└─ EnterZone
Some of the effects this has:
- We no longer need to modify
GoToPlaceat all. It can continue to use the reservation system as normal, and redirect robots that are trying to approach the same zone entry point at the same time. This has a bonus effect of fixing the pileup problem you mentioned, so you will no longer need to implement ago-to-waitpointcapability. - The ZoneEntry lane event doesn't need to do anything for now. Its purpose will be taken over by
BookZoneWaypoint->EnterZonein the proposedGoToZonesequence. Maybe in the future theZoneEntrylane event might be used to check if the zone is locked out, or publish some kind of message, but for now it can simply do nothing.
| const YAML::Node zones_yaml = graph_config["zones"]; | ||
| if (!zones_yaml) | ||
| { | ||
| std::cout << "Your navigation graph does not provide zone information. " |
There was a problem hiding this comment.
I don't think this warning is needed since zones are an optional feature. There are many navigation graphs out there that predate the existence of the zone feature, and there's no harm in the fact that they don't contain zone information. A warning like this might scare users into regenerating their nav graphs when it's not really necessary.
| auto wrapped_finished = | ||
| [context = _context, finished = std::move(finished)]() | ||
| { | ||
| context->set_is_zone_task(false); |
There was a problem hiding this comment.
This isn't a very robust approach to tracking whether a zone task is happening. If there's any subtle fluke that leads to the task ending without this callback being triggered (for example, I think there are some cases where kill can do this) then the is_zone_task flag could get stuck as true.
If we really need to track this, we should consider using a similar pattern to _lift_destination where the GoToZone event would hold onto a std::shared_ptr<void>. As long as the std::shared_ptr<void> has a count, then we know a zone event is happening. After the GoToZone event destructs, the count will automatically drop.
| namespace phases { | ||
|
|
||
| //============================================================================== | ||
| class ZoneEntry |
There was a problem hiding this comment.
Is there any particular reason you decided to implement ZoneEntry and ZoneExit as legacy phases instead of Events? The legacy phase concept only exists so that we don't need to port all the old implementations of phases into the new task system. New task actions are meant to be implemented using the Event API.
|
At the community forum today we talked about reimplementing If you want to do conditional branching, you can tweak the last line: The second callback determines what happens when the sequence is finished executing. You can have that callback trigger a new sequence generated by the |
@mxgrey, thanks for your detailed explanation & example on the implementation of the dynamically generated sequence! Regarding the events, after a few rounds of internal discussion (together with @lttey), we're thinking of a new sequence and would appreciate your guidance: explanation what each event does: We think a 'single-authority' method is cleaner: This only needs 2 small changes at fleet adapter library side:
Bit of context on why this took us a while: What are your thoughts on all of this? |
This sounds great overall, and I appreciate the diagram that you've drawn. One detail that I think is worth pointing out: If you Related to that, in my experience, |


New feature implementation
Implemented feature
This is the main PR for GoToZone Feature. Tracked in Stage A of open-rmf/rmf#726.
Introduces GoToZone task/event (input a zone name instead of a waypoint) & a zone booking system that lets robots claim, hold, and release specific waypoints inside named zones (e.g., consultation rooms, parking zone, ward bedside parking) under the arbitration of a new standalone Zone Supervisor node. Fleet adapters interact with the zone supervisor via new
rmf_zone_msgstopics, and zone entry/exit (request booking/release booking) are wired into the plan execution pipeline via nav-graph lane events.This PR is the deep-dive for an 8-repo feature.
rmf_internal_msgsrmf_zone_msgspackage (7 msgs)rmf_building_map_msgsGraphZone,ZoneVertex,ZoneTransitionLanermf_trafficrmf_traffic_editorrmf_taskGoToZonetask-sequence event +ZoneGuardModelrmf_ros2(this)rmf_visualizationrmf_demosdispatch_zone.pyMerge sequence: 1 --> 2 --> 3 --> 4.
Implementation description
A. Overview: End-to-end flow
1. Dispatch. A user submits a zone task either as a top-level task (
category: "zone") or as aGoToZoneevent nested inside a larger task sequence. The request may carry optionalmodifiers.waypoint_preferencehints that influence waypoint selection inside the zone:group— a label the user assigns to subsets of zone vertices in the Traffic Editor (e.g.left,right,center). The supervisor prefers candidates in the matching group.orientation— the robot's desired heading (radians) at the final selected waypoint.preferred_waypoints— an ordered list of candidate waypoint names; each entry may be the fully qualified vertex name (e.g.consultation#R#p1#wait_2) or the short alias (wait_2) shown in Traffic Editor / RViz.Future stages will extend this list with sensor-driven options.
2. Deserialization. The JSON request is validated and deserialized into a
GoToZone::Descriptionbyrmf_fleet_adapter/src/rmf_fleet_adapter/tasks/Zone.cpp, producing an event-sequence description for the task planner.3. Bidding (
ZoneGuardModel). During task bidding, the task planner callsZoneGuardModel::estimate_finishfor each candidate robot. If a robot's current waypoint is already one of the zone's internal waypoints,estimate_finishreturnsstd::nulloptand that robot is excluded from bidding on this particular zone task (while remaining eligible for other tasks). This prevents awarding a zone task to a robot that is already parked inside the zone.4. Execution entry. When a robot wins the bid, its
TaskManagerdispatches the task and invokes the execution logic atrmf_fleet_adapter/src/rmf_fleet_adapter/events/GoToZone.cpp.GoToZone::Active:is_zone_task = trueand records the caller's modifiers on theRobotContext.robot_task_requestassignments that bypass bidding.GoToPlace::Activebuilt fromGoToPlace::Description::make_for_one_of(goals).wrapped_finishedcallback so that on completion/cancel/kill,is_zone_task,zone_task_modifiers, andbooked_zone_goalare all cleared on theRobotContext.5. Initial planning. Inside
GoToPlace, the planner selects the lowest-cost goal from the list (initially any internal vertex in the zone) and produces a plan toward it.6. Zone entry — booking handshake. When the plan routes the robot onto a lane whose
entry_eventisZoneEntry(wired byparse_graph.cppfrom the nav graph's transition-lane metadata),ExecutePlaninserts aZoneEntryphase. The phase:ZoneRequestof typeENTRYonzone_requests, stamped with a freshrequest_idof the form{fleet}_{robot}_{zone}_{random_hex}.zone_states(transient-local QoS) and waits for aZoneBookingwhoserequest_idmatches exactly.set_booked_zone_goal(...)andset_booked_zone_waypoint(...)on theRobotContext, then invokesrequest_replan().7. Replan to the assigned waypoint. On replan,
GoToPlace::_find_plan()readsbooked_zone_goalfrom theRobotContextand uses it as the planner goal in place of "any of the internal vertices". The robot now heads to the specific waypoint the supervisor assigned.8. In-zone operation. The robot parks at the assigned waypoint.
_booked_zone_waypointremains set, holding an RAII_zone_stubbornnessreference that prevents traffic negotiators from displacing the robot while it is occupying its booked slot.9. Zone exit — release. When a subsequent task (for example, a
GoToPlaceto a waypoint outside the zone) routes the robot onto a lane whoseexit_eventisZoneExit,ExecutePlaninserts aZoneExitphase. The phase publishes aZoneRequestof typeEXIT, awaits release confirmation from the zone supervisor, then clearsbooked_zone_waypointon theRobotContext. The stubbornness reference drops.10. Recovery paths. Two out-of-band mechanisms handle bookings that are not released cleanly by the normal flow:
ZoneManualReleasemessage to the supervisor with the affected(robot, fleet, zone)triple, then zone supervisor drops the booking and publishes aZoneBookingRevokedmessage so the affectedRobotContextcan clean up its zone-state fields.stale_booking_check_interval = 60 s) that compares each booked robot's reported position against the x/y of its booked waypoint. A booking that previously latchedhas_arrivedand is now out of tolerance is marked as suspect. If the suspect state persists forstale_booking_grace_period(default 180 s), the supervisor revokes the booking and publishes aZoneBookingRevokedto the affected robot.B. New task type (
zone) and new event type (go_to_zone)Two JSON schemas define the external surface:
schemas/task_description__zone.json— the top-levelcategory: "zone"task. Dispatch withdispatch_task_request.schemas/event_description__zone.json— thego_to_zoneevent for use inside a composed task sequence.C. New fleet-adapter event:
events/GoToZoneThe event-level handler for
go_to_zone. Responsibilities:GoToPlace::Activethat targets all internal vertices of the named zone as amake_for_one_ofgoal set.RobotContext: setsis_zone_task = trueand records the caller'szone_task_modifiers.wrapped_finishedcallback that clears task-level zone state (is_zone_task,zone_task_modifiers,booked_zone_goal) on completion / cancel / kill, preventing state leakage into the next task._booked_zone_waypointis intentionally not cleared here, it stays set until the robot physically leaves the zone (viaZoneExitor forcedZoneBookingRevoked), so that the stubbornness guard remains in force for the whole in-zone period.D. New phases:
ZoneEntryandZoneExitImplemented as legacy-style task phases (via
LegacyPhaseShim), consistent with existing low-level phases such asDoorOpenandRequestLift. They are inserted byExecutePlanwhen the robot traverses a lane whoseentry_event/exit_eventcarries aZoneEntryorZoneExit— wired at nav-graph parse time inparse_graph.cpp.ZoneEntrypublishes anENTRYZoneRequestto the zone supervisor, waits for a matchingZoneBooking, stores the assigned waypoint and planner goal on theRobotContext, then callsrequest_replan()so that the outerGoToPlace(insideevents/GoToZone) routes the robot to the granted waypoint.ZoneExitpublishes anEXITZoneRequestto the zone supervisor, waits for release confirmation, then clears the booking on theRobotContext.Open question for reviewers. These phases follow the older
LegacyPhaseShimpattern. We are happy to migrate them to the newer event-based framework used byLockMutexGroupif Open-RMF maintainers prefer that direction.E. New node:
zone_supervisorA standalone ROS 2 node that owns zone-booking authority. All callbacks share a single
MutuallyExclusivecallback group, so_zone_log,_zones, and the derived indexes are accessed without additional locks.Responsibilities:
ZoneRequest(ENTRY/EXIT) and allocates waypoints through a 4-stage algorithm:group_hintis set and matches any candidate, narrow to that subset, otherwise keep all candidates.preferred_waypointsis set, return the first one present in the current candidate set.priorityvalue.ZoneStatesnapshots on state change (event-driven).has_arrivedlatch that prevents false revocations while the robot is still in transit.ZoneManualReleasefor operator-initiated recovery (e.g. a broken-down robot that needs to be manually pulled out of a zone).Tunable parameters:
stale_booking_check_interval(default 60 s)stale_booking_distance_threshold(default 1.0 m)stale_booking_grace_period(default 180 s)F.
RobotContextnew fields_is_zone_taskboolExecutePlanto decide whether aZoneEntryphase should be inserted when the plan crosses a zone entry lane._zone_task_modifiersZoneTaskModifiersstructgroup_hint,orientation_hint,preferred_waypoints) carried from the task description into theZoneRequestpayload._booked_zone_goalstd::optional<Plan::Goal>GoToPlace::_find_plan()when non-empty. Cleared when theGoToZonetask completes / cancels / kills._booked_zone_waypointstd::stringZoneExit) or the booking is revoked. Also gates_zone_stubbornness._zone_stubbornnessstd::shared_ptr<void>_booked_zone_waypointis set, released when cleared.G. Known limitations
No transit-zone support. If a user dispatches a plain
GoToPlacewhose planned path happens to pass through a zone, noZoneEntry/ZoneExitevents fire — zones are only honored when the task / event is explicitlyGoToZone. Transit-zone handling is deferred to a later stage (lift cabin zone and lift waiting zone).ZoneExitonly fires on zone exit-lane traversal. If a subsequent plan starts inside the zone but the startset resolves to a waypoint outside the zone (an edge case where the robot's startset has already merged onto an external waypoint), the exit lane is never traversed and the booking is not released through the normal path. But the zone supervisor's periodic stale-booking check (default every 60 s) will eventually remove such bookings.Direct
GoToPlaceto a zone internal vertex is soft-blocked, not hard-blocked. Zone internal vertices are exposed in RViz / Traffic Editor only by their "short name" (e.g.wait_2), the fully qualified form (consultation#R#p1#wait_2) thatGoToPlacewould need to resolve is intentionally hidden. This discourages, but does not prevent, a user from dispatchingGoToPlacedirectly to a zone waypoint — which would bypass the supervisor and leave the zone in an inconsistent state.GenAI Use
We follow OSRA's policy on GenAI tools