Safety
What to check before a script runs, how to stand and balance the robot, what to do when something goes wrong, what the SDK, the robot and the firmware each stop, and the readiness codes.
The SDK is a client of the robot. It sends what you ask, bounded by your limits, and reads back what the robot reports. The stops that matter are in the firmware and on the robot. When in doubt, open Asimov Manager and press the E-Stop.
Before You Run a Script
- Support the robot, as Supporting the Robot describes: hanging from its gantry hook with both feet on the floor for a first stand and walk, hanging from its gantry hook or seated on a bench for joint control. A walking robot needs 2 m of clear floor ahead of it.
- Clear the area. Keep people, cables and furniture out of the robot's reach and keep the robot in sight.
- Have a way to stop the robot open. Keep Asimov Manager open with its E-Stop in the page header or the phone top bar (it needs no drive session), and the battery unit within reach to cut power. Stopping the Robot explains each.
- Make sure nothing else is driving. Close any Cockpit session and unpair the gamepad. Both outrank a script, and a paired gamepad holds control even while idle.
- Check the robot.
python check.pyormenlo statusreports battery, actuator temperatures, faults and whether the state stream is fresh. Neither sends anything. - Start slow. Pass
Robot(limits=Limits(vx=0.2, vy=0.2, vyaw=0.4))or save limits on the robot, and use shortdurationvalues until the script behaves.
Nothing in the SDK is an emergency stop
balance(), damp() and close() are commands sent over a network link. None of them cuts
power, and none reaches a robot the link has lost. Keep the E-Stop open and the battery
unit within reach whenever the robot stands.
Standing and Balancing
The robot has three robot modes, and only one of them keeps it upright on its own. In
DAMP no actuator holds a position. In STAND the actuators hold a standing pose and
nothing balances it: a free-standing robot in STAND tips over. In MOVE the walking
policy balances the robot, at zero velocity or any other. So the robot must be supported in
DAMP and STAND, and stands free only once balance() has returned and it reports MOVE.
This is the sequence for a script, and the order in which the Quickstart runs it as separate files:
import sys
from menlo.asimov import NotReadyError, Robot, RobotFaultedError, WaitTimeoutError
with Robot().connect() as robot:
# 1. The robot hangs from its gantry hook with both feet on the floor.
try:
robot.stand() # DAMP -> STAND; returns once the robot is armed
robot.balance() # armed STAND -> MOVE at zero velocity; returns once MOVE is reported
except RobotFaultedError as exc: # latched until the firmware restarts
sys.exit(f"faulted: {exc}")
except NotReadyError as exc: # nothing was sent; the message says what fixes it
sys.exit(str(exc))
except WaitTimeoutError as exc: # sent, but the robot did not arm or reach MOVE in time
sys.exit(str(exc))
# 2. Only now: slacken the gantry until the robot carries its own weight.
input("Robot balancing in MOVE. Slacken the gantry, then press Enter to walk. ")
# 3. Walk. Every set_velocity() ends with zero velocity; the robot stays in MOVE.
robot.set_velocity(vx=0.2, duration=3.0)
robot.balance() # zero velocity: balancing in place
# 4. Support the robot again before it leaves MOVE.
input("Take up the gantry's slack so it holds the robot, then press Enter to damp. ")
robot.damp() # every actuator stops holding its positionWhy each step is where it is:
stand()on the gantry hook, both feet on the floor. STAND holds a pose and does not balance, so the hook carries the robot.stand()checks the robot first, sends STAND and returns once the robot reports STAND and has been upright for 0.5 s: the firmware accepts MOVE only after that, sostand()returning means the robot is armed. It raisesNotReadyErrorwith nothing sent when the robot is not ready, and refuses in MOVE: a walking robot that drops to STAND loses its balance loop.balance()before the support comes off. From an armed STAND,balance()sends zero velocity, which is what puts the robot in MOVE, and returns once the robot reports MOVE. If the robot is in STAND but not armed, it waits for the arming hold before it sends. MOVE at zero velocity is the only state in which the robot stands free, so the hook keeps carrying the robot untilbalance()has returned: until then the robot is in STAND, and a robot in STAND that loses its support tips over.- Slacken the gantry only after
balance()returns. The SDK cannot see the gantry; the script asks you. Slacken it gradually until the robot carries its own weight, with 2 m of clear floor ahead. - Walk, then
balance().set_velocity()runs in MOVE only, holds the velocity for itsdurationand ends with zero velocity.balance()in MOVE is the same zero velocity, never checked and never refused: it ends a walk at once and the robot balances in place. - Support the robot before
damp().damp()is never refused, and a standing robot falls when its actuators let go. Take up the gantry's slack, or seat the robot on a bench, first. The robot's own controls go from Walk to Stand before Damp (Bring It Back Down) because an operator sees that the support is holding the robot; a script does not, so it stays in MOVE until it damps.
Catch all three errors around stand() and balance(). A RobotFaultedError means a
critical alert, a fall included, latched DAMP until the firmware restarts; a
NotReadyError means nothing was sent and its message names what fixes it; a
WaitTimeoutError means the command was sent and the robot did not report the mode in
time, so read robot.get_state().mode before you decide what to do next. Keep Asimov Manager
open at the E-Stop throughout: nothing in the SDK is an emergency stop.
If Something Goes Wrong
- Press the E-Stop in Asimov Manager, or cut power at the battery unit. The E-Stop damps every actuator at once and a free-standing robot falls; use it when a robot lying on the floor is better than what is happening now. If the robot's software is not running, the E-Stop is disabled: cut power at the battery unit. Do not try to catch a falling robot.
- Stop the script. Ctrl-C in a script that uses
with Robot().connect()leaves the block, andclose()sends zero velocity. A script that keeps running keeps re-sending its velocity at 10 Hz;menlo balancefrom another terminal does not end it. - Read what the robot reports, and write it down.
menlo statusshows the robot mode, a FAULTED badge and what latched, from the current alerts and the latchederror_flags. In a script,state.faultedand thefaultedmessage frompreflight()name the causes, andstand(),balance(),set_velocity(),set_joints(),trajectory()andwait_until()raiseRobotFaultedError. - Recover the robot. Follow Recovering After an E-Stop or a Fall: support and inspect the robot. Its Turn Off Robot step restarts the firmware, so do step 3 first.
- Restart the firmware, if the recovery did not. A critical alert holds the robot in
DAMP, and
error_flagsset, until the firmware restarts; restarting clears the record of what latched. Use Restart on the Firmware (RPU) row of the Troubleshoot page, or Turn Off Robot and Start Robot on Overview.
Do not call stand() on a robot that is walking or falling. STAND has no balance loop: it
stiffens the joints into a pose, and a free-standing robot in that pose tips over. End a walk
with balance(), which leaves the robot in MOVE, balancing in place.
Who Stops What
| Layer | Stops | Does not stop |
|---|---|---|
| SDK | a held velocity: zero on balance(), close(), the end of a with block, a lost link | anything after a fault; another program's velocity |
| The robot | velocity and trajectory in DAMP (dropped); a velocity 2 s after the last command on udp and hybrid; a trajectory 2 s after the last setpoint (DAMP) | a velocity a script keeps re-sending |
| Firmware | MOVE before an armed STAND (the robot stays in STAND, nothing reports it); everything, on a critical alert (DAMP, latched) | a command because of where it came from: it obeys whichever arrived last |
The SDK also clamps every velocity to your Limits, runs every wait against fresh state,
and fences a running set_joints() so that any verb ends it. close() sends zero velocity
only when this Robot holds one; a Robot that sent nothing does not stop another program.
After close() stops re-sending a trajectory, the robot enters DAMP 2 s later. Neither the
robot nor the firmware checks command timestamps.
Who Holds Control
The robot gives the Cockpit priority over a paired gamepad, the gamepad over udp, and
udp over livekit. The state stream does not say who holds control, so a walk that does
not happen, with no fault and no refusal, is the first thing to check.
An idle paired gamepad locks the SDK out
A gamepad paired to the robot holds control while nobody touches it. Every velocity a script sends is dropped without a report. Unpair the gamepad on the Controller page of Asimov Manager before you run a script; see Gamepad.
Limits
The firmware caps vx and vy at 0.4 m/s and vyaw at 0.8 rad/s. Limits() defaults to
those caps and the SDK clamps every velocity to them before it leaves, so Sent.clamped is
True exactly when the robot would not have walked at the speed you asked for. A tighter
envelope is Robot(limits=Limits(vx=0.2, vy=0.2, vyaw=0.4)), MENLO_LIMITS="0.2,0.2,0.4",
or limits on a saved robot. Limits above the caps are sent as asked and the firmware clamps
them.
Arming
MOVE is accepted only from STAND held upright, gravity z below -0.87 (under 30 degrees of
tilt), for 0.5 s. A velocity sent earlier is not refused and not reported: the robot stays in
STAND while the hold runs out. stand() returns once the robot is armed, balance() in
STAND waits for arming before it sends, and robot.armed reads the same test the firmware
applies. set_velocity() does not wait for arming: it runs in MOVE only, and in STAND it
raises NotReadyError naming balance().
Without a gravity vector, arming goes unverified
A robot that reports no gravity gives preflight("move") and preflight("trajectory") the
unknown_gravity warning, and they pass in STAND without seeing arming. robot.armed is
None, and balance() sends without waiting. Check state.gravity before you rely on the
check in that case.
Faults
A critical alert, such as a fall, actuator over-temperature, battery protection, a joint past
its limit, lost actuator communication or a watchdog, makes the firmware latch DAMP. The
latch, and error_flags, stay until the firmware restarts. The SDK reports it as
State.faulted, reports faulted from preflight(), raises RobotFaultedError from every
command that checks the robot and from wait_until(), and drops a held velocity without
sending anything more.
Watchdogs
| Watchdog | Where | What happens |
|---|---|---|
| Velocity | the robot, udp and hybrid | zero velocity 2 s after the last velocity command; the robot stays in MOVE, balancing |
| Trajectory | the robot, every mode | DAMP 2 s after the last setpoint |
| Link | SDK, every mode | no state for 2 s: zero velocity if one is held, on_link_lost fires, every verb raises LinkLostError until close() and connect() |
A script that holds a velocity re-sends it at 10 Hz, so the velocity watchdog does not stop
it: end it with balance(), a duration, or close().
On a livekit connection, Asimov Edge stops a held velocity when the SDK sends zero or
leaves the room. A script that hangs while it holds a velocity keeps the robot walking until
LiveKit drops the participant. Bound every hold with a duration, and prefer udp or
hybrid for a control loop.
Check Before You Move
stand(), balance() in STAND, set_velocity(), set_joints() and trajectory() check
the robot before they send anything. A ready robot adds no delay. A problem that clears on
its own (no_state, stale_state, not_armed, or DAMP still reported just after a
stand()) is waited out for up to the command's timeout: 10 s for stand(), 5 s for
balance(), set_velocity() and set_joints(). trajectory() and
set_velocity(hold=False) check once and do not wait unless given a timeout. Any other
blocking problem raises NotReadyError at once, or RobotFaultedError for a latched fault,
and nothing is sent. The message names every blocking problem, its code and what fixes it:
not ready to move: the robot is in DAMP; stand() it first (wrong_mode)
not ready to move: the robot is in STAND; balance() it first (wrong_mode)
not ready to move: battery at 12 % (below 20 %) (battery_low); charge the battery
not ready to stand: the firmware latched DAMP (FALL_DETECTED); it stays latched until the firmware restarts (faulted)balance() in MOVE and damp() are never checked: both are how you make the robot do less.
from menlo.asimov import NotReadyError, Robot, RobotFaultedError, WaitTimeoutError
with Robot().connect() as robot:
try:
robot.stand()
robot.balance()
robot.set_velocity(vx=0.3, duration=3.0)
except RobotFaultedError as exc: # latched until the firmware restarts
print("faulted:", exc)
except NotReadyError as exc: # nothing was sent
print(exc)
if exc.has("battery_low"):
...
except WaitTimeoutError as exc: # sent, but the robot did not arm or reach MOVE in time
print(exc)RobotFaultedError is a NotReadyError, so except NotReadyError alone catches every
"the robot cannot do that now". exc.problems lists the blocking problems, exc.has(code)
tests one, and exc.preflight carries the whole check.
robot.preflight(action) is the same check without a command: it answers whether the robot
can "stand", "move" or run a "trajectory" right now, sends nothing and never raises
for a robot condition. check.py prints it for all three actions:
# The SDK counts the 0.5 s a robot in STAND must be upright to arm from its own samples.
time.sleep(0.6)
print(f"robot mode {robot.get_state().mode.name}, armed {robot.armed}")
for action in ("stand", "move", "trajectory"):
print(robot.preflight(action)) # "ready to move", or "not ready to move:" and why| Code | Blocks | Meaning | What clears it |
|---|---|---|---|
not_connected | yes | closed, link lost, or protocol mismatch | close() and connect() |
no_state | yes | connected with require_state=False and nothing reported | wait for the firmware |
stale_state | yes | latest state older than 0.5 s | a command waits for the next sample; otherwise check the link and the robot's UDP state target |
faulted | yes | a critical alert is latched, or the robot reports FAULT_DAMP | restart the firmware |
battery_protecting | yes | the battery is protecting itself | charge it or let it cool |
battery_low | yes | below 20 % | charge |
unknown_battery | warning | the robot reports no battery | |
joint_hot | yes | an actuator at or above 60 °C | let it cool |
unknown_joint_temp | warning | the robot reports no joint temperatures | |
wrong_mode | yes | stand from MOVE or UNKNOWN; move and trajectory from DAMP or UNKNOWN; set_velocity() also from STAND | from DAMP, stand() first; from STAND, balance() first; in MOVE the robot is already standing, so do not stand it: end a walk with balance() |
not_armed | yes | move or trajectory in STAND before the 0.5 s upright hold; the message gives the tilt in degrees | hold STAND upright; a command waits for it |
unknown_gravity | warning | the robot reports no gravity, so arming cannot be seen |
stand passes from DAMP or STAND; balance() runs the move check from STAND, which
passes once the robot is armed; set_velocity() passes in MOVE only; trajectory passes in
MOVE or an armed STAND. A command waits only for no_state, stale_state and not_armed;
every other blocking problem fails it at once, since waiting cannot clear it. Blocking
problems are listed first; a warning never blocks.
With no state at all, every action reports no_state or not_connected. Robot mode UNKNOWN
means the robot reports a mode the SDK does not know; every action then reports wrong_mode.
battery_low at 20 % is stricter than the firmware on purpose: the firmware only warns at
that level, and latches DAMP once the battery protects itself (battery_protecting).
joint_hot at 60 °C matches the firmware's warning; the firmware latches DAMP at 80 °C.
Shutdown
Leaving the with block calls close(): zero velocity if one is held, then the link drops.
The robot stays in MOVE, balancing in place, and the Cockpit or the gamepad can take it from
there. Call damp() at the end of a session only on a supported robot, hanging from its
gantry hook or seated on a bench: every actuator stops holding its position and a standing
robot falls.
damp.py asks before it sends, then waits for the robot to report DAMP:
# damp() is never refused, so there is no check here, only the question.
if not YES:
mode = robot.get_state().mode.name
question = f"Robot mode {mode}. Is the robot supported? Damp now? [y/N] "
if input(question).strip().lower() not in ("y", "yes"):
print("nothing sent")
sys.exit(0)
try:
robot.damp() # returns once the robot reports DAMP
except WaitTimeoutError as exc: # sent, but DAMP was not reported: the message says what next
print(exc)
sys.exit(1)
print(f"robot mode {robot.get_state().mode.name}")Turning the robot off is in Power.
Related
- Stopping the Robot: the E-Stop, pause, damp and cutting power
- Stand Up and Walk: the same sequence from the Cockpit
- Move the Robot: stand, balance, walk, and the check each command runs
- Control Joints: joint control on a supported robot
- Troubleshooting: each error and its fix
How is this guide?