Menlo

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.

examples/read_state.pyView on GitHub ↗
# 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"
    )
FieldMeaning
state.modeMode.DAMP, Mode.STAND, Mode.MOVE, Mode.FAULT_DAMP or Mode.UNKNOWN
state.jointsone 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_posone radian value per actuator
state.gravitygravity projected into the body frame; z near -1 when upright
state.gyro, state.quat, state.euler, state.yawangular rate, orientation, and heading in radians counter-clockwise
state.batteryBattery(voltage_v, current_a, soc_percent, max_cell_temp_c, protection) or None
state.alerts, state.error_flags, state.faultedalerts and faults reported by the firmware
state.age_sseconds 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:

examples/check.pyView on GitHub ↗
# 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

check.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.transport

Joint 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.

CallbackArguments
robot.on_stateevery accepted State
robot.on_mode_change(before, after), both Mode
robot.on_alert(alert, change), with change "raised" or "cleared"
robot.on_link_lostthe LinkLostError

How is this guide?

On this page