Move the Robot
stand, balance, set_velocity, the check each command runs first, and the velocity limits.
Walking is one verb, set_velocity(vx, vy, vyaw), held for a duration or until you end it.
Everything else on this page is about getting the robot into the robot mode that accepts it,
and ending a walk safely.
| Robot mode | What the robot does | Verb that reaches it |
|---|---|---|
| DAMP | every actuator compliant; the robot rests on a bench, hangs from its gantry hook, or folds to the ground | damp() |
| STAND | joints stiffened into the standing pose; no balance loop | stand() |
| MOVE | the walking policy balances and walks | balance() from an armed STAND |
The firmware accepts MOVE from STAND only once the robot is armed: STAND held upright for 0.5 s. In DAMP, the robot drops velocity commands and nothing reports it.
A walking robot needs clear floor
Support the robot for a first run, hanging from its gantry hook with both feet on the floor, and keep 2 m of clear floor ahead. Keep Asimov Manager open at the E-Stop; nothing in the SDK is an emergency stop. Work through Before You Run a Script and Standing and Balancing first.
Stand Up
stand() puts the robot in STAND: the actuators hold a standing pose by position control
alone, without a balance loop. Use it only to bring the robot out of DAMP. Call it on a robot
in DAMP that hangs from its gantry hook with both feet on the floor, as in
Stand Up and Walk, and never after a walk.
try:
# Checks the robot first. Returns once it reports STAND and has been upright for
# 0.5 s (armed): the firmware accepts MOVE only after that.
robot.stand()
except (NotReadyError, WaitTimeoutError) as exc:
print(exc) # what is wrong and what fixes it
sys.exit(1)
print(f"robot mode {robot.get_state().mode.name}, armed {robot.armed}")stand() checks the robot before it sends: the state is fresh, no fault is latched, the
robot is not in MOVE, and battery and actuator temperatures are within limits. It raises
NotReadyError with nothing sent otherwise, and the script prints the message: what is
wrong and what fixes it. Once sent, stand() reads the robot's own report and returns when
the robot is armed. It raises WaitTimeoutError when that takes more than 10 s and
RobotFaultedError when a fault latches meanwhile.
Balance
balance() puts the robot in MOVE at zero velocity: the walking policy balances it in
place, and this is the only robot mode in which the robot stands free. Call it with the
robot still supported, and slacken the gantry only after it returns.
try:
# Checks the robot first when it is in STAND. Returns once it reports MOVE.
robot.balance()
except (NotReadyError, WaitTimeoutError) as exc:
print(exc) # what is wrong and what fixes it
sys.exit(1)
print(f"robot mode {robot.get_state().mode.name}, balancing in place")From STAND, balance() runs preflight("move") first. Before the arming hold has passed it
waits, up to timeout (5 s by default), then sends zero velocity, which is what puts the
robot in MOVE, and returns once the robot reports MOVE; WaitTimeoutError when it does not
within timeout. In DAMP, with a fault latched or with a low battery it raises
NotReadyError at once and sends nothing. In MOVE, balance() is never checked: it ends any
held velocity and sends zero, so it is also how a walk ends.
The firmware reports STAND at once but accepts MOVE only after STAND has been held upright,
gravity z below -0.87, for 0.5 s. A velocity sent before that is neither refused nor
reported: the robot stays in STAND. robot.armed is the arming test as a property: True
once armed or in MOVE, False in DAMP or before the hold has passed, None with no state,
when the Robot is closed, in robot mode UNKNOWN, or in STAND when the robot reports no
gravity. Without gravity, the check in STAND passes with an unknown_gravity
warning: the SDK cannot see arming, so it does not claim it, and the zero velocity goes out
unverified.
Walk
set_velocity() runs in MOVE only. It checks the robot first and raises NotReadyError
with nothing sent when it is in DAMP (stand() it first) or STAND (balance() it first),
when a fault is latched, or when the battery or an actuator is out of limits. The codes it
can carry are in Check Before You Move.
try:
# Checks the robot first: it must be in MOVE. Held and re-sent at 10 Hz for
# DURATION_S, then zero velocity; returns after that.
sent = robot.set_velocity(vx=VX, vy=VY, vyaw=VYAW, duration=DURATION_S)
except NotReadyError as exc: # nothing was sent, or (RobotFaultedError) a fault ended it
print(exc)
sys.exit(1)
if sent.clamped:
print("the SDK reduced the speed to the robot's limits:", sent.command)
print(f"robot mode {robot.get_state().mode.name}, balancing in place")Axes: vx forward in m/s, vy left in m/s, vyaw counter-clockwise in rad/s. A walk is
set_velocity(vx=0.2, duration=3.0), a strafe set_velocity(vy=0.15, duration=3.0), a turn
on the spot set_velocity(vyaw=0.3, duration=3.0); combine them in one call.
Hold a Velocity
By default set_velocity() needs a duration: the SDK re-sends the velocity at 10 Hz for
that long, sends zero, and returns once the zero has gone out (wait=True). With
wait=False it returns at once with a Sent and the hold runs in the background, with or
without a duration, until something ends it:
| The hold ends when | What is sent |
|---|---|
duration seconds pass | zero velocity, by the SDK |
balance() | zero velocity |
another set_velocity() | the new velocity |
stand(), damp(), trajectory(), set_joints() | that verb |
close(), or the end of the with block | zero velocity, then the link drops |
| the link is lost (no state for 2 s) | zero velocity; every verb raises until close() and connect() |
| the firmware latches a fault or restarts | nothing; the hold is dropped |
The next verb or the end of the with block cuts an unexpired wait=False hold short, so a
script that calls set_velocity(duration=3.0, wait=False) and returns does not walk for 3 s.
set_velocity(hold=False) sends exactly one packet and re-sends nothing: your own loop is
the clock, as in stream_velocity.py. It takes no duration and no wait, and the check
runs once per call, so a ready robot adds no delay between packets.
On udp and hybrid, the robot zeroes the velocity 2 s after the last packet it received;
it stays in MOVE, balancing. On livekit, Asimov Edge stops a held velocity when the SDK
sends zero or leaves the room, so always bound a hold with a duration; see
Watchdogs.
Drive from the Keyboard
keyboard.py turns key presses into short bounded holds of about 0.3 s, so releasing a key
stops the robot. w/s walk forward and back, a/d strafe, q/e turn, space sends
balance(), t stands (from DAMP only; a refusal shows on the status line), b damps after
asking, and x or Ctrl-C quits with zero velocity and close(). From DAMP, t then space
brings the robot to STAND and then to MOVE. A one-line status shows the robot mode, arming,
battery and the last command.
The keyboard walks the robot backward, sideways and round as well as forward: keep clear floor all around it, not only ahead.
while True:
show(robot, last)
key = next_key(TICK_S)
if key is None:
continue
if key in QUIT:
break
if key in MOVES:
last = move(robot, key)
elif key == " ":
last = balance(robot)
elif key == "t":
last = stand(robot)
elif key == "b":
last = confirm_and_damp(robot, next_key)End a Walk
balance() sends zero velocity. The robot stays in MOVE and the walking policy keeps it
balanced in place. End a walk this way; a set_velocity() with a duration does the same
when the duration runs out. stand() after walking removes the balance loop and the robot
falls; the SDK refuses it in MOVE.
To finish a session in DAMP, support the robot first, hanging from its gantry hook or seated
on a bench, then run damp.py; see Shutdown.
Turn by Heading
State.yaw is the heading in radians, counter-clockwise. Turn until the heading has changed
by an angle instead of guessing a duration:
import math
from menlo.asimov import Robot
with Robot().connect() as robot:
before = robot.get_state().yaw
robot.set_velocity(vyaw=0.3, duration=15.0, wait=False) # checks the robot first; NotReadyError when it cannot walk
robot.wait_until(
lambda s: s.yaw is not None
and abs(math.remainder(s.yaw - before, math.tau)) >= math.radians(90),
timeout=15.0,
)
robot.balance()math.remainder keeps the difference in (-pi, pi], so the wrap at pi does not count as a
full turn. wait_until checks for a fault before it evaluates the predicate and raises
StateStaleError when the stream goes quiet, so a robot that fell or stopped talking is
never read as "turned".
Limits
The Motion Control Board firmware caps velocity at 0.4 m/s forward and sideways and 0.8 rad/s
turning. Limits() defaults to those caps, so Sent.clamped is True exactly when the
robot would not have walked at the speed you asked for; sent.command is the velocity that
went out. Lower limits keep a script inside a smaller envelope:
from menlo.asimov import Limits, Robot
robot = Robot(limits=Limits(vx=0.2, vy=0.2, vyaw=0.4))Robot(limits=) wins over MENLO_LIMITS="vx,vy,vyaw", which wins over the saved robot's
[robots.NAME.limits], which wins over the defaults. Values above the firmware caps are sent
as asked and the firmware clamps them. Negative or non-finite limits raise ValueError.
Related
- Safety: the stand, balance, walk, damp sequence and why
- Read State:
robot.armed,wait_until, faults - Stand Up and Walk: the same robot modes from the Cockpit
- Reference:
set_velocity,balance,stand,damp,Limits - Troubleshooting:
NotReadyError,WaitTimeoutErrorandRobotFaultedError
How is this guide?