> ## Documentation Index
> Fetch the complete documentation index at: https://docs.generalrobotics.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Chapter 2 — Plan your own routes

> Find mapped places, plan several stops, return home, and restart navigation.

Planning lets you inspect a route before executing it. Complete [Chapter 1](/navigation/first-mission) first. This chapter adds
semantic search and authored coordinates to the saved-mission workflow.

[Download this guide as a notebook](/assets/notebooks/navigation/planning.ipynb)

## 1. Connect and localize

Start in a project folder with a [GRID development session](/get-started/first-skill):

```bash theme={null}
grid skill run --dev --robot YOUR_ROBOT
```

Keep that CLI process running. Use its workspace `.venv` Python environment
(or select that kernel if you use the downloadable notebook). Run the snippets
in order in one Python session. Set your registered robot name and use the numbered selectors.
The robot must have navigation enabled.

```python theme={null}
from grid_nexus_client import make_robot
from grid_nexus_client.nav import make_nav

ROBOT = "YOUR_ROBOT"
robot = make_robot(ROBOT)
nav = make_nav(robot)
state = nav.status()
print(state)
if state.mission_running:
    raise RuntimeError("A mission is already running; coordinate with its operator first")
```

Choose a completed map, start navigation, and check the viewer against the
robot. Keep home for the return trip later in this guide.

```python theme={null}
MAP_PAGE = 1
maps = [m for m in nav.list_maps(page=MAP_PAGE, limit=100) if m.status == "COMPLETED"]
if not maps:
    raise RuntimeError("No completed maps on this page. Check the account or MAP_PAGE.")
for number, item in enumerate(maps, 1):
    print(number, item.name, item.map_id)
choice = input("Map number: ").strip()
if not choice.isdecimal() or not 1 <= int(choice) <= len(maps):
    raise ValueError("Choose a listed map number.")
MAP_ID = maps[int(choice) - 1].map_id
```

```python theme={null}
nav.start(map_id=MAP_ID, timeout_s=90)
nav.view()
home = nav.localize(timeout_s=30)
print(home)
```

## 2. Find a destination in the map

`find()` searches semantic objects already captured in the map. It does not
inspect the live camera, resolve local editor waypoint names, or find a saved
mission. Change the label to something present in your map.

```python theme={null}
LABEL = "table"
matches = nav.find(LABEL)
for index, waypoint in enumerate(matches):
    print(index, waypoint)
if not matches:
    raise RuntimeError("No semantic matches; choose another label or a verified coordinate")
```

Results are `Waypoint` objects in server order, not sorted by distance or
confidence. An empty list is a valid outcome. Inspect the coordinates and
choose the intended match instead of assuming the first one is right.

## 3. Plan and inspect

`plan()` uses the current localized pose as its start. It returns a Path
without moving the robot. Select the match you inspected above.

```python theme={null}
MATCH_INDEX = 0  # Choose after inspecting the matches.
path = nav.plan(matches[MATCH_INDEX])
print(path.to_dict())
```

`path.waypoints` are the requested or adjusted stops; `path.route` is the dense
route between them. Planning may move a target to reachable free space, so
inspect the returned coordinates as well as the target you supplied.

You can also pass one map-frame (x, y) pair, a `Waypoint`, a list of targets,
or a sparse Path. Passing a semantic string directly selects its first match.
The [type reference](/navigation/data-types) shows each accepted form.

## 4. Run the reviewed route

This next snippet moves the robot. If it has moved since planning, plan again:
a dense route is validated and executed, not automatically rebuilt from a new
start. Check the terminal result before continuing.

This example uses `blocking=True` explicitly. It is also the default.
The notebook cell stays busy until the mission ends. Another cell, including
one containing `nav.cancel_mission()`, cannot run during that wait.
To cancel, interrupt the running cell. The remote client requests cancellation;
check that the physical robot stopped. Use the e-stop if stopping cannot be confirmed.

```python theme={null}
if input("Drive the inspected route? Type RUN: ") != "RUN":
    raise RuntimeError("Mission not started")
result = nav.run_mission(path, blocking=True)
print(result)
if result.status != "succeeded":
    raise RuntimeError(f"Mission {result.status}: {result.message}")
```

For a one-step operation, `move_to(target)` plans and drives immediately.
Use the separate plan / `run_mission` steps when you need a review point.

## 5. Author several stops

