# Path Recording & Replay
**Save successful paths and re-execute them on demand.**
Sometimes you don't need a dynamic planner to calculate a new path every time. In scenarios like **routine patrols, warehousing, or repeatable docking**, it is often more reliable to record a "golden path" once and replay it exactly.
The **Planner** component facilitates this via three ROS 2 services:
1. `save_plan_to_file`: Saves the currently active plan (or recorded history) to a CSV file.
2. `load_plan_from_file`: Loads a CSV file and publishes it as the current global plan.
3. `start_path_recording`: Starts recording the robot's actual odometry history to be saved later.
## The Recipe
This recipe sets up a basic navigation stack but exposes the **Save/Load/Record Services** to the Web UI instead of the standard "Click-to-Nav" action.
**Create a file named `path_recorder.py`:**
```python
import numpy as np
import os
from ament_index_python.packages import get_package_share_directory
# Kompass Imports
from kompass.robot import (
AngularCtrlLimits, LinearCtrlLimits, RobotGeometry, RobotType, RobotConfig, RobotFrames
)
from kompass.components import (
DriveManager, DriveManagerConfig, Planner, PlannerConfig,
MapServer, MapServerConfig, TopicsKeys, Controller
)
from kompass.ros import Topic, Launcher, ServiceClientConfig
from kompass.control import ControllersID
from kompass_interfaces.srv import PathFromToFile, StartPathRecording
def run_path_recorder():
kompass_sim_dir = get_package_share_directory(package_name="kompass_sim")
# 1. Robot Configuration
my_robot = RobotConfig(
model_type=RobotType.DIFFERENTIAL_DRIVE,
geometry_type=RobotGeometry.Type.CYLINDER,
geometry_params=np.array([0.1, 0.3]),
ctrl_vx_limits=LinearCtrlLimits(max_vel=0.4, max_acc=1.5, max_decel=2.5),
ctrl_omega_limits=AngularCtrlLimits(max_vel=0.4, max_acc=2.0, max_decel=2.0, max_steer=np.pi / 3),
)
# 2. Configure Components
planner = Planner(component_name="planner", config=PlannerConfig(loop_rate=1.0))
planner.run_type = "Timed"
controller = Controller(component_name="controller")
controller.algorithm = ControllersID.PURE_PURSUIT
controller.direct_sensor = True # Use direct sensor for simple obstacle checks
driver = DriveManager(
component_name="drive_manager",
config=DriveManagerConfig(critical_zone_distance=0.05)
)
# Handle message types (Twist vs TwistStamped)
cmd_msg_type = "TwistStamped" if os.environ.get("ROS_DISTRO") in ["rolling", "jazzy", "kilted"] else "Twist"
driver.outputs(robot_command=Topic(name="/cmd_vel", msg_type=cmd_msg_type))
map_server = MapServer(
component_name="global_map_server",
config=MapServerConfig(
map_file_path=os.path.join(kompass_sim_dir, "maps", "turtlebot3_webots.yaml"),
grid_resolution=0.5
)
)
# 3. Define Services for UI Interaction
save_path_srv = ServiceClientConfig(
name=f"{planner.node_name}/save_plan_to_file", srv_type=PathFromToFile
)
load_path_srv = ServiceClientConfig(
name=f"{planner.node_name}/load_plan_from_file", srv_type=PathFromToFile
)
start_path_recording = ServiceClientConfig(
name=f"{planner.node_name}/start_path_recording", srv_type=StartPathRecording
)
# 4. Launch
launcher = Launcher()
launcher.kompass(
components=[map_server, planner, driver, controller],
multiprocessing=True,
)
odom_topic = Topic(name="/odometry/filtered", msg_type="Odometry")
launcher.inputs(location=odom_topic)
launcher.robot = my_robot
launcher.frames = RobotFrames(world="map", odom="map", scan="LDS-01")
# 5. Enable UI
launcher.enable_ui(
inputs=[save_path_srv, load_path_srv, start_path_recording],
outputs=[
map_server.get_out_topic(TopicsKeys.GLOBAL_MAP),
odom_topic,
planner.get_out_topic(TopicsKeys.GLOBAL_PLAN),
],
)
launcher.bringup()
if __name__ == "__main__":
run_path_recorder()
```
---
## Workflow: Two Ways to Generate a Path
Once the recipe is running and you have the Kompass UI open (`http://0.0.0.0:5001`), you can generate a path using either the planner or by manually driving the robot.
### Option A: Save a Computed Plan
Use this if you want to save the path produced by the global planner and you want to "freeze" that exact path for future use.
1. **Generate Plan:** Trigger the planner (e.g., via the `/clicked_point` input on the UI).
2. **Verify:** Check if the generated path looks good...
3. **Save:** In the UI Inputs panel, go to the `planner/save_plan_to_file` in the *Inputs* panel
* Enter the **file_location:** `/tmp/` and **file_name:** `computed_path.json`
* Click **Send**.
### Option B: Record a Driven Path (Teleop)
Use this if you want the robot to follow a human-demonstrated path (e.g., a specific maneuver through a tight doorway).
1. **Start Recording:** In the UI Inputs panel, select `planner/start_path_recording`.
* **recording_time_step:** `0.1` (Records a point every 0.1 seconds).
* Click **Call**.
2. **Drive:** Use your keyboard or joystick (e.g., `ros2 run teleop_twist_keyboard teleop_twist_keyboard`) to drive the robot along the desired route.
3. **Save:** When finished, select `planner/save_plan_to_file`.
* Enter the **file_location:** `/tmp/` and **file_name:** `computed_path.csv`
* Click **Send**.
*(Note: Calling save automatically stops the recording process).*
---
## Replay the Path
Now that you have your "Golden Path" saved (either computed or recorded), you can replay it anytime.
1. **Restart:** You can restart the stack or simply clear the current plan.
2. **Load:** In the UI **Inputs** panel, select `planner/load_plan_from_file`.
* **file_location:** `/tmp/`
* **file_name:** `driven_path.csv` (or `computed_path.csv`)
* Click **Send**.
**Result:**
The planner immediately loads the CSV file and publishes it as the **Global Plan**. The **Controller** receives this path and begins executing it immediately, retracing your steps exactly.
---
## Use Cases
* **Routine Patrols:** Record a perfect lap around a facility and replay it endlessly.
* **Complex Docking:** Manually drive a complex approach to a charging station, save the plan, and use it for reliable docking.
* **Multi-Robot Coordination:** Share a single "highway" path file among multiple robots to ensure they stick to verified lanes.