Troubleshooting
What each SDK error means and what to do about it.
Start with menlo status: it connects, reads state for a second, and prints what the robot
reports without sending anything. The SDK refuses a command only when there is no live
state; everything else the robot reports is for your script to judge.
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. See No State on udp or hybrid.
menlo robots add NAME --udp HOST runs this check and prints the same advice.
No State on udp or hybrid
On udp and hybrid, the robot listens for commands on UDP port 8850 and sends its state to
one address: the udp-state-host and udp-state-port (8851 by default) in the Asimov Edge
parameters. Commands go out from your computer, but state comes in to it, so a firewall on
your computer that blocks inbound UDP 8851 from the robot leaves the SDK with no state:
connect() raises ConnectError, a command raises NotReadyError with no_state or
stale_state, and menlo stand, balance and walk print Not feasible:. livekit is
not affected.
First check that udp-state-host is this computer's address as the robot sees it; the robot
sends state to that one host only. Then allow inbound UDP 8851 from the robot:
- Linux with ufw:
sudo ufw allow from <robot address> to any port 8851 proto udp. With firewalld:sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="<robot address>" port port="8851" protocol="udp" accept', thensudo firewall-cmd --reload. - macOS: the firewall allows or blocks per application. Open System Settings > Network > Firewall > Options and allow incoming connections for the Python you run the SDK with, or answer Allow when macOS asks the first time a script connects.
- Windows: answer Allow for private networks when Windows asks the first time a
script connects, or in an administrator PowerShell run
New-NetFirewallRule -DisplayName "Asimov state" -Direction Inbound -Protocol UDP -LocalPort 8851 -Action Allow.
Run menlo status again: the state rate and a fresh sample show that state arrives.
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
There was no live state to send against, and nothing was sent. stand(), balance()
outside MOVE, set_velocity(), set_joints() and trajectory() read the latest state
sample before they send. The codes are no_state (the robot has not reported) and
stale_state (the latest sample is older than 0.5 s); both are waited out for up to the
command's timeout first, and the message then ends in (waited <timeout> s). A closed
Robot raises NotConnectedError, a lost link LinkLostError and a protocol version
mismatch ProtocolMismatchError instead, at once.
e.problems carries them, e.has(code) tests one and e.preflight is the whole report.
Check the link with menlo status. On udp or hybrid, see
No State on udp or hybrid.
The robot mode, a fault, an alert, a hot actuator or a low battery never raise
NotReadyError: the command is sent and the firmware decides. A rule against them is yours
to write; see Write Your Own Guard.
RobotFaultedError
A wait found state.faulted set after the command was sent: 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; catch it
first to tell a fault apart. e.sent is the command that 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. The message says when the robot was not armed: wait untilrobot.armedisTrue, thenbalance()again. Otherwise another controller may hold it: the Asimov Manager Cockpit or a paired gamepad outranks a script, and a command it outranks has no effect.balance()from DAMP: raised at once,the robot is in DAMP: stand() first.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.
The Robot Tipped Over After stand() in MOVE
stand() is sent from any robot mode. STAND has no balance loop, so a free-standing robot
asked to stand from MOVE tips over. Before stand() in MOVE, hang the robot from its gantry
hook or seat it on a stool or bench; rest.py asks first. To stand still after a walk, call
balance(): zero velocity keeps the robot in MOVE, balancing in place. See
Bringing the Robot to Rest.
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.
set_velocity()is sent in any robot mode: in DAMP the robot drops it, and in a STAND that is not armed it leaves the robot in STAND. MOVE is entered only from STAND held upright for 0.5 s, and an early velocity is neither refused nor reported. Checkstate.modeandrobot.armed, and callstand()thenbalance()first. - Another source holds control; see above. The state stream does not say who does.
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, 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
robot.get_state() or damp() before the robot reported (after
connect(require_state=False)), or any verb 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, what the SDK reports, and your own guard
- Troubleshooting the Robot: problems that are not the SDK's
How is this guide?