Skip to content

Monitor Recon Robots

Watch a Recon fleet from your own tools: connection and battery, what each robot is doing and how far along it is, and the history of every activity it ran, from the robot state and event operations.

On this page

Two kinds of reads cover a robot fleet: the state operations say what each robot is doing now, and the events operation says what it did. The examples use the robot_api helper from the overview.

You need an API key with read-only access to the robots' locations.

Fleet state

getMinimalStateList returns one entry per robot:

Python
for robot in robot_api("getMinimalStateList", {})["minimalStates"]:
    progress = ""
    if robot.get("activity") == "ROUTE_RUN" and robot.get("waypointTotal"):
        progress = f"stop {robot['waypointIndex'] + 1} of {robot['waypointTotal']}"
    print(f"{robot['name']}: {robot['connectionStatus']}, {robot.get('batteryPercent')}%, {robot.get('activity')} {progress}")
FieldMeaning
uuid, name, locationUuidThe robot, as the other robot operations and the console know it
connectionStatus, healthStatusGREEN, YELLOW, ORANGE or RED, best to worst, as for every device
batteryPercentBattery level, 0 to 100
activity, activityUuidWhat the robot is doing (below) and that activity's ID. For a dispatch, activityUuid is the routeRunUuid that deploy returned
event, phase, phaseEventThe latest step of the activity: event is its lifecycle (below), phase the part in progress, such as NAVIGATING, DWELLING, UNDOCKING or CHARGING
routeUuid, waypointUuid, waypointIndex, waypointTotalDuring a patrol: the route, the current stop (a stop ID from the route), its position counted from 0, and the number of stops
goalPose, pose, mapIdWhere the robot is heading, where it is, and the map those poses are on
estimatedTimeRemainingSec, distanceRemainingTime (seconds) and distance (meters) left to the current goal
failureReasonWhy the latest activity failed, when it did
firmwareVersion, hwVariationFirmware and hardware model

For one robot, getFullState returns the same fields plus the robot's capabilities. Include force (false is fine); the call is refused without it:

Python
state = robot_api("getFullState", {"deviceUuid": ROBOT_UUID, "force": False})["fullState"]
capability = state.get("robotPlatformCapability") or {}
print(state["activity"], state.get("phase"), capability.get("supportedActions"))

Use activity and phase to tell whether a robot is docked, charging or out working; the mode and charging fields aren't reported.

Activities

activityThe robot is
IDLEWaiting for a command
ROUTE_RUNRunning a patrol or route. phase is DWELLING while it waits at a stop
NAVIGATE_TO_POSEGoing to a point it was sent to
NAVIGATE_TO_HOMEReturning to its charging pad because it was sent home
IDLE_DOCKINGReturning to its charging pad on its own, after its work is done or it has sat idle
LOW_BATTERY_DOCKINGReturning to its charging pad to recharge. It refuses new work until the battery recovers
MANUAL_CMD_VEL, MANUAL_ACTION, MANUAL_POSTUREBeing driven live by an operator
EMERGENCYStopped by an emergency stop, until someone releases it
EXPLORE_AND_MAP, EXPLORE_AREA, MAINTENANCE_HOLDMapping its space, or held for map maintenance

New values can be added. Treat one you don't recognize as busy.

Event history

findRobotEvents returns what a robot did between startMs (inclusive) and endMs (exclusive). The response is newline-delimited JSON (application/x-ndjson), one event per line, newest first:

Python
import json
import time

def robot_events(robot_uuid, start_ms, end_ms):
    response = requests.post(
        f"{API}/findRobotEvents",
        headers=HEADERS,
        json={"deviceUuid": robot_uuid, "startMs": start_ms, "endMs": end_ms},
        timeout=60,
    )
    response.raise_for_status()
    events = []
    for line in response.text.splitlines():
        if not line.strip():
            continue
        event = json.loads(line)
        if "_error" in event:
            raise RuntimeError(f"findRobotEvents stopped early: {event.get('_message')}")
        events.append(event)
    return events

now = int(time.time() * 1000)
for event in robot_events(ROBOT_UUID, now - 24 * 3600 * 1000, now):
    print(event["timestampMs"], event.get("activity"), event.get("event"), event.get("phase"), event.get("failureReason"))
  • There's no paging. Ask for a window you can handle, a day for example, and walk back window by window for longer periods.
  • A last line with _error means the stream stopped before the end, though the status is 200. Retry that window.
  • Events can arrive late. A robot that was offline uploads what it recorded when it reconnects, with the original timestampMs. When you collect events on a schedule, overlap the windows and skip events whose uuid you've already stored.
FieldMeaning
uuidThe event's ID; use it to skip duplicates
timestampMsWhen it happened, by the robot's clock
activity, activityUuidThe activity the event belongs to
eventSTARTED, PROGRESS, SUSPENDED, RESUMED, COMPLETED, FAILED, CANCELED or REJECTED
phase, phaseEventFor PROGRESS, the phase that STARTED or COMPLETED
routeUuid, waypointUuid, waypointIndex, waypointTotalThe route and the stop, during a patrol
preemptedByFor SUSPENDED or REJECTED, the activity that took priority
failureReasonFor FAILED, why
originatorWho or what started the activity: type is USER, API_TOKEN or RULE, for example

Each activity follows the same lifecycle: STARTED, then PROGRESS events as it moves through its phases (arriving at a stop is a PROGRESS event whose phaseEvent is COMPLETED), possibly SUSPENDED and RESUMED when something with higher priority interrupts it, and exactly one of COMPLETED, FAILED or CANCELED at the end. An activity the robot refused is a single REJECTED event, with preemptedBy naming what blocked it.

Alert on what matters

ConditionCheck
OfflineconnectionStatus isn't GREEN on two polls in a row
Low battery away from the padbatteryPercent under your threshold while activity isn't one of the docking activities
Patrol failedA FAILED event for a ROUTE_RUN, with its failureReason
Dispatch refusedA REJECTED event, with preemptedBy
StuckThe same activity, phase and waypointIndex for much longer than the route usually takes

Polling the state list every minute or two is far below your rate limit. Robot events don't arrive on the event WebSocket or on organization webhooks; for real-time updates, connect to the robot live socket.