Run on real arms
Take a script that works in the sim, as the quickstart does, to the arms on your bench: check the bench, bring the drives up, and take them down again without dropping an arm.
"""Checks the bench, brings its arm up on the real drives, homes it, and parks it before the drives are disabled."""
import sys
import anvil.control
import anvil.hw
import anvil.workcell
# The preset that is the bench: the arms, their variant and how they stand.
workcell = anvil.workcell.WorkcellConfig.OPENYAM_V2_SINGLE
robot = workcell.robot
# Read-only: brings the CAN links up, asks every drive and camera what it is, and moves nothing.
report = anvil.hw.check_readiness(workcell=workcell)
print(report.render())
if not report.ok:
sys.exit("the bench is not ready: fix each failed line above and run again")
hw = anvil.hw.DamiaoHw(buses=anvil.hw.buses(robot=robot))
hw.enable()
stop = anvil.control.CollisionStop(workcell=workcell)
trajectory_controller = anvil.control.JointTrajectoryController(robot=robot, collision_stop=stop)
with anvil.control.Runtime(hw=hw, controllers=[trajectory_controller]) as runtime:
runtime.start()
if runtime.realtime_refusal is not None:
print(f"the loop is not real-time: {runtime.realtime_refusal}")
handle = runtime.controller(kind=anvil.control.JointTrajectoryController)
runtime.activate(controller=handle)
try:
handle.play(trajectory=robot.home_trajectory(from_q=runtime.latest().q), timeout_s=30.0)
print(f"homed: {robot.actuator_summary}")
# The work goes here.
finally:
# Leaving the with block disables the drives, so park the arm where it can rest limp first. A faulted loop
# moves nothing more: support the arm by hand before the block ends.
if runtime.fault() is None:
runtime.activate(controller=handle)
handle.play(trajectory=robot.dehome_trajectory(from_q=runtime.latest().q), timeout_s=30.0)
1. Pick the workcell that is your bench
Each shipped bench is a class attribute of WorkcellConfig, named for the arms'
variant and how many stand there: OPENYAM_V2_SINGLE, OPENARM_V2_BIMANUAL, OPENYAM_V2_LEADER_FOLLOWER.
WorkcellConfig.preset_shapes() lists them all with their variant and
shape. If you do not know which your bench is, ask it: anvil.hw.Survey().detect() reads
every drive on the machine, moving nothing, and names the presets the drives could be.
A bench with cameras or a table is the preset with those added: append a CameraConfig
per camera and a KeepOutBox per obstacle, save
it, and load it in every script after.
2. Check the bench
check_readiness brings every CAN link up, then asks each drive and camera what it is.
It writes nothing and moves nothing, and takes about half a second. Bringing the links up runs sudo ip link set,
which anvil-robotics setup lets run without a password: run it once on a Linux machine that drives the arms, and it
also grants the control loop its real-time priority and installs a real-time kernel where the machine takes one. An
Anvil box arrives set up.
Print render(): one line per finding, each pass, warn, missing or fail. Go on only
when Report.ok is true:
- fail: the bench is not this workcell: a drive is another motor model than the description says, or not on Anvil firmware, a motor answers where the arm has none, an arm's drives say it is the other side. The line says what was found, where, and what to do.
- missing: a drive, bus or camera did not answer. By default that holds the run up too. To run without it, check
with
strict=Falseand enable withhw.enable(require_enabled=False); the arm comes up without that part. - warn: worth reading, and does not hold the run up.
It does not matter what the CAN links are called. buses finds each arm on the link its drives
answer on, by the identity record each drive carries.
3. Bring the drives up
DamiaoHw opens each arm's bus and enable switches the drives on.
From there, build the controllers and the Runtime as in the sim. Hand each controller the
one CollisionStop: a controller built without one sends its targets into a collision.
Collision stop and speed limits says what it does and does not
catch.
If Runtime.realtime_refusal is not None, the loop thread is not real-time
and may run late; the message says what would fix it.
4. Park the arms before the drives go off
Leaving the runtime's with block disables every drive, however the block is left: at its end, on an exception, on
Ctrl-C. A disabled arm is limp and falls under gravity. Bring the arms somewhere they can rest limp before the block
ends, in a finally so an exception cannot skip it.
RobotConfig.dehome_trajectory runs the homing run backwards, ending
at the row the homing starts from, the pose the arm's description gives a limp arm to settle in.
To stop the arms in place instead, Runtime.freeze holds each joint where it is, with
torque on, until a controller is activated again.
5. When a drive drops out
A drive that loses power or faults stops reading Enabled, and one that goes unanswered for five ticks in a row reads
CommunicationLoss instead. By default the loop drives on: every other drive keeps following its controller, and the
dropped drive's actuator says what it reads in each snapshot's status_names and status_faults, so a gripper that
browns out leaves both arms working and says so. Its joint's position is the last one it reported, and a controller
goes on using it.
To stop instead, enable with hw.enable(stop_on_dropout=True). Then the loop faults on the tick that finds a dropped
drive and writes nothing more: every drive still answering holds its last command, torque on. From then on, every
runtime call that drives the arms or reads their state, such as latest, activate, play and freeze, raises
HwFaultError, its message naming the drive and what it reads.
Runtime.fault() returns the same message without raising, and
Runtime.fault_snapshot() what the loop read on that tick, whose
status_faults say which drive dropped out.
That fault lasts the runtime's life, and nothing moves the arms again. Support the arm by hand, then let the script
leave the with block, which disables the drives that are still holding. Fix what dropped out, then run the script
again: a new DamiaoHw and a new runtime.