> ## Documentation index
> Fetch the complete documentation index at: https://developer.rhombus.com/llms.txt
> Use it to discover all available pages before exploring further.

# Dispatch and Track Recon Robots

> Send a Recon robot on a patrol, to a point on its map, or back to its charging pad with the deploy and sendHome operations, then follow the run to its outcome through the robot's state and events.

Two operations move a robot: [`deploy`](https://developer.rhombus.com/api-reference/robot-webservice/deploy-robot) sends it on a patrol or to a point, and [`sendHome`](https://developer.rhombus.com/api-reference/robot-webservice/send-robot-home) returns it to its charging pad. Both hand the command to the robot and return; the robot then accepts or refuses it, and reports how it went. The examples use the `robot_api` helper from the [overview](https://developer.rhombus.com/robots/overview#conventions).

**You need** an API key with admin access to the robot.

> **Warning**: These calls move a physical machine. Make sure the area is safe for the robot to drive through, ask a person to confirm before anything automated or AI-driven sends a robot out, and keep a way to stop it at hand: the emergency stop on the [live socket](https://developer.rhombus.com/robots/live-socket).

## Check the robot first

Dispatch a robot that's connected and free: waiting (`IDLE`), docking or charging on its own (`IDLE_DOCKING`), or done with its last activity. Read its state ([Monitor robots](https://developer.rhombus.com/robots/monitor#fleet-state)):

```python Python
FREE = {"IDLE", "IDLE_DOCKING"}
DONE = {"COMPLETED", "FAILED", "CANCELED"}

state = robot_api("getFullState", {"deviceUuid": ROBOT_UUID, "force": False})["fullState"]
if state["connectionStatus"] != "GREEN":
    raise SystemExit("The robot isn't connected")
if state.get("activity") not in FREE and state.get("event") not in DONE:
    raise SystemExit(f"The robot is busy: {state.get('activity')}")
```

The robot still has the final say: it can refuse a command this check lets through ([When the robot refuses](https://developer.rhombus.com/robots/dispatch#when-the-robot-refuses)).

## Run a patrol now

Deploy with `navType` `PATROL` and the patrol's UUID. The patrol must be enabled, and its route must be on the robot's map:

```python Python
run = robot_api("deploy", {"deviceUuid": ROBOT_UUID, "navType": "PATROL", "patrolUuid": PATROL_UUID})
RUN_UUID = run["routeRunUuid"]
```

The patrol runs with its own arrival actions. A patrol needs no schedule to be dispatched this way ([Routes and patrols](https://developer.rhombus.com/robots/routes-and-patrols#create-a-patrol)).

## Send the robot to a point

Deploy with `navType` `POSE` and a complete pose (all three `position` and all four `orientation` values). To send it to a saved waypoint, use that waypoint's pose:

```python Python
waypoint = robot_api("getRobotWaypoint", {"mapUuid": MAP_UUID, "waypointUuid": WAYPOINT_UUID})["waypoint"]
run = robot_api("deploy", {"deviceUuid": ROBOT_UUID, "navType": "POSE", "pose": waypoint["pose"]})
RUN_UUID = run["routeRunUuid"]
```

When the robot arrives the activity completes, and it waits there until its next command or until it returns to its pad on its own (`IDLE_DOCKING`).

## Send the robot home

```python Python
robot_api("sendHome", {"deviceUuid": ROBOT_UUID})
```

The robot drives to its charging pad and docks. `sendHome` **interrupts** a patrol rather than ending it: the patrol is `SUSPENDED`, and it can resume by itself (a `RESUMED` event) after the robot has docked. To end a run, send [`CMD_NAVIGATE_CANCEL`](https://developer.rhombus.com/robots/live-socket#cmd_navigate_cancel) on the live socket; there's no REST operation that cancels one.

## What the response means

`"error": false` with a `routeRunUuid` means the command reached the robot, not that the robot took it:

- **The robot accepts or refuses it.** Follow the run ([below](https://developer.rhombus.com/robots/dispatch#follow-the-run)) to learn which.
- **An unreachable robot** fails the call with a `5xx` status. Check `connectionStatus` before you dispatch, and retry later rather than in a loop.
- **An incomplete request** also fails with `500`, not `400`: a `PATROL` deploy without `patrolUuid` or for a disabled patrol, or a `POSE` deploy without a complete pose. Validate before you send.

### When the robot refuses

The robot weighs a new command against what it's doing. It refuses (a `REJECTED` event, with `preemptedBy` naming the activity in the way) when:

- its emergency stop is engaged;
- it's held for map maintenance;
- it's returning to its pad to recharge, or recharging after a low battery;
- an operator is driving it;
- for a patrol, it's on its way to a point or back to its pad.

A new patrol replaces a patrol that's running (the old run ends as `CANCELED`), and `sendHome` suspends one. A robot on its charging pad backs off the pad first (phase `UNDOCKING`).

## Follow the run

The `routeRunUuid` from `deploy` is the run's **activity ID**: it appears as `activityUuid` in the robot's state and its events.

- **While it runs**, poll [`getFullState`](https://developer.rhombus.com/robots/monitor#fleet-state): `activity`, `phase`, `waypointIndex` of `waypointTotal`, `estimatedTimeRemainingSec` and `distanceRemaining`, as long as `activityUuid` is your run.
- **For the outcome**, read the robot's [events](https://developer.rhombus.com/robots/monitor#event-history) for that `activityUuid` until one is final: `COMPLETED`, `FAILED` (with `failureReason`), `CANCELED` or `REJECTED`.

```python Python
import time

FINAL = {"COMPLETED", "FAILED", "CANCELED", "REJECTED"}

def wait_for_run(robot_uuid, run_uuid, started_ms, timeout_s=3600):
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        now = int(time.time() * 1000)
        for event in robot_events(robot_uuid, started_ms - 60_000, now + 60_000):
            if event.get("activityUuid") == run_uuid and event.get("event") in FINAL:
                return event
        time.sleep(15)
    raise TimeoutError(f"run {run_uuid} hasn't finished")

started = int(time.time() * 1000)
run = robot_api("deploy", {"deviceUuid": ROBOT_UUID, "navType": "PATROL", "patrolUuid": PATROL_UUID})
result = wait_for_run(ROBOT_UUID, run["routeRunUuid"], started)
print(result["event"], result.get("failureReason") or "", (result.get("preemptedBy") or {}).get("activity", ""))
```

`robot_events` is the reader from [Monitor robots](https://developer.rhombus.com/robots/monitor#event-history). The window starts a minute early and ends a minute late to allow for the robot's clock. For updates as they happen, the [live socket](https://developer.rhombus.com/robots/live-socket) delivers the same events as `BEHAVIOR_EVENT` messages, with the run's ID in `activity_uuid`.

Keep your own record of runs from these events. The route run and waypoint run operations in the API Reference don't return runs started by `deploy` or by patrol schedules.
