Ir al contenido

Mapas y puntos de ruta de Recon

Trabaja con el mapa de un robot Recon a través de la API: encuentra el mapa asignado, dibújalo con la posición del robot y las zonas de exclusión, convierte entre píxeles de la imagen y coordenadas del mapa, y guarda puntos de ruta para las rutas.

En esta página

Todo lo que hace un robot ocurre en su mapa: las poses, los puntos de ruta y las rutas son coordenadas en el sistema de coordenadas del mapa. Esta guía lee el mapa, lo dibuja y coloca puntos de ruta en él. Los ejemplos usan la función auxiliar robot_api de la descripción general.

Necesitas una clave de API con acceso de solo lectura a la ubicación del robot para leer el mapa, y acceso de administrador a esa ubicación para guardar puntos de ruta.

Encuentra el mapa del robot

El robot crea su mapa durante la configuración, y cada robot tiene un mapa asignado. getMapAssignment lo devuelve:

Python
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"]
  • mapMeta describe el mapa: uuid, locationUuid, resolution, origin y, cuando el mapa está colocado sobre un plano de planta, floorPlan.
  • mapFilters enumera las zonas de exclusión aplicadas en este mapa (abajo).
  • Un robot sin mapa asignado no devuelve ninguno de los dos. Las rutas y las patrullas necesitan uno.

Para listar todos los mapas de una ubicación, por ejemplo para elegir uno antes de crear puntos de ruta, llama a findMaps con un locationUuid (o sin él, para toda la organización).

Dibuja el mapa

getMapImage devuelve el mapa asignado del robot como una imagen PGM en escala de grises (image/x-portable-graymap): el espacio ocupado en oscuro y el espacio libre en claro. La mayoría de las bibliotecas de imágenes leen PGM:

Python
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")

Un robot sin mapa asignado responde 404.

Píxeles y coordenadas del mapa

getMapMeta devuelve los dos valores que relacionan la imagen con las coordenadas del mapa:

  • resolution: metros por píxel.
  • origin: la posición en el mapa de la esquina inferior izquierda de la imagen. Usa sus dos primeros valores, x e y.

Las filas de la imagen se cuentan de arriba hacia abajo, mientras que la y del mapa crece hacia arriba:

Python
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 + OY

Para mostrar dónde está el robot y hacia dónde va, lee pose, goalPose y navPath (su trayecto planificado, una lista de puntos [x, y, z]) de getFullState y dibújalos con map_to_pixel:

Python
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")

Para mostrar un punto que se mueva con el robot, el socket en vivo del robot transmite la pose a medida que cambia.

Zonas de exclusión

Una zona de exclusión es un área en la que el robot nunca entra. Cada una de las que aparecen en mapFilters tiene un uuid y un name; getMapFilterImage devuelve su máscara, una imagen PGM del mismo tamaño que el mapa:

Python
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")

En la máscara, 0 (negro) es un área en la que el robot nunca entra, 255 (blanco) no tiene efecto, y los valores intermedios son áreas que el robot evita cuando puede. Dibuja los píxeles negros sobre el mapa con un color de advertencia y no coloques puntos de ruta allí.

Puntos de ruta

Un punto de ruta es una pose con nombre en un mapa. Las paradas de una ruta pueden hacer referencia a un punto de ruta en lugar de repetir su pose, así que varias rutas pueden compartir el mismo lugar.

OperaciónCuerpo
createRobotWaypointmapUuid, name, pose
findRobotWaypointsByMapmapUuid
getRobotWaypointmapUuid, waypointUuid
updateRobotWaypointwaypointUuid, y name o pose (un campo que omitas no cambia)
deleteRobotWaypointmapUuid, waypointUuid

Guarda el lugar donde está el robot

La pose más fiable es una que el robot ya alcanzó: llévalo hasta el lugar (en la consola o por el socket en vivo) y guarda su pose actual:

Python
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"])

Coloca uno desde el mapa

Para que alguien elija un lugar haciendo clic en la imagen del mapa, convierte el clic con pixel_to_map y el rumbo elegido con heading_to_orientation de la descripción general:

Python
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"]

Antes, compara el píxel seleccionado con el mapa y con las máscaras de exclusión: el robot no puede llegar a un punto de ruta que está dentro de una pared o de una zona de exclusión.

Antes de eliminar uno

Al eliminar un punto de ruta no se revisa si alguna ruta lo usa, y una parada cuyo punto de ruta ya no existe se omite. Busca su uuid en el waypointRefUuid de las paradas de cada ruta (findRobotRoutesByMap) y actualiza esas rutas primero.