neo_waypoint_follower is a ROS 2 package developed by Neobotix GmbH for waypoint following for Neobotix ROX robots.
It provides two core functionalities:
- Waypoint Saving: Save waypoints from RViz MarkerArray topics to a YAML file via a ROS 2 service.
- Waypoint Looping: Autonomously loop through saved waypoints using the Nav2 navigation stack, with flexible control over repetition, timing, and failure handling, via a ROS 2 service.
-
Purpose: Listens to a MarkerArray topic (e.g., from RViz) and saves the received waypoints to a YAML file on request.
-
Parameters:
waypoints_topic(string): Topic to subscribe for waypoints (default:/waypoints)output_file(string): Path to save the waypoints YAML file
-
Service:
/save_waypoints(std_srvs/srv/Trigger): Saves the latest received waypoints to the specified YAML file.
-
Runtime note:
- The
output_fileparameter is read right before saving. You can change it at runtime:ros2 param set /save_waypoints_server output_file /home/user/my_waypoints.yaml ros2 service call /save_waypoints std_srvs/srv/Trigger {}
- The
-
Purpose: Loads waypoints from a YAML file and sends navigation goals to the Nav2 stack, looping through the waypoints as configured.
-
Modes:
- Waypoint Loop Mode: Loops through all waypoints for the specified number of repeats (
repeat_count). - Single Goal Mode: If only one waypoint is loaded, the node switches to single-goal navigation and executes just that goal once.
- Waypoint Loop Mode: Loops through all waypoints for the specified number of repeats (
-
Parameters:
yaml_file(string): Path to the waypoints YAML fileframe_id(string): Frame ID for waypoints (default:map)has_loop(bool): Runtime loop intent flag used for run metadata capture (default:false)repeat_count(int): Number of times to repeat the waypoint loop (default:100)wait_at_waypoint_ms(int): Time to wait at each waypoint in milliseconds (default:200)history_cap(int): Max retained run history entries (default:15, range:1..200)history_dir(string): Run history directory (default:/var/lib/neo/lemma-gui/history)history_file(string): Run history file name (default:navigation_runs.json)stop_on_failure(bool): Stop looping on navigation failure (default:true)action_name(string): Nav2 action server name (default:/navigate_to_pose)odom_topic(string): Odometry topic for distance tracking (default:/odom)
-
Services:
/start_waypoint_loop(std_srvs/srv/Trigger): Starts the waypoint loop or single-goal mode/pause_waypoint_loop(std_srvs/srv/Trigger): Pauses the waypoint loop/resume_waypoint_loop(std_srvs/srv/Trigger): Resumes the waypoint loop/cancel_waypoint_loop(std_srvs/srv/Trigger): Cancels and resets the waypoint loop/publish_loaded_waypoints(std_srvs/srv/Trigger): Publishes the currently loaded waypoints once to a latched topic/run_history/list(neo_waypoint_follower/srv/RunHistoryList): Lists navigation runs. If persistence backend is degraded, service returnssuccess=falsewith an errormessagebut may still include last-known in-memoryentries./run_history/delete(neo_waypoint_follower/srv/RunHistoryDelete): Deletes one run byrun_id/run_history/clear(neo_waypoint_follower/srv/RunHistoryClear): Clears all run history entries
-
Topics:
/waypoint_loop/metrics(neo_waypoint_follower/msg/LooperMetrics): Aggregated looper metrics (QoS: best_effort, durability_volatile)/waypoint_loop/loaded_waypoints(neo_waypoint_follower/msg/Waypoints): Names + poses for the currently loaded waypoints (QoS: reliable, transient_local)
-
Message:
neo_waypoint_follower/LooperMetricsstd_msgs/Header header- Progress:
uint32 loop_idx(0-based current loop index)uint32 wp_idx(0-based current waypoint index)string current_waypoint_name
- Distances (meters):
float64 distance_from_startfloat64 distance_from_lastfloat64 distance_remaining
- Timing:
builtin_interfaces/Duration etabuiltin_interfaces/Duration navigation_time
- State:
uint8 looper_state— enum values:LOOPER_IDLE = 0LOOPER_RUNNING = 1LOOPER_WAITING = 2LOOPER_PAUSED = 3LOOPER_FINISHED = 4LOOPER_ERROR = 5
uint8 nav_result_code— rawrclcpp_action::ResultCodefrom latest NavigateToPose resultuint16 nav_error_code—NavigateToPose.Result.error_codestring nav_error_msg—NavigateToPose.Result.error_msgstring status_message— human-readable state/error summary
-
Message:
neo_waypoint_follower/Waypointsstd_msgs/Header headerstring[] namesgeometry_msgs/PoseStamped[] poses
-
Message:
neo_waypoint_follower/RunHistoryEntry- Identity/timing:
run_id,started_at_ms,ended_at_ms,status,cause_code - Route snapshot:
route_name,route_description,yaml_file,route_waypoints - Route settings snapshot:
has_loop,loop_count,wait_at_waypoint_ms,pauses - Progress snapshot:
wall_time_sec,distance_traveled,distance_remaining,completion_pct - Diagnostics:
nav_result_code,nav_error_code,nav_error_msg,status_message
- Identity/timing:
-
Behavior Notes:
- Lowering
repeat_countbelow the current loop index will cause the run to finish right after the current goal completes. - Changing
wait_at_waypoint_msdoes not affect an already running timer; it takes effect from the next waypoint. - If only one waypoint is loaded, single-goal mode is activated automatically.
- Start-time busy conditions are reported by
/start_waypoint_loopTrigger response (success=false, message). - Runtime action failures (for example ABORTED) are surfaced via
LOOPER_ERROR+nav_result_code/nav_error_code/nav_error_msg. - Run history is written by
waypoint_looperto:/var/lib/neo/lemma-gui/history/navigation_runs.json- File schema:
{ "schemaVersion": 1, "entries": [...] } - Writes are atomic (
temp file + rename) and failures are warning-only (navigation continues).
- Lowering
-
Runtime note:
- The
yaml_fileparameter is read when you press Start. To switch waypoint files at runtime, set the param and call Start again:ros2 param set /waypoint_looper yaml_file /home/user/waypoints_alt.yaml ros2 service call /start_waypoint_loop std_srvs/srv/Trigger {}
- The
The package provides a launch file for easy configuration and startup:
ros2 launch neo_waypoint_follower waypoint_follower_launch.py| Argument | Default Value | Description |
|---|---|---|
| waypoints_topic | /waypoints |
Topic to listen to for waypoints |
| save_waypoints_path | <pkg>/config/waypoints.yaml |
Path used by save_waypoints_server |
| load_waypoints_path | <pkg>/config/waypoints.yaml |
Path used by waypoint_looper |
| frame_id | map |
Frame ID for waypoints |
| repeat_count | 10 |
Number of times to repeat the loop |
| wait_at_waypoint_ms | 500 |
Time to wait at each waypoint (ms) |
| stop_on_failure | true |
Stop looping on navigation failure |
Example:
ros2 launch neo_waypoint_follower waypoint_follower_launch.py repeat_count:=10 wait_at_waypoint_ms:=1000Open separate terminals and run the following commands in order:
a. Launch the simulation:
ros2 launch rox_bringup bringup_sim_launch.pyb. Launch the navigation stack (with simulation time):
ros2 launch rox_navigation navigation.launch.py use_sim_time:=Truec. Launch RViz:
ros2 launch neo_nav2_bringup rviz_launch.pyd. Launch the waypoint follower (with example arguments):
ros2 launch neo_waypoint_follower waypoint_follower_launch.py repeat_count:=10 wait_at_waypoint_ms:=1000You can also load a custom waypoints YAML file by specifying the load_waypoints_path launch argument. For example:
ros2 launch neo_waypoint_follower waypoint_follower_launch.py load_waypoints_path:=/home/adarsh/waypoints_test.yamlMake sure your custom YAML file follows the template provided in config/waypoints_template.yaml.
- Add waypoints in RViz using the Navigation2 Panel.
First select theWaypoint / Nav Thru Poses Mode. Then, when you add waypoints using theNav2 Goalmarkers, the waypoints are automatically published to the/waypointstopic by the Nav2 stack.
Once you have finished marking all desired waypoints in RViz, do not choose any options from the Nav2 panel. Proceed to the next step. - Save waypoints by calling the service:
ros2 service call /save_waypoints std_srvs/srv/Trigger {} - Start waypoint looping:
More Controls:
ros2 service call /start_waypoint_loop std_srvs/srv/Trigger {}
-
Pause then resume:
ros2 service call /pause_waypoint_loop std_srvs/srv/Trigger {} ros2 service call /resume_waypoint_loop std_srvs/srv/Trigger {} -
Cancel & reset (no auto-restart):
ros2 service call /cancel_waypoint_loop std_srvs/srv/Trigger {}
-
Publish loaded waypoints to a latched topic
Trigger the node to publish the currently loaded waypoints once, then echo them:
ros2 service call /publish_loaded_waypoints std_srvs/srv/Trigger {} ros2 topic echo /waypoint_loop/loaded_waypoints -
Dynamic Parameter Update
You can now change parameters at runtime using:
ros2 param set /waypoint_looper has_loop true
ros2 param set /waypoint_looper repeat_count 2
ros2 param set /waypoint_looper wait_at_waypoint_ms 0This allows you to adjust loop intent/count/wait while the node is running. See "Behavior Notes" above for details on how these changes affect execution.
-
Run history operations
ros2 service call /run_history/list neo_waypoint_follower/srv/RunHistoryList {} ros2 service call /run_history/delete neo_waypoint_follower/srv/RunHistoryDelete "{run_id: run_20260224T171533412Z_a3f91c}" ros2 service call /run_history/clear neo_waypoint_follower/srv/RunHistoryClear {}
For more details and usage examples, please visit our official documentation at: https://neobotix-docs.de/
Minimal, production-ready waypoint vault utilities bundled with neo_waypoint_follower.
- Owns a simple on-disk vault directory for waypoint YAMLs.
- Provides services to list, save, load into the looper (params only), and preview waypoints to a latched topic.
- Saves route-level metadata (
name,description,has_loop,loop_count,wait_at_waypoint_ms) together with waypoints in one YAML write. - Preserves route sequence by writing waypoints in Marker ID order (avoids
wp_1, wp_10, wp_2lexicographic jumps).
- Vault directory:
/var/lib/neo/lemma-gui/waypoints - Preview topic:
/vault/preview_waypoints(QoS: reliable, transient_local) - Works alongside existing nodes:
save_waypoints_serverandwaypoint_looper.
-
Parameters:
vault_dir(string, default:/var/lib/neo/lemma-gui/waypoints): Base directory that stores waypoint YAML files.looper_node(string, default:/waypoint_looper): Target node name for settingyaml_file(and optional loop params).frame_id(string, default:map): Frame used when previewing waypoints.waypoints_topic(string, default:/waypoints): MarkerArray topic cached for/vault/save_current.
-
Services:
/vault/list(neo_waypoint_follower/srv/VaultList)- Request: empty
- Response:
bool success,string message,string[] filenames,int32[] points,bool[] has_loop,int32[] loop_count,int32[] wait_ms,string[] names,string[] descriptions
/vault/save_current(neo_waypoint_follower/srv/VaultSaveCurrent)- Request:
string filename,bool allow_overwrite,string name,string description,bool has_loop,int32 loop_count,int32 wait_ms - Response:
bool success,string message - Behavior: reads latest MarkerArray from
waypoints_topic, writes<vault_dir>/<filename>.yamlwithmetadata+waypointsin one pass
- Request:
/vault/load_to_looper(neo_waypoint_follower/srv/VaultLoadToLooper)- Request:
string filename,bool set_loop_params,int32 loop_count,int32 wait_ms - Response:
bool success,string message - Behavior: sets
/waypoint_looperparameteryaml_fileand (optionally)repeat_count,wait_at_waypoint_ms
- Request:
/vault/preview_once(neo_waypoint_follower/srv/VaultPreview)- Request:
string filename - Response:
bool success,string message - Behavior: publishes a one-shot
neo_waypoint_follower/Waypointsto/vault/preview_waypoints(latched)
- Request:
/vault/delete(neo_waypoint_follower/srv/VaultDelete)- Request:
string filename - Response:
bool success,string message - Behavior: deletes a route YAML from
vault_dir(filename is sanitized; path traversal is blocked)
- Request:
/vault/rename(neo_waypoint_follower/srv/VaultRename)- Request:
string filename,string new_name,string new_description - Response:
bool success,string message,string new_filename - Behavior: updates route metadata and derives a normalized target filename from
new_name; moves file when slug changes and rejects collisions
- Request:
-
Topics:
/vault/preview_waypoints(neo_waypoint_follower/msg/Waypoints): One-shot preview published with QoStransient_localso late subscribers receive it.
waypoints is required. metadata is optional
metadata:
name: Warehouse Patrol
description: Nightly perimeter route
has_loop: true
loop_count: 3
wait_at_waypoint_ms: 500
waypoints:
point_1:
position: {x: 0.0, y: 0.0, z: 0.0}
orientation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}
point_2:
position: {x: 1.0, y: 2.0, z: 0.0}
orientation: {x: 0.0, y: 0.0, z: 0.7071, w: 0.7071}-
List vault files:
ros2 service call /vault/list neo_waypoint_follower/srv/VaultList {} -
Save current waypoints with metadata:
ros2 service call /vault/save_current neo_waypoint_follower/srv/VaultSaveCurrent \ "{filename: patrol_a, allow_overwrite: true, name: 'Warehouse Patrol', description: 'Night route', has_loop: true, loop_count: 3, wait_ms: 500}" -
Load into looper (set params only):
ros2 service call /vault/load_to_looper neo_waypoint_follower/srv/VaultLoadToLooper \ "{filename: patrol_a, set_loop_params: true, loop_count: 3, wait_ms: 500}" -
Preview once (latched):
ros2 service call /vault/preview_once neo_waypoint_follower/srv/VaultPreview "{filename: patrol_a}" ros2 topic echo /vault/preview_waypoints
-
Delete a route:
ros2 service call /vault/delete neo_waypoint_follower/srv/VaultDelete "{filename: patrol_a}" -
Rename route metadata and file slug:
ros2 service call /vault/rename neo_waypoint_follower/srv/VaultRename \ "{filename: patrol_a, new_name: 'Warehouse Patrol v2', new_description: 'Updated route'}"
vault_manager is included in waypoint_follower_launch.py with sensible defaults. Override its parameters if needed:
ros2 launch neo_waypoint_follower waypoint_follower_launch.py \
vault_dir:=/var/lib/neo/lemma-gui/waypointsmetadatanow persists route settings together with waypoints:has_loop,loop_count,wait_at_waypoint_ms./vault/save_currentacceptshas_loop,loop_count,wait_msand writes metadata in one YAML save pass./vault/listreturnshas_loop,loop_count,wait_msarrays; missing numeric metadata is preserved as unspecified (-1) instead of inventing defaults./vault/load_to_loopercan applyrepeat_countandwait_at_waypoint_mswhenset_loop_params=true.
- Run history is now owned by
waypoint_looperand persisted on robot disk at/var/lib/neo/lemma-gui/history/navigation_runs.json. - JSON file is versioned:
{ "schemaVersion": 1, "entries": [...] }. - New services:
/run_history/list,/run_history/delete,/run_history/clear. run_idformat:run_YYYYMMDDTHHMMSSmmmZ_<6hex>.- Persistence failures are warning-only for active navigation lifecycle events (run execution continues).
