Skip to content

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.

On this page

Two operations move a robot: deploy sends it on a patrol or to a point, and sendHome 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.

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.

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):

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).

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
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).

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
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
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 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) 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: activity, phase, waypointIndex of waypointTotal, estimatedTimeRemainingSec and distanceRemaining, as long as activityUuid is your run.
  • For the outcome, read the robot's events for that activityUuid until one is final: COMPLETED, FAILED (with failureReason), CANCELED or REJECTED.
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. 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 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.