Menlo
Python SDK

Troubleshooting

What the SDK's errors mean and what to do about them.

ConnectError: Nothing to Connect To

Robot() found neither MENLO_MANAGER_URL + MENLO_CREDENTIAL in the environment nor a default in ~/.menlo/robots.toml. Run menlo login <manager-url>, or pass a ConnectionConfig. With several robots saved and no default, menlo use <name> picks one.

ConnectError: The Manager Refused to Mint a Token

The credential is wrong, revoked, or the URL is not the manager. Mint one on the robot (asimovctl sdk-token create --role control) and menlo login again. A manager that redirects is reported rather than followed: use the address it redirected to.

ConnectError: No State From the Robot (UDP)

The robot never reported in. Check, in order:

  1. Its edge service is running with --udp-control.
  2. Its --udp-state-host is this machine's address as the robot sees it. The robot reports to one configured destination; it doesn't reply to whoever asked.
  3. Nothing is blocking port 8851 inbound — firewall, VPN.

ConnectError: Could Not Bind the State Port

Another SDK process holds it — one client per port on the UDP lane. Close it, or use state_bind=("0.0.0.0", <other port>) and point the robot at the matching --udp-state-port.

ConnectError: The Config Lacks a Lane

connect("hybrid") needs both udp= and livekit=; connect("udp") needs udp=. connect() with no mode needs a config with exactly one lane. cfg.available_modes() says which modes a config can reach.

ProtocolMismatchError

The robot speaks another asimov.io version than the SDK was built against. Update whichever is behind. connect(allow_version_skew=True) proceeds anyway, and commands may be misread.

wait_for(Mode.STAND) Times Out and the Robot Stays DAMP

  • After a fall: robot.state.faulted is True and the wait raises RobotFaultedError. Clear the fault first — it outranks every client.
  • Another controller holds the robot: release it and try again.

wait_for(Mode.STAND) Times Out and the Robot Is in MOVE

Zero velocity keeps the firmware in MOVE; stop() doesn't return the STAND posture. Usually that's what you want. Reach for stand() only to wake a robot up, or on one that's held — see Safety.

A stand() or damp() Did Nothing

Something is streaming setpoints and the verb got overwritten. Stop the stream, then send it. In an emergency kill the process instead. See Safety.

set_velocity Returned but the Robot Did Not Walk

set_velocity returns at once; the hold runs in the background, and the next verb or the end of the with block cuts it short. Pass wait=True, or sleep for the duration.

StateStaleError

The robot went quiet for longer than stale_after (default: the link timeout, 2 s). Treat it as absent, not slow — the connection dropped, its edge service stopped, or it's reporting to a different machine. After two seconds you get LinkLostError too.

LinkLostError

Terminal for that session. The robot isn't left running: it zeroes the velocity itself once commands stop arriving. close(), then connect() again.

NotConnectedError

A verb or robot.state before the robot reported (after connect(require_state=False)), or after close(). close() during a set_velocity(..., wait=True) raises this in the waiting thread.

UnsupportedError

This lane does not carry that capability: camera, microphone and speaker need the hybrid or livekit lane and the [livekit] extra. Gate with robot.has("camera").

ImportError Naming Pillow, numpy or OpenCV

Frame.to_jpeg, to_numpy, Clip.save_frames and Clip.save_mp4 need those packages at call time. The SDK does not depend on them; install the one named.

Every Sent.wait_outcome() Returns Unknown

Expected — the edge does not report per-command verdicts yet. Use wait_for and wait_until instead. When it does report them, Unknown becomes Applied or Refused with no change to your code.

Velocity Is Slower Than Asked

Check sent.clamped and sent.command. The SDK clamps to Limits before anything reaches the robot; raise it with Robot(cfg, limits=Limits(vx=...)).

Two Sessions Keep Disconnecting Each Other

Both joined the room with the same identity. With ManagerConfig, each session gets its own unless you set the same label; with LiveKitConfig, one token is one participant, so mint two.

How is this guide?

On this page