Recon
Recon Maps and Waypoints
Work with a Recon robot's map through the API: find the assigned map, draw it with the robot's position and keep-out zones, convert between image pixels and map coordinates, and save waypoints for routes.
On this page
Everything a robot does happens on its map: poses, waypoints and routes are all coordinates in the map's frame. This guide reads the map, draws it, and places waypoints on it. The examples use the robot_api helper from the overview.
You need an API key with read-only access to the robot's location to read the map, and admin access to it to save waypoints.
Find the robot's map
A robot builds its map during setup, and each robot has one assigned map. getMapAssignment returns it:
assignment = robot_api("getMapAssignment", {"deviceUuid": ROBOT_UUID})
map_meta = assignment.get("mapMeta")
if not map_meta:
raise SystemExit("This robot has no assigned map yet")
MAP_UUID = map_meta["uuid"]
keepouts = [f for f in assignment.get("mapFilters", []) if f["type"] == "KEEPOUT"]mapMetadescribes the map:uuid,locationUuid,resolution,originand, when the map is placed on a floor plan,floorPlan.mapFilterslists the keep-out zones applied on this map (below).- A robot with no assigned map returns neither. Routes and patrols need one.
To list every map at a location, for example to choose one before you create waypoints, call findMaps with a locationUuid (or without one for the whole organization).
Draw the map
getMapImage returns the robot's assigned map as a grayscale PGM image (image/x-portable-graymap): occupied space dark, free space light. Most image libraries read PGM:
import io
from PIL import Image
response = requests.post(f"{API}/getMapImage", headers=HEADERS, json={"deviceUuid": ROBOT_UUID}, timeout=60)
response.raise_for_status()
map_image = Image.open(io.BytesIO(response.content)).convert("RGB")
map_image.save("map.png")A robot with no assigned map answers 404.
Pixels and map coordinates
getMapMeta returns the two values that connect the image to the map's coordinates:
resolution: meters per pixel.origin: the map position of the image's lower-left corner. Use its first two values,xandy.
Image rows count down from the top, while map y grows upward:
meta = robot_api("getMapMeta", {"deviceUuid": ROBOT_UUID})
RES = meta["resolution"]
OX, OY = meta["origin"][0], meta["origin"][1]
HEIGHT = map_image.height
def map_to_pixel(x, y):
return (x - OX) / RES, HEIGHT - (y - OY) / RES
def pixel_to_map(px, py):
return px * RES + OX, (HEIGHT - py) * RES + OYTo show where the robot is and where it's going, read pose, goalPose and navPath (its planned path, a list of [x, y, z] points) from getFullState and draw them through map_to_pixel:
from PIL import ImageDraw
state = robot_api("getFullState", {"deviceUuid": ROBOT_UUID, "force": False})["fullState"]
draw = ImageDraw.Draw(map_image)
path = [map_to_pixel(p[0], p[1]) for p in state.get("navPath") or []]
if len(path) > 1:
draw.line(path, fill=(0, 128, 0), width=2)
if state.get("pose"):
px, py = map_to_pixel(*state["pose"]["position"][:2])
draw.ellipse([px - 4, py - 4, px + 4, py + 4], fill=(0, 102, 204))
map_image.save("map-with-robot.png")For a dot that moves with the robot, the robot live socket streams the pose as it changes.
Keep-out zones
A keep-out zone is an area the robot never enters. Each one in mapFilters has a uuid and a name; getMapFilterImage returns its mask, a PGM image the same size as the map:
for zone in keepouts:
response = requests.post(
f"{API}/getMapFilterImage",
headers=HEADERS,
json={"deviceUuid": ROBOT_UUID, "mapFilterUuid": zone["uuid"]},
timeout=60,
)
response.raise_for_status()
mask = Image.open(io.BytesIO(response.content))
mask.save(f"keepout-{zone['name']}.png")In the mask, 0 (black) is an area the robot never enters, 255 (white) has no effect, and values in between are areas the robot avoids when it can. Draw the black pixels over the map in a warning color, and don't place waypoints there.
Waypoints
A waypoint is a named pose on a map. Route stops can reference a waypoint instead of repeating its pose, so several routes can share the same spot.
| Operation | Body |
|---|---|
createRobotWaypoint | mapUuid, name, pose |
findRobotWaypointsByMap | mapUuid |
getRobotWaypoint | mapUuid, waypointUuid |
updateRobotWaypoint | waypointUuid, and name or pose (a field you leave out is unchanged) |
deleteRobotWaypoint | mapUuid, waypointUuid |
Save the spot where the robot stands
The most reliable pose is one the robot has reached: drive it to the spot (in the console or over the live socket), then save its current pose:
state = robot_api("getFullState", {"deviceUuid": ROBOT_UUID, "force": False})["fullState"]
waypoint = robot_api("createRobotWaypoint", {
"mapUuid": state["mapId"],
"name": "Loading dock door",
"pose": state["pose"],
})["waypoint"]
print(waypoint["uuid"])Place one from the map
To let someone click a spot on the map image, convert the click with pixel_to_map and the chosen heading with heading_to_orientation from the overview:
import math
x, y = pixel_to_map(412, 238)
waypoint = robot_api("createRobotWaypoint", {
"mapUuid": MAP_UUID,
"name": "North fence",
"pose": {"position": [x, y, 0.0], "orientation": heading_to_orientation(math.pi / 2)},
})["waypoint"]Check the clicked pixel against the map and the keep-out masks first: a waypoint in a wall or a keep-out zone is one the robot can't reach.
Before you delete one
Deleting a waypoint doesn't check whether routes use it, and a route stop whose waypoint is gone is skipped. Look for its uuid in the waypointRefUuid of each route's stops (findRobotRoutesByMap) and update those routes first.