Troubleshooting
What each SDK error means and what to do about it.
Start with menlo status: it connects, runs the readiness check, and prints the first
blocking reason without sending anything.
ConnectError: Nothing to Connect To
Robot() found neither MENLO_UDP_HOST nor MENLO_MANAGER_URL with MENLO_CREDENTIAL
in the environment, and no saved robot. Run menlo setup, or pass a ConnectionConfig.
With several robots saved and no default, menlo robots use NAME picks one, or set
MENLO_ROBOT. MENLO_MANAGER_URL without MENLO_CREDENTIAL, or the reverse, is the same
error: they go together.
ConnectError: The Saved Robot Lacks What the Mode Needs
The message names the fix, such as menlo robots add lab --udp <robot address>. hybrid
needs the robot's address, the Asimov Manager URL and an SDK credential; udp the address;
livekit the Manager URL and the credential. cfg.available_modes() says which modes a config can reach.
ConnectError: Asimov Manager Refused the Credential
The SDK credential is wrong or revoked, or the URL is not Asimov Manager. Issue a new one on
the Developer page of Asimov Manager with the Control role
(Get an SDK Credential) and run
menlo setup again. A credential with the Observe role
connects but cannot drive on livekit. A manager that redirects is reported rather than followed: use
the address it redirected to.
ConnectError: No State From the Robot on UDP
The robot never reported in. The robot sends UDP state to one address, so check, in order:
udp-controlis on in the Asimov Edge parameters in Asimov Manager.udp-state-hostis this machine's address as the robot sees it, andudp-state-portis the port the SDK binds (8851 by default).- Nothing blocks port 8851 inbound: a firewall or a VPN.
menlo robots add NAME --udp HOST runs this check and prints the same advice.
ConnectError: Could Not Bind the State Port
Another process holds port 8851; one client per port on udp. Close it, or use
state_bind=("0.0.0.0", <other port>) and set udp-state-port to match.
ProtocolMismatchError
The robot speaks another protocol version than the SDK was built against. Update whichever is
behind. connect(allow_version_skew=True) proceeds anyway, and the robot may misread the commands the
SDK sends.
NotReadyError
The check that stand(), balance() from STAND, set_velocity(), set_joints() or
trajectory() runs before it sends
found a blocking problem, and nothing was sent, except when a wait raised a
RobotFaultedError after the command went out; see the next section. The message names each
problem, its code and what fixes it; e.problems carries them, e.has(code) tests one and e.preflight is
the whole check. Each code and its fix is in
Check Before You Move.
A problem that clears on its own (no_state, stale_state, not_armed) is waited out
for up to timeout seconds first; the message then ends in (waited <timeout> s). Every other
problem raises at once: a robot in DAMP asked to walk needs stand(), a robot in STAND
asked to walk needs balance(), a robot in MOVE asked to stand needs balance() instead, a
low battery needs charging, a hot actuator needs to cool.
RobotFaultedError
A command's check, or a wait, found state.faulted set: a critical alert, such as a fall,
over-temperature or battery protection, has latched DAMP. The message and state.alerts
name it; write it down before anything restarts the firmware, since restarting clears the
record of what latched. Nothing a script sends clears it. Then follow
Recovering After an E-Stop or a Fall
to support and inspect the robot; its Turn Off Robot step restarts the firmware. See
If Something Goes Wrong.
RobotFaultedError is a NotReadyError, so except NotReadyError catches it too; e.sent
is set when the fault latched after a command went out.
WaitTimeoutError
A command was sent and the robot did not report the effect in time. e.last is the last
state seen and e.sent what went out.
stand(): the robot did not report STAND and arming withintimeout(10 s). See the next section.balance()from STAND: the robot did not report MOVE withintimeout(5 s) after the zero velocity went out. Another controller may hold it: the Asimov Manager Cockpit or a paired gamepad outranks a script, and a command it outranks has no effect.damp(): the robot did not report DAMP withintimeout(5 s). Check that state is still arriving withmenlo status, and that atrajectory()loop is not overwriting the verb.set_joints(wait=True): a joint did not reach its target withintolerance. It may be blocked, or the gains may be too soft to hold it.wait_until(): the predicate stayed false; the message names the robot mode and the age of the last sample.
stand() Times Out and the Robot Stays DAMP
- A fault is latched:
stand()raisesRobotFaultedErrorinstead; see above. - The robot dropped the command: another source holds control. The Cockpit outranks a paired gamepad, which outranks udp, which outranks livekit, and a paired gamepad holds control while idle. Unpair it and try again.
- The robot reports STAND but is not armed: it is not upright. The firmware arms after
0.5 s of STAND under 30 degrees of tilt;
robot.armedreads the test.
stand() Raises NotReadyError in MOVE
stand() refuses from MOVE with wrong_mode: STAND has no balance loop and a free-standing
robot tips over. Zero velocity keeps the firmware in MOVE, so balance() is how to stand
still after a walk. stand() is for waking a robot up from DAMP.
set_velocity() Raises NotReadyError in STAND
set_velocity() runs in MOVE only and refuses from STAND with wrong_mode. Call
balance() first: from an armed STAND it puts the robot in MOVE at zero velocity and
returns once the robot reports MOVE. See
Standing and Balancing.
set_velocity Returned but the Robot Did Not Walk
In order of likelihood:
- The hold was cut short: with
wait=False,set_velocityreturns at once, and the next verb or the end of thewithblock ends the hold. Keep the connection open for the duration, or leavewaitat its default so the call returns after the hold. - The robot is not in MOVE:
balance()from STAND sends zero velocity without a report of the effect when the robot reports no gravity, since the check then passes with anunknown_gravitywarning and the SDK cannot see arming. MOVE is accepted only from STAND held upright for 0.5 s, and an early velocity is neither refused nor reported. Checkstate.gravityandstate.mode. - Another source holds control; see above. The state stream does not say who does.
A robot in DAMP or STAND is not on this list: set_velocity() raises NotReadyError there
and sends nothing.
A stand() or damp() Did Nothing
A loop is streaming trajectory() setpoints and the next setpoint overwrote the verb. A
stand() sent into the loop is overwritten this way. A damp() usually holds, because
the robot drops trajectories once it reports DAMP, but a setpoint that arrives
first still wins. Stop the loop, then send the verb. A set_joints() does not have this
problem: any verb ends it.
StateStaleError
The robot went quiet for longer than stale_after (default: the link timeout, 2 s) during
wait_until() or a command's wait. Treat it as absent, not slow: the connection dropped,
the robot's software stopped, or it is reporting to a different machine. LinkLostError
follows. A stream that is merely late before a command is sent is stale_state in the
check, waited out and then a NotReadyError.
LinkLostError
No state for 2 s. The SDK sent zero velocity if it held one, and every verb now raises this until you
close() and connect() again. On udp and hybrid, the robot also zeroes the velocity 2 s
after the last command it received.
NotConnectedError
A verb or robot.get_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 connection does not carry that capability. camera, microphone and speaker need
hybrid or livekit; on those, a capability is claimed only from a track that arrived
within media_timeout of connecting. 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
The robot reports no per-command verdict, so Unknown is what every command gets and
on_refused never fires. Unknown is neither success nor refusal: read the effect with
wait_until and robot.get_state(), or let stand(), balance() and damp() wait for it.
Velocity Is Slower Than Asked
Check sent.clamped and sent.command. The SDK clamps to Limits, whose defaults are the
firmware caps of 0.4 m/s and 0.8 rad/s; a robot does not walk faster than that. A tighter
limit came from Robot(limits=), MENLO_LIMITS, or the saved robot's limits.
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.
Related
- Safety: what each layer stops and the readiness codes
- Troubleshooting the Robot: problems that are not the SDK's
How is this guide?