Read State
One snapshot of everything the robot reports, the readiness check, and how to wait on it.
robot.get_state() returns the latest sample the robot reported, decoded: robot mode, joints, IMU,
battery, alerts. Reading it costs nothing, because the robot streams it anyway. A field the
robot does not report is None, never a guessed zero.
read_state.py is a tour of the state: robot mode, arming, what latched, battery, the
hottest actuators, gravity, state age and rate, alerts. It sends nothing and works in every
connection mode.
# robot.get_state() is the latest sample the robot sent. It is a snapshot: read it again
# for newer values. A field the robot does not report is None, never a guess.
s = robot.get_state()
# Robot mode: DAMP (no actuator holds a position), STAND (a held standing pose, no
# balance) or MOVE (the walking policy balances the robot, at zero or any velocity).
print(f"robot mode {s.mode.name}")
# Armed: the firmware accepts MOVE only after STAND has been held upright for 0.5 s.
# None means the SDK cannot tell (no gravity vector reported).
print(f"armed {robot.armed}")
# Faulted: a critical alert latched DAMP. The robot stays in DAMP, and error_flags stay
# set, until the firmware restarts. The preflight check names what latched.
print(f"faulted {s.faulted}")
for problem in robot.preflight("stand").problems:
if problem.code == "faulted":
print(f" {problem.message}")
# Battery, from the battery management system. None when the robot reports none.
if s.battery is not None:
b = s.battery
print(
f"battery {b.soc_percent:.0f} %, {b.voltage_v:.1f} V, {b.current_a:+.1f} A, "
f"warmest cell {b.max_cell_temp_c:.0f} °C, protecting {b.protecting}"
)
else:
print("battery not reported")
# Actuators: one Joint per actuator, with the firmware's name, position (rad), velocity
# (rad/s), current (A) and temperature (°C). The firmware warns at 60 °C and latches
# DAMP at 80 °C.
temps = sorted(((j.temp, j.name) for j in s.joints if j.temp is not None), reverse=True)
if temps:
hottest = ", ".join(f"{name} {temp:.0f} °C" for temp, name in temps[:HOTTEST])
print(f"hottest {hottest}")
else:
print("hottest actuator temperatures not reported")
# IMU: gravity as the body sees it. z is close to -1 when the robot is upright;
# upright is True below -0.8. euler is (roll, pitch, yaw) in rad from the IMU quaternion.
if s.gravity is not None:
gx, gy, gz = s.gravity
print(f"gravity ({gx:+.2f}, {gy:+.2f}, {gz:+.2f}), upright {s.upright}")
if s.euler is not None:
roll, pitch, yaw = s.euler
print(f"orientation roll {roll:+.2f}, pitch {pitch:+.2f}, yaw {yaw:+.2f} rad")
# Alerts the firmware reports now. Severity 0 is critical: it latches DAMP.
alerts = [f"{a.name}{' (critical)' if a.critical else ''}" for a in s.alerts]
print(f"alerts {', '.join(alerts) or 'none'}")
# Freshness: how old the sample is. Decisions to move are made on samples at most
# 0.5 s old; robot.preflight() reports stale_state beyond that.
print(f"state age {s.age_s * 1000:.0f} ms")
# The same fields as a stream, a few times a second.
for _ in range(STREAM_LINES):
time.sleep(STREAM_PERIOD_S)
s = robot.get_state()
now = time.monotonic()
rate = sum(1 for t in tuple(arrivals) if now - t <= 1.0)
battery = f"{s.battery.soc_percent:.0f} %" if s.battery else "n/a"
print(
f"{s.mode.name:5} armed {robot.armed} upright {s.upright} faulted {s.faulted} "
f"battery {battery} age {s.age_s * 1000:.0f} ms rate {rate:.0f} Hz"
)| Field | Meaning |
|---|---|
state.mode | Mode.DAMP, Mode.STAND, Mode.MOVE, Mode.FAULT_DAMP or Mode.UNKNOWN |
state.joints | one Joint(name, pos, vel, current, temp) per actuator, firmware order |
state.joint("L_Knee") | a joint by name; KeyError on an unknown name |
state.joint_pos | one radian value per actuator |
state.gravity | gravity projected into the body frame; z near -1 when upright |
state.gyro, state.quat, state.euler, state.yaw | angular rate, orientation, and heading in radians counter-clockwise |
state.battery | Battery(voltage_v, current_a, soc_percent, max_cell_temp_c, protection) or None |
state.alerts, state.error_flags, state.faulted | alerts and faults reported by the firmware |
state.age_s | seconds since this sample arrived |
Is It Ready
robot.preflight(action) reads the latest state and answers whether the robot can "stand",
"move" or run a "trajectory" right now. It sends nothing and never raises for a robot
condition. check.py prints all three:
# 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 whycheck.ok is True when no problem is blocking; check.problems lists them, blocking
first; check.has(code) tests one; str(check) is readable. Each code and what clears it
is in Check Before You Move.
stand(), balance() from STAND, set_velocity(), set_joints() and trajectory() run
the same check before they send. A problem that clears on its own (no_state, stale_state, not_armed) is waited
out for up to timeout seconds; any other raises NotReadyError, carrying .preflight and
.problems, with nothing sent. A latched fault raises RobotFaultedError, a subclass. Call
preflight() yourself when you want the answer without a command, as check.py does.
robot.armed is the arming test on its own: True in MOVE, or in STAND once the robot has
been upright for 0.5 s; False in DAMP or before that; None without state, closed, in
UNKNOWN, or in STAND with no gravity reported.
Wait on the Robot
Waits read the robot's own report, never a sleep. The code below moves the robot, so it belongs after the checklist and Standing and Balancing:
if robot.get_state().mode is Mode.DAMP:
robot.stand() # returns once the robot reports STAND and is armed
robot.balance() # returns once the robot reports MOVE
robot.set_velocity(vx=0.2, duration=2.0) # checks the robot, then sends
robot.wait_until(lambda s: s.mode is Mode.MOVE and s.upright, timeout=10.0)stand(), balance(), damp(), set_joints(wait=True) and wait_until read the robot's report and raise
WaitTimeoutError (also a TimeoutError) when the condition is not met in time, with
.last holding the last state seen; .sent on a command carries what went out. A stream
that goes quiet raises StateStaleError instead: a robot that did not reach the state and a
stream that stopped need different fixes. A fault is checked before the predicate and raises
RobotFaultedError, so a fall is never read as success. stale_after on wait_until
defaults to the link timeout of 2 s.
Upright
state.upright is True when gravity z is below -0.8, about 37 degrees of tilt, and None
when the robot reports no gravity: unknown and fallen are different answers. It is not the
arming test; the firmware arms at -0.87, under 30 degrees, and robot.armed reads that.
Faults
state.faulted is True when error_flags is set or any alert is critical (severity 0).
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. It stays
latched, and error_flags stay set, until the firmware restarts. The robot may report it as
Mode.FAULT_DAMP. Every command that checks the robot, and wait_until, raise
RobotFaultedError; preflight() reports faulted; nothing a script sends clears it.
alert.name is the firmware's name for an alert, alert.critical its severity test.
What the SDK Knows About the Body
info = robot.info
info.dof, info.joint_names # 25 and the firmware's actuator order on Asimov 1
info.capabilities # frozenset({'drive', 'state', 'battery', 'camera', ...})
info.protocol_version, info.limits, info.endpoint, info.transportJoint names come from menlo.asimov.robots.ASIMOV_1_BIPED_JOINTS, chosen by the reported
joint count. robot.has("camera") and robot.require("drive", "battery") test the
capabilities; the set is what this connection carries, so it holds no media on udp.
Callbacks
For event-driven code, register a callback instead of polling. Callbacks run on the
transport thread, the one that delivers state: keep them short. robot.damp() from a
callback, such as on_alert damping on an alert, sends DAMP and returns at once. The other
commands that wait for the robot, and wait_until(), raise RuntimeError there with nothing
sent; pass wait=False, or call them from your own thread.
| Callback | Arguments |
|---|---|
robot.on_state | every accepted State |
robot.on_mode_change | (before, after), both Mode |
robot.on_alert | (alert, change), with change "raised" or "cleared" |
robot.on_link_lost | the LinkLostError |
Related
- Move the Robot: what to do once the robot is ready
- Safety: every preflight code
- Reference: every field and type
How is this guide?