Copy verified XY coordinates from the [mission editor](/navigation/mission-editor).
Fill `STOPS` with (x, y) pairs in metres; do not copy coordinates from a
different map. This produces a fresh plan and leaves execution to you.

```python theme={null}
from grid_types import Path, Waypoint

STOPS = []  # Fill with map-frame (x, y) pairs from your environment.
if not STOPS:
    raise ValueError("Set STOPS to verified map coordinates before planning")
sparse = Path([Waypoint(x, y) for x, y in STOPS], map_id=MAP_ID, name="Inspection")
tour = nav.plan(sparse)
print(tour.to_dict())
```

After reviewing it, execute tour with the same guarded `run_mission` pattern
above. A sparse Path contains stops; a dense Path also contains route.
`plan(existing_path)` replans its waypoints from the current pose. Python
`Waypoint` has no heading field today; use a saved editor mission for a final
heading requirement. UI route optimization and clearance settings belong to
the editor, not extra arguments to `nav.plan()`.

## 6. Return to the starting location

Build a new route to the original XY position. This does not restore the
original heading. Reversing an old dense route would assume the robot's
current position and the environment are unchanged.

```python theme={null}
return_path = nav.plan(Waypoint(home.x, home.y, name="Start"))
print(return_path.to_dict())
if input("Drive back to the starting position? Type RUN: ") != "RUN":
    raise RuntimeError("Return mission not started")
result = nav.run_mission(return_path, blocking=True)
print(result)
if result.status != "succeeded":
    raise RuntimeError(f"Return {result.status}: {result.message}")
```

## 7. Cancel, stop, or restart

Choose the operation that matches your intent. Do not run both just to cancel a mission.

| Operation | What stops? | Does localization keep updating? | What happens next? |
| - | - | - | - |
| `nav.cancel_mission()` | The current mission's motion is cancelled. | Yes. | Wait for the mission to end, then plan another route. No `start()` is needed. |
| `nav.stop(retain_localization=True)` | The mission and navigation workers stop. | No. | Call `start()` again. A saved estimate can help initialization, but a fresh position fix is still required. |
| `nav.stop()` | The mission and navigation workers stop. | No. | Call `start()` again without a retained estimate. |

`retain_localization` means **retain initialization data**, not **keep tracking the robot**.
Neither cancellation nor restart resumes the old mission. Read results before `stop()` clears the current run.

### Cancel from a separate notebook cell

For this workflow, replace the blocking execution in step 4 with
`nav.run_mission(path, blocking=False)` after the same route review and RUN prompt.
Do not execute both versions. The start cell returns while the robot continues moving.
Then a separate cell can request cancellation:

```python theme={null}
nav.cancel_mission()
print(nav.status())
```

Check that `mission_running` becomes `False` and confirm the robot stopped.
A cancellation request alone does not prove stopping. Do not start another mission while the current one is active.
A slow sensor or network call can still keep the kernel busy and delay cancellation.
[Chapter 3](/navigation/observation) shows a complete non-blocking observation loop with cancellation cleanup.

### Stop navigation and start it again

The blocking runs above have already ended before this example can run.
Use this when you want to stop the navigation services, then initialize them again.
It is not an emergency stop cell for an earlier blocking call.

```python theme={null}
print(nav.result())
print(nav.events()[-5:])
nav.stop(retain_localization=True)
print(nav.start(map_id=MAP_ID, timeout_s=90))
```

To change maps use `set_map(new_map_id)` after start. That stops the active
runtime and any mission, switches maps, and localizes again. Never reuse a
path or coordinates from the previous map.

## 8. Finish

Shutdown releases the shared Nav instance and affects other clients attached
to it. Collect results first, then release navigation before the borrowed robot.
Run this cleanup even after an earlier step fails. If a connection failure
prevents stopping motion, use the robot's e-stop.

```python theme={null}
nav.shutdown()
robot.shutdown()
```

Next, [observe while navigating](/navigation/observation) or add
[per-mission recording and constraints](/navigation/configuration#per-mission-options).


## Related topics

- [Navigation through GRID CLI](/navigation/overview.md)
- [Chapter 1 — Run your first mission](/navigation/first-mission.md)
- [Your first skill](/get-started/first-skill.md)
- [Agentic navigation](/cli/agentic-navigation.md)
- [Chapter 3 — Detect objects while navigating](/navigation/observation.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.