anvil.hw
anvil.hw
The hw interfaces a run drives the arms through, the bench tools for Damiao motors, and the readiness check.
SimHw and DamiaoHw are the two hw interfaces: a robot in an anvil.sim.World, and real Damiao drives on CAN.
Both stand in the same place, drives to enable, a state to read and a command to write, with no loop around them: a
script drives one tick by tick, or hands it to an anvil.control.Runtime, which runs the loop. buses says where a
robot's drives are for a DamiaoHw, and check_readiness, or a Survey asked several questions, says whether they are
there and fit to run before anything is commanded.
DamiaoBus is one CAN interface held at the bench: what answers on it, what each motor is, and the writes that
configure and zero a motor. ConfigureSequence walks an arm's joints one motor at a time, and ZeroLimitSweep and
ZeroAuto zero a whole arm, by hand or by driving it into its stops. A bench result with no class of its own comes back
as a dict, its keys named on the call that returns it.
RealHand is a RealHand L6 on a CAN link, its own or an arm's, written its six angles directly and never part of a run;
RealHand.scan finds which links the hands answer on.
Classes:
| Name | Description |
|---|---|
HwInterface |
What a |
ResolvedMotor |
One motor of a |
ResolvedBus |
One arm's bus as |
Finding |
One line of a |
Report |
What a readiness check found, from |
SimHw |
The sim hw interface: the robot of an |
DamiaoHw |
Real Damiao drives over |
DamiaoMotor |
A motor on a bus, by the address it listens on and the one it replies from. |
BusScan |
What |
DamiaoBus |
One CAN interface held at the bench, and the bench operations on the motors on it. |
Survey |
What this host's drives are, read once and held: every CAN link it has and every joint address on each. |
ConfigureSequence |
Every joint of an arm configured in turn, one motor on the bus at a time. |
ZeroLimitSweep |
An arm held passive while someone sweeps each joint through its whole range by hand, then zeroed from it. |
ZeroAuto |
An arm zeroed by driving each joint into its hard stops by itself, from rest, in about four minutes. |
FoundRealHand |
A RealHand L6 answering on a CAN link, as |
RealHand |
A RealHand L6 on a CAN link, written its six angles directly. |
Functions:
| Name | Description |
|---|---|
torque_full_scales_nm |
Returns each actuator's torque full scale in newton meters, actuator order: what a |
buses |
Returns |
check_readiness |
Returns whether the machine on the bench is |
status_names |
Returns the first |
status_faults |
Returns whether each of the first |
HwInterface
Bases: Part
What a anvil.control.Runtime drives the arms through: an anvil.hw.SimHw or an anvil.hw.DamiaoHw.
It is the type a runtime takes its hw as. Build one of the two, never this.
Methods:
| Name | Description |
|---|---|
close |
Frees the part now rather than once it is collected. |
close
close() -> None
Frees the part now rather than once it is collected.
A hw interface lets go of its buses, disabling the drives it enabled. A part a runtime took is the runtime's to free, and closing it does nothing.
ResolvedMotor
dataclass
ResolvedMotor(
actuator: int, joint: str, slave_id: int, master_id: int
)
One motor of a ResolvedBus: the actuator it drives and the addresses it listens and replies on.
DamiaoHw scales the motor's frames by the maxima the motor itself holds, read off it at DamiaoHw.enable.
Attributes:
| Name | Type | Description |
|---|---|---|
actuator |
int
|
Where the motor's joint sits in an |
joint |
str
|
The joint's name in the arm's description, e.g. |
slave_id |
int
|
The address the motor listens on, 0 to 255. |
master_id |
int
|
The address the motor replies from, 0 to 255. |
actuator
instance-attribute
actuator: int
Where the motor's joint sits in an anvil.control.JointState and an anvil.control.RobotCommand.
joint
instance-attribute
joint: str
The joint's name in the arm's description, e.g. "joint_3" or "gripper".
slave_id
instance-attribute
slave_id: int
The address the motor listens on, 0 to 255.
master_id
instance-attribute
master_id: int
The address the motor replies from, 0 to 255.
ResolvedBus
dataclass
ResolvedBus(interface: str, motors: list[ResolvedMotor])
One arm's bus as DamiaoHw takes it: the CAN interface the arm answers on, and its motors.
buses returns one per arm; edit one before constructing a DamiaoHw to drive an arm somewhere else.
Attributes:
| Name | Type | Description |
|---|---|---|
interface |
str
|
The CAN interface, e.g. |
motors |
list[ResolvedMotor]
|
The arm's motors, chain joints in description order, gripper last. |
interface
instance-attribute
interface: str
The CAN interface, e.g. "can0", or a bus name a workcell declares, e.g. "follower_l".
DamiaoHw resolves a declared name to the link this host has for it when it opens the bus.
motors
instance-attribute
motors: list[ResolvedMotor]
The arm's motors, chain joints in description order, gripper last.
Finding
dataclass
Finding(
severity: str,
holds_up: bool,
subject: str,
detail: str,
arm: str | None,
joint: str | None,
interface: str | None,
camera: str | None,
)
One line of a Report: what was looked at, and what was found.
Attributes:
| Name | Type | Description |
|---|---|---|
severity |
str
|
How much it matters: |
holds_up |
bool
|
Whether it holds the run up: a failure, or a part missing in a strict check. |
subject |
str
|
What it is about, e.g. |
detail |
str
|
What was found, in the terms an operator would act on. |
arm |
str | None
|
The arm it concerns, by its name in the workcell, or |
joint |
str | None
|
The joint it concerns, by its name in the arm's description, or |
interface |
str | None
|
The CAN interface it concerns, by its own name, e.g. |
camera |
str | None
|
The camera it concerns, by its name in the workcell, or |
severity
instance-attribute
severity: str
How much it matters: "pass", "warn", "missing" (a part that did not answer) or "fail".
holds_up
instance-attribute
holds_up: bool
Whether it holds the run up: a failure, or a part missing in a strict check.
subject
instance-attribute
subject: str
What it is about, e.g. "left" or "left joint_3".
detail
instance-attribute
detail: str
What was found, in the terms an operator would act on.
arm
instance-attribute
arm: str | None
The arm it concerns, by its name in the workcell, or None where it concerns no arm.
joint
instance-attribute
joint: str | None
The joint it concerns, by its name in the arm's description, or None where it concerns no joint.
interface
instance-attribute
interface: str | None
The CAN interface it concerns, by its own name, e.g. "can0", or None where it concerns none.
camera
instance-attribute
camera: str | None
The camera it concerns, by its name in the workcell, or None where it concerns no camera.
Report
dataclass
Report(
ok: bool,
summary: str,
findings: list[Finding],
hardware: list[dict[str, Any]],
)
What a readiness check found, from check_readiness or Survey.check: a verdict and every finding behind it.
Printing it prints summary; render() is the whole report. A warning is worth reading and does not hold a run up.
Methods:
| Name | Description |
|---|---|
render |
Returns the whole report as an operator reads it, one marked line per finding. |
failures |
Returns the findings that failed, for a script that wants to say why it is stopping. |
missing |
Returns the findings of parts that did not answer, which a lenient start comes up without. |
Attributes:
| Name | Type | Description |
|---|---|---|
ok |
bool
|
Whether the workcell can run: false when a finding failed, or when a part is missing and the check was strict. |
summary |
str
|
The verdict in a line or two: what printing the report shows. |
findings |
list[Finding]
|
Every finding, in the order the check made them. |
hardware |
list[dict[str, Any]]
|
The physical robot it was judged on, one dict per arm in the robot's order. |
ok
instance-attribute
ok: bool
Whether the workcell can run: false when a finding failed, or when a part is missing and the check was strict.
summary
instance-attribute
summary: str
The verdict in a line or two: what printing the report shows.
findings
instance-attribute
findings: list[Finding]
Every finding, in the order the check made them.
hardware
instance-attribute
hardware: list[dict[str, Any]]
The physical robot it was judged on, one dict per arm in the robot's order.
Each holds arm, the arm's name in the workcell; bus, the bus the workcell declares for it; interface, the CAN
link it was placed on, None for none, which its finding says why; and motors, every motor that answered there,
each a dict as Survey.links describes one.
What an anvil.recorder.Recorder given the report writes into each episode.
render
render() -> str
Returns the whole report as an operator reads it, one marked line per finding.
failures
failures() -> list[Finding]
Returns the findings that failed, for a script that wants to say why it is stopping.
missing
missing() -> list[Finding]
Returns the findings of parts that did not answer, which a lenient start comes up without.
SimHw
SimHw(world: World)
Bases: HwInterface
The sim hw interface: the robot of an anvil.sim.World, driven there, standing where a DamiaoHw stands.
The world comes first and says the robot, the scene and the clock; this takes the robot over where it stands and
drives its simulated drives, which come up disabled and saturate at their motor model's maxima. Drive it tick by
tick with read and write, or hand it to an anvil.control.Runtime, which takes it over. Closing it, or letting
it be collected, disables its drives and leaves the robot in the world where it falls; a server the world started
runs on while this does. Every method but close raises anvil.OperationError once it is closed, or once a runtime
has taken it.
Once the sim stops serving the robot (its server went away, let go of the robot, or did not answer within a
minute) enable, disable, read and write raise anvil.UnreachableError saying why, and send nothing more:
take the robot over again with a new SimHw.
Takes over the robot standing in world and waits for its first state, its drives disabled.
Its cameras go on watching, as a loop comes up on an arm already bolted down. A world serves one SimHw at a
time, so while another drives it this waits until that one lets go.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
world
|
World
|
The world whose robot to drive. |
required |
Raises:
| Type | Description |
|---|---|
OperationError
|
|
Methods:
| Name | Description |
|---|---|
close |
Disables the drives and lets go of the robot, which stays in the world where it falls. |
enable |
Enables the drives, which come up disabled, as the hardware's do. |
disable |
Disables the drives: the arm goes limp and falls under gravity. |
read |
Returns the latest state the sim sent, as |
write |
Sends one tick's commands, blocking in a lockstep world until the tick they drive has been stepped. |
Attributes:
| Name | Type | Description |
|---|---|---|
lockstep_hz |
int | None
|
The rate a lockstep world steps at, in hertz, or |
motors |
list[str]
|
The motors the sim drives, in actuator order, each |
motor_addresses |
list[tuple[str, int, int]]
|
A |
lockstep_hz
property
lockstep_hz: int | None
The rate a lockstep world steps at, in hertz, or None for a world that keeps its own clock.
motors
property
motors: list[str]
The motors the sim drives, in actuator order, each <arm>_<joint> by the workcell's and description's names.
For example "follower_joint_1", or "follower_gripper".
motor_addresses
property
motor_addresses: list[tuple[str, int, int]]
A (bus, slave_id, master_id) tuple per motor, in actuator order, at the addresses the robot declares.
The sim is never told the addresses; these are what a scan of the metal would find answering. Sends nothing.
close
close() -> None
Disables the drives and lets go of the robot, which stays in the world where it falls.
A server the world started stops once this and the world are both closed or collected. Closing twice does nothing.
enable
enable() -> None
Enables the drives, which come up disabled, as the hardware's do.
Raises:
| Type | Description |
|---|---|
UnreachableError
|
The sim did not enable them, or has stopped serving the robot. |
disable
disable() -> None
Disables the drives: the arm goes limp and falls under gravity.
Raises:
| Type | Description |
|---|---|
UnreachableError
|
The sim did not disable them, or has stopped serving the robot. |
read
read() -> tuple[int, list[float], list[float]]
Returns the latest state the sim sent, as (t_ns, q, qd), without waiting for one.
t_ns is the world's simulated time, in nanoseconds, not the host's clock. q is each motor's position in
radians and qd its velocity in radians per second, one entry per motor in actuator order. A lockstep world
sends a state for each write; a free-running one sends them at its own pace, and this takes the newest.
Raises:
| Type | Description |
|---|---|
UnreachableError
|
The sim has stopped serving the robot. |
write
write(robot_command: RobotCommand) -> None
Sends one tick's commands, blocking in a lockstep world until the tick they drive has been stepped.
A lockstep world steps one tick here, waiting up to a minute for it; a free-running one has already moved on
and picks the commands up at its next tick. Only the first len(motors) joint commands are sent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
robot_command
|
RobotCommand
|
One command per actuator. |
required |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
A value in one of the first |
UnreachableError
|
The server went away, let go of this robot, or did not step a lockstep tick within a minute, now or before. |
DamiaoHw
DamiaoHw(buses: Sequence[ResolvedBus])
Bases: HwInterface
Real Damiao drives over SocketCAN, one CAN interface per arm.
Get its buses from buses, check the machine with check_readiness, then construct it, enable it and drive it
tick by tick with write and read, or hand it to an anvil.control.Runtime, which takes it over. Closing it, or
letting it be collected, disables the drives and closes the buses. Every method raises anvil.OperationError once
it is closed, or once a runtime has taken it.
A drive enable enabled that stops reading Enabled, as one that lost power, timed out, went silent or faulted
does, drops its joint, so from the write that finds it until the next enable, read, state and write
raise anvil.UnreachableError naming each such drive and what it reads; a runtime driving it faults, and the
other drives hold their last command.
Opens each arm's bus and takes the arm on it as declared, enabling nothing.
Nothing is asked of the drives: whether they are there and agree with the declaration is check_readiness's
question, asked before this. A listed motor that does not answer, or one on a bus that would not open, is
driven all the same and reads CommunicationLoss once silent; a bus that would not open is tried again at
enable. Each bus is held while this is, so no other program, and no DamiaoBus, can open it meanwhile.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
buses
|
Sequence[ResolvedBus]
|
One per arm, as |
required |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
An actuator that is negative, or an address outside 0 to 255; |
Methods:
| Name | Description |
|---|---|
enable |
Enables the drives and checks they took it, as is needed before commanding them or handing this to a runtime. |
disable |
Disables the drives: the arm is unpowered afterwards and falls under gravity. |
read |
Returns the state the last |
state |
Returns the state |
write |
Sends one tick's commands and takes the replies already waiting, without blocking. |
close |
Frees the part now rather than once it is collected. |
Attributes:
| Name | Type | Description |
|---|---|---|
motor_addresses |
list[tuple[str, int, int]]
|
A |
aliased |
list[str]
|
Each bus this host had under another name, as |
motor_addresses
property
motor_addresses: list[tuple[str, int, int]]
A (bus, slave_id, master_id) tuple per motor this drives, in the order the buses were given.
bus is the interface each was opened on, or tried, e.g. "can0". Sends nothing, so it says nothing of
whether they answer: state()'s status does.
aliased
property
aliased: list[str]
Each bus this host had under another name, as "follower_l -> can0"; empty where every name was a link.
enable
enable(
require_enabled: bool = True,
stop_on_dropout: bool = False,
) -> None
Enables the drives and checks they took it, as is needed before commanding them or handing this to a runtime.
A drive not on Anvil firmware is never enabled. Each other drive is first put in MIT mode until it is powered off, and its scaling maxima are read off it; one that answers the enable with a latched fault is cleared and enabled again. A drive that did not enable ignores the commands the loop is about to send, so the arm would be driven from a joint that cannot answer; one that has never answered is sent none until an enable hears it, so it is never driven toward a position nothing measured.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
require_enabled
|
bool
|
Whether a drive left not enabled is an error. False brings the robot up without it: the
drives that could enable are, the rest say what they are in |
True
|
stop_on_dropout
|
bool
|
Whether a drive that stops reading |
False
|
Raises:
| Type | Description |
|---|---|
OperationError
|
With |
disable
disable() -> None
Disables the drives: the arm is unpowered afterwards and falls under gravity.
A drive that was enabled and does not confirm the disable is logged as a warning, since it may still hold its last command with torque on.
read
read() -> tuple[int, list[float], list[float]]
Returns the state the last write collected, as (t_ns, q, qd), without touching the bus.
t_ns is when the last exchange with the drives began, a write, an enable or a disable, in nanoseconds on the
host's monotonic clock, time.monotonic_ns(); 0 before the first. q is each actuator's position in radians
and qd its velocity in radians per second. A write takes the replies already waiting rather than waiting for
its own, so the state is the previous tick's.
state
state() -> JointState
Returns the state read returns, whole: torques, each actuator's drive status and its temperatures too.
write
write(robot_command: RobotCommand) -> None
Sends one tick's commands and takes the replies already waiting, without blocking.
A motor that has not answered for five writes in a row reads CommunicationLoss in state()'s status.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
robot_command
|
RobotCommand
|
One command per actuator. |
required |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
A value in one of the first |
UnreachableError
|
A drive that was enabled no longer reads |
close
close() -> None
Frees the part now rather than once it is collected.
A hw interface lets go of its buses, disabling the drives it enabled. A part a runtime took is the runtime's to free, and closing it does nothing.
DamiaoMotor
dataclass
DamiaoMotor(slave_id: int, master_id: int)
A motor on a bus, by the address it listens on and the one it replies from.
What DamiaoBus.scan finds, and what every per-motor call of a DamiaoBus takes.
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
An address outside 0 to 255. |
Attributes:
| Name | Type | Description |
|---|---|---|
slave_id |
int
|
The address the motor listens on, 0 to 255. |
master_id |
int
|
The address the motor replies from, 0 to 255. |
slave_id
instance-attribute
slave_id: int
The address the motor listens on, 0 to 255.
master_id
instance-attribute
master_id: int
The address the motor replies from, 0 to 255.
BusScan
dataclass
BusScan(found: list[DamiaoMotor], stopped_by: str | None)
What DamiaoBus.scan found on a bus.
Attributes:
| Name | Type | Description |
|---|---|---|
found |
list[DamiaoMotor]
|
Every motor that answered, by ascending slave address. |
stopped_by |
str | None
|
Why the scan ended early, or |
found
instance-attribute
found: list[DamiaoMotor]
Every motor that answered, by ascending slave address.
stopped_by
instance-attribute
stopped_by: str | None
Why the scan ended early, or None where it did not.
Set where the bus stopped accepting frames, which is what an interface that is up but unplugged looks like.
DamiaoBus
DamiaoBus(interface: str)
Bases: Handle
One CAN interface held at the bench, and the bench operations on the motors on it.
Every per-motor call takes the motor's addresses, a DamiaoMotor, from scan(), from bind() or written out. The
interface is held while this is open: no other program, and no other DamiaoBus or DamiaoHw, can open it
meanwhile, so nothing else talks on the wire. It is let go when this is closed or collected, or when a zeroing run
takes it over, whether or not the run then starts. Every method but close raises anvil.OperationError once it
is closed, and every one but close and interface once a zeroing run has taken it.
Opens interface and holds it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
interface
|
str
|
A CAN interface, e.g. |
required |
Raises:
| Type | Description |
|---|---|
NotFoundError
|
This host has no such interface. |
BusyError
|
Another program, or another bus or hw interface in this one, holds the interface; the message names the holder. |
OperationError
|
The interface would not open for another reason, which the message names. |
Methods:
| Name | Description |
|---|---|
available |
Returns every CAN interface this host has, by name, sorted, reading |
scan |
Returns every motor that answers on the bus, asking every address and writing nothing. |
bind |
Returns which of the arm's joints answer on the bus where the description puts them, writing nothing. |
versions |
Returns the motor's version registers, writing nothing. |
identity |
Returns the motor's identity record, and whether its calibration still matches it, writing nothing. |
registers |
Returns every register the protocol names, read off the motor one at a time, writing nothing. |
state |
Returns what the motor reports about itself, decoded by the scaling maxima read off it, writing nothing. |
set_mit_mode |
Puts the motor into MIT mode, the only mode the control stack commands in, moving nothing. |
zero |
Zeroes the motor where it stands and saves it to flash, disabling it first but moving nothing itself. |
configure |
Configures whatever motor answers on the bus: its addresses and, for a joint, MIT mode and identity record. |
stamp_version |
Stamps the arm's version into the identity record of each of its motors on the bus, reading each back. |
enable_limp |
Enables |
disable |
Disables |
zero_limit_sweep |
Returns a |
zero_auto |
Returns a |
close |
Frees the handle now rather than once this object is collected. Closing twice does nothing. |
Attributes:
| Name | Type | Description |
|---|---|---|
interface |
str
|
The interface that was opened: what the name it was given resolved to on this host, e.g. |
interface
property
interface: str
The interface that was opened: what the name it was given resolved to on this host, e.g. "can0".
available
staticmethod
available() -> list[str]
Returns every CAN interface this host has, by name, sorted, reading /sys and opening nothing.
scan
scan() -> BusScan
Returns every motor that answers on the bus, asking every address and writing nothing.
bind
bind(description: ArmDescription) -> dict[str, Any]
Returns which of the arm's joints answer on the bus where the description puts them, writing nothing.
Only the description's addresses are asked, so a motor that merely shares the bus is never touched; the maxima
of each one that answered as described are read off it. A gripper whose description names an alternate_motor
is that motor where it answers and the gripper's own address does not.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
description
|
ArmDescription
|
The arm whose joints to look for. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict of |
versions
versions(motor: DamiaoMotor) -> dict[str, str]
Returns the motor's version registers, writing nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motor
|
DamiaoMotor
|
The motor to ask. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
A dict of |
Raises:
| Type | Description |
|---|---|
UnreachableError
|
The motor did not answer. |
OperationError
|
A register held something that is not text. |
identity
identity(motor: DamiaoMotor) -> dict[str, Any]
Returns the motor's identity record, and whether its calibration still matches it, writing nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motor
|
DamiaoMotor
|
The motor to ask. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict whose |
registers
registers(motor: DamiaoMotor) -> list[dict[str, Any]]
Returns every register the protocol names, read off the motor one at a time, writing nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motor
|
DamiaoMotor
|
The motor to read. |
required |
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
One dict per register: |
state
state(motor: DamiaoMotor) -> dict[str, Any]
Returns what the motor reports about itself, decoded by the scaling maxima read off it, writing nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motor
|
DamiaoMotor
|
The motor to ask. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict of |
Raises:
| Type | Description |
|---|---|
UnreachableError
|
The motor did not answer its scaling registers or the state request. |
OperationError
|
Its scaling registers hold a maximum that is zero, negative or not a number. |
set_mit_mode
set_mit_mode(
motor: DamiaoMotor, save: bool
) -> dict[str, Any]
Puts the motor into MIT mode, the only mode the control stack commands in, moving nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motor
|
DamiaoMotor
|
The motor to set. |
required |
save
|
bool
|
Whether to commit the mode to flash, so it survives a power cycle. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict of |
zero
zero(motor: DamiaoMotor, kind: str) -> dict[str, Any]
Zeroes the motor where it stands and saves it to flash, disabling it first but moving nothing itself.
A zero is only accepted while the motor is disabled, so a motor holding a joint up lets go of it here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motor
|
DamiaoMotor
|
The motor to zero. |
required |
kind
|
str
|
|
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict whose |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
UnreachableError
|
The motor did not answer its scaling registers. |
OperationError
|
Its scaling registers hold a maximum that is zero, negative or not a number. |
configure
configure(
gripper_only: bool,
allow_multiple: bool,
save: bool,
master_id: int | None = None,
slave_id: int | None = None,
description: ArmDescription | None = None,
motor: str | None = None,
side: str | None = None,
role: str | None = None,
) -> dict[str, Any]
Configures whatever motor answers on the bus: its addresses and, for a joint, MIT mode and identity record.
Named as a joint, motor of description, the motor takes that joint's addresses, MIT mode and a fresh
identity record naming side and role; master_id and slave_id override the joint's addresses, or set
just those where no joint is named. The bus is scanned first. Writes to the motors; moves nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gripper_only
|
bool
|
Whether the record is a standalone gripper's rather than the arm's. |
required |
allow_multiple
|
bool
|
Whether to configure every motor that answers rather than refuse more than one. |
required |
save
|
bool
|
Whether to commit the writes to flash. |
required |
master_id
|
int | None
|
The reply address to set, 0 to 255, or |
None
|
slave_id
|
int | None
|
The listening address to set, 0 to 255, or |
None
|
description
|
ArmDescription | None
|
The arm the motor belongs to, or |
None
|
motor
|
str | None
|
The joint to configure the motor as, e.g. |
None
|
side
|
str | None
|
|
None
|
role
|
str | None
|
|
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict of |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
UnreachableError
|
No motor answered on the bus. |
OperationError
|
More than one answered without |
stamp_version
stamp_version(
description: ArmDescription,
) -> list[dict[str, Any]]
Stamps the arm's version into the identity record of each of its motors on the bus, reading each back.
What changes when an arm is rebuilt as another version of its family, as turning an OpenYAM's base plate
switches it between openyam/v1 and openyam/v2, so detecting the arm reads the new build. Only the version
changes, and only in records that name the arm's family; a gripper stamped as a gripper of its own keeps its
record. Every record is read before any is written, so an arm that is not whole is refused with nothing
changed, and J1 is written last, so a run interrupted part-way is finished by running it again. The UUID a
record names a motor by carries its version, so each motor stamped is named anew. Writes to the motors; moves
nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
description
|
ArmDescription
|
The arm as it is now built, whose version to stamp. |
required |
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
One dict per motor, J1 last, of |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
The arm's family has no identity record, or a chain motor's record does not read or names another family. |
UnreachableError
|
A chain motor did not answer where the description puts it. |
enable_limp
enable_limp(motors: Sequence[DamiaoMotor]) -> None
Enables motors limp, lights green, commanded no stiffness, no damping and no torque, so nothing moves.
The arm hangs as it did switched off; what shows someone which arm is on the bus. Writes no flash.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motors
|
Sequence[DamiaoMotor]
|
The motors to enable. |
required |
Raises:
| Type | Description |
|---|---|
OperationError
|
A motor's scaling maxima would not read or it would not enable; every one is disabled again, and the message names it. |
disable
disable(motors: Sequence[DamiaoMotor]) -> None
Disables motors, each as far as it answers: one that does not is off already, or gone.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motors
|
Sequence[DamiaoMotor]
|
The motors to disable. |
required |
zero_limit_sweep
zero_limit_sweep(
description: ArmDescription,
allow_missing: bool,
side: str | None = None,
motors: Sequence[str] | None = None,
) -> ZeroLimitSweep
Returns a ZeroLimitSweep of the arm on this bus, which takes the bus over; see that class.
zero_auto
zero_auto(
description: ArmDescription,
side: str | None = None,
motors: Sequence[str] | None = None,
) -> ZeroAuto
Returns a ZeroAuto run of the arm on this bus, which takes the bus over; see that class.
close
close() -> None
Frees the handle now rather than once this object is collected. Closing twice does nothing.
Survey
Survey(configure_buses: bool = True)
Bases: Handle
What this host's drives are, read once and held: every CAN link it has and every joint address on each.
One survey answers three questions without reading the buses again: what is on each link (links), which shipped
workcell the links are (detect), and whether they are fit to run as a given workcell (check). Reads only:
nothing is written, no drive is enabled and nothing moves. Every method but close raises anvil.OperationError
once it is closed.
Surveys the host, about a second for a bimanual robot with its leaders.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
configure_buses
|
bool
|
Whether to bring every link up first, as |
True
|
Methods:
| Name | Description |
|---|---|
links |
Returns every link the survey found, as one dict each. |
detect |
Returns which shipped workcells the surveyed links could be. |
check |
Returns whether the surveyed machine is |
close |
Frees the handle now rather than once this object is collected. Closing twice does nothing. |
links
links() -> list[dict[str, Any]]
Returns every link the survey found, as one dict each.
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
One dict per link: |
detect
detect() -> dict[str, Any]
Returns which shipped workcells the surveyed links could be.
Only those at the most trusted level any fits are offered: the records' roles and sides, then their families,
then the motors alone, records_ignored saying which. Trusting the records, an arm is a leader only on a link
whose gripper's record names one, since a follower taken for a leader is left limp.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict of |
check
check(
workcell: WorkcellConfig, strict: bool = True
) -> Report
Returns whether the surveyed machine is workcell, and fit to run, as check_readiness reports it.
The drives are judged as surveyed, and the cameras as they answer now.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
workcell
|
WorkcellConfig
|
The workcell the machine should be. |
required |
strict
|
bool
|
Whether a part that did not answer holds the run up, as |
True
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
close
close() -> None
Frees the handle now rather than once this object is collected. Closing twice does nothing.
ConfigureSequence
ConfigureSequence(
description: ArmDescription,
gripper_only: bool,
allow_fw_mismatch: bool,
role: str | None = None,
side: str | None = None,
)
Bases: Handle
Every joint of an arm configured in turn, one motor on the bus at a time.
Whatever motor is connected is written to the current joint's addresses and MIT mode, saved to flash, and given a
fresh identity record. The walk holds no bus of its own, since the operator swaps motors on it between joints: swap
the motor in, call configure(bus=...), then call it again to retry or advance() to move on. A joint that was not
configured is never advanced past. Every method but close raises anvil.OperationError once it is closed.
Starts a walk over the joints of description at its first joint, writing nothing until configure().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
description
|
ArmDescription
|
The arm whose joints to configure. |
required |
gripper_only
|
bool
|
Whether to walk the gripper alone, its record a standalone gripper's. |
required |
allow_fw_mismatch
|
bool
|
Whether to configure a motor on other firmware than the motors configured before it; without it, such a motor is reported and not written. |
required |
role
|
str | None
|
|
None
|
side
|
str | None
|
|
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
Methods:
| Name | Description |
|---|---|
joints |
Returns every joint the walk configures, in order, e.g. |
remaining |
Returns the joints not yet configured, the one |
configure |
Configures the one motor on |
advance |
Moves on to the next joint. |
close |
Frees the handle now rather than once this object is collected. Closing twice does nothing. |
joints
joints() -> list[str]
Returns every joint the walk configures, in order, e.g. ["J1", "J2"].
remaining
remaining() -> list[str]
Returns the joints not yet configured, the one configure() writes first; empty once all are.
configure
configure(bus: DamiaoBus) -> dict[str, Any]
Configures the one motor on bus as the current joint, writing its flash but moving nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bus
|
DamiaoBus
|
The bus the motor is on, alone. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict of |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
Every joint is already configured. |
UnreachableError
|
No motor answered on the bus. |
OperationError
|
More than one answered, the bus stopped accepting frames part-way through the scan,
or |
advance
advance() -> None
Moves on to the next joint.
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
The last |
close
close() -> None
Frees the handle now rather than once this object is collected. Closing twice does nothing.
ZeroLimitSweep
ZeroLimitSweep(
bus: DamiaoBus,
description: ArmDescription,
allow_missing: bool,
side: str | None = None,
motors: Sequence[str] | None = None,
)
Bases: Handle
An arm held passive while someone sweeps each joint through its whole range by hand, then zeroed from it.
Each joint is held in MIT mode at zero torque, so it backdrives by hand; poll() reads every joint and says how far
each is from ready, and committing trims each joint onto the zero its observed range puts it at, moving the arm a
little, and zeroes it. The sweep holds its bus: closing it, letting it be collected or abort() disables every
joint, and the first two close the bus. Every method but close raises anvil.OperationError once it is closed,
and poll, commit_joint and commit after commit() or abort() raise anvil.InvalidArgumentError.
Finds the arm's joints on bus, takes the bus over, and arms each joint that answered passive.
bus answers nothing from then on. A wrong argument is refused before the bus is taken; a joint that does not
answer is refused after, and the bus is closed all the same: open the interface again.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bus
|
DamiaoBus
|
The bus the arm is on. |
required |
description
|
ArmDescription
|
The arm to sweep. |
required |
allow_missing
|
bool
|
Whether to sweep the joints that answer when some do not. Without it, every joint but the gripper has to answer where the description puts it. |
required |
side
|
str | None
|
|
None
|
motors
|
Sequence[str] | None
|
The joints to sweep, e.g. |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
UnreachableError
|
A joint that has to answer did not, or no joint answered at all. |
OperationError
|
|
Methods:
| Name | Description |
|---|---|
armed |
Returns each swept joint and whether arming it passive took, as |
poll |
Reads every joint once, widening its observed range, and returns how each stands. |
commit_joint |
Zeroes one joint without ending the sweep, moving it onto its zero first for up to two seconds. |
commit |
Zeroes every joint that is ready, each moved onto its zero first, then ends the sweep, every joint disabled. |
abort |
Ends the sweep with every joint disabled, zeroing nothing. |
close |
Frees the handle now rather than once this object is collected. Closing twice does nothing. |
armed
armed() -> list[dict[str, Any]]
Returns each swept joint and whether arming it passive took, as {"motor", "armed"}.
armed is {"ok": None} or {"error": ...}; a joint that failed to arm is disabled and still swept.
poll
poll() -> list[dict[str, Any]]
Reads every joint once, widening its observed range, and returns how each stands.
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
One |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
The sweep has ended. |
commit_joint
commit_joint(motor: str) -> dict[str, Any]
Zeroes one joint without ending the sweep, moving it onto its zero first for up to two seconds.
The joint is armed passive again afterwards, and its observed range starts over.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motor
|
str
|
The joint to zero, e.g. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict whose |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
The sweep has no joint |
commit
commit() -> list[dict[str, Any]]
Zeroes every joint that is ready, each moved onto its zero first, then ends the sweep, every joint disabled.
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
One |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
The sweep has already ended. |
abort
abort() -> None
Ends the sweep with every joint disabled, zeroing nothing.
close
close() -> None
Frees the handle now rather than once this object is collected. Closing twice does nothing.
ZeroAuto
ZeroAuto(
bus: DamiaoBus,
description: ArmDescription,
side: str | None = None,
motors: Sequence[str] | None = None,
)
Bases: Handle
An arm zeroed by driving each joint into its hard stops by itself, from rest, in about four minutes.
This moves the arm, under power, with no hand on it. Only a yam or an openarm has a run of every joint, and only an
openyam one of J1 alone, for a base plate turned to switch its mount: it stands the arm up, turns the base into both
its stops and back to rest, and zeroes J1 and nothing else, in about two minutes. The arm is parked at rest
first: what each joint reads there is where the run puts its zero until it finds the joint's stops, so the zero it
starts from may be off by any amount. start() enables the motors; each poll() from then on is one 10 ms tick,
and the run paces itself, so polling faster does not move the arm faster. A step that goes wrong holds the arm
where it stopped, as hold() does, and later polls keep holding it until abort() lets it go; a finished run is
zeroed at rest by commit(). The run holds its bus: closing it, or letting it be collected, lets the arm go
wherever it is and closes the bus, but for a held run, whose motors go on holding the arm on the last command sent,
with nothing driving them, until they are disabled or powered off. Every method but close raises
anvil.OperationError once it is closed.
Connects to the arm on bus, taking over the bus, and checks the arm is whole, enabling nothing.
bus answers nothing from then on. A wrong argument is refused before the bus is taken; an arm that is not
whole is refused after, and the bus is closed all the same: open the interface again.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bus
|
DamiaoBus
|
The bus the arm is on. |
required |
description
|
ArmDescription
|
The arm to zero, a yam or an openarm. |
required |
side
|
str | None
|
|
None
|
motors
|
Sequence[str] | None
|
|
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
The arm has no run, or no run of the joints |
UnreachableError
|
A chain motor did not answer, or not on the address the description gives it. |
OperationError
|
|
Methods:
| Name | Description |
|---|---|
plan |
Returns the plan of the run |
gripper_left_out |
Returns why the gripper is not in the run, or |
start |
Enables every motor where it stands, taken as parked at rest; the arm moves from the next |
poll |
Runs one tick, waiting out the rest of the last one first, and returns where the run is. |
hold |
Stops moving and holds the arm where it is, for someone to support it; only |
commit |
Zeroes each joint of a finished run where it is held at rest, then releases the arm, every motor disabled. |
abort |
Lets the arm go wherever it is, every motor disabled, zeroing nothing. |
close |
Frees the handle now rather than once this object is collected. Closing twice does nothing. |
plan
staticmethod
plan(
description: ArmDescription,
motors: Sequence[str] | None = None,
) -> str
Returns the plan of the run ZeroAuto would make of the same arguments, without a bus or a motor moving.
One step per line, degrees, the arm's joints in chain order as the left arm takes them, hold for a joint
left where it is: pin_deg, how far a setpoint leads its joint; parked, the rest it starts from; then each
move to a posture, search of a joint into its min or max stop, and gripper search, in order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
description
|
ArmDescription
|
The arm, a yam or an openarm. |
required |
motors
|
Sequence[str] | None
|
As |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
The arm has no run, or no run of the joints |
gripper_left_out
gripper_left_out() -> str | None
Returns why the gripper is not in the run, or None where it is.
It is left out at connect where it does not answer, and during the run where a search of it gives up: jaws that
stop short or meet no stop. Without it the arm is zeroed alone, and the gripper by hand with a ZeroLimitSweep
of it alone.
start
start() -> None
Enables every motor where it stands, taken as parked at rest; the arm moves from the next poll().
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
The run has already started. |
UnreachableError
|
A motor would not enable; every motor is disabled again. |
poll
poll() -> dict[str, Any]
Runs one tick, waiting out the rest of the last one first, and returns where the run is.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict of |
hold
hold() -> None
Stops moving and holds the arm where it is, for someone to support it; only abort() lets it go.
commit
commit() -> list[dict[str, Any]]
Zeroes each joint of a finished run where it is held at rest, then releases the arm, every motor disabled.
Each joint is disabled only as it is zeroed, so nothing moves between the two. A run of J1 alone zeroes J1 alone.
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
One |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
The run has not finished. |
abort
abort() -> None
Lets the arm go wherever it is, every motor disabled, zeroing nothing.
It falls under gravity: support it first.
close
close() -> None
Frees the handle now rather than once this object is collected. Closing twice does nothing.
FoundRealHand
dataclass
FoundRealHand(side: Side, link: str)
RealHand
RealHand(link: str, side: Side)
Bases: Handle
A RealHand L6 on a CAN link, written its six angles directly.
Construct one for the hand on a link, which may be its own or the arm's it is bolted to: it answers on the CAN ID
its description gives its side, which no arm motor uses. Nothing here runs a loop or reads the hand back: the
caller sends the angles it wants at its own pace, typically those of an anvil.apps.GloveTeleop, and the hand
holds the last ones it was sent, also once this is closed. Every method but close raises anvil.OperationError
once it is closed. Linux only: elsewhere no hand opens.
Opens link for the side hand; nothing is sent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
link
|
str
|
The CAN link the hand is on, e.g. |
required |
side
|
Side
|
Which hand: each answers on its own CAN ID. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
OperationError
|
|
Methods:
| Name | Description |
|---|---|
scan |
Returns every hand answering on any CAN link that is up, asked without moving it. |
answers |
Returns whether the hand answers on its link, asked without moving it. |
set_speed |
Sets every motor's speed, from 0 to 100; at 0 the fingers do not move. |
send |
Commands the hand's six angles, each from 0 closed to 100 open. |
close |
Frees the handle now rather than once this object is collected. Closing twice does nothing. |
scan
staticmethod
scan() -> list[FoundRealHand]
Returns every hand answering on any CAN link that is up, asked without moving it.
Each link is asked for the left hand's CAN ID and the right's, a few times each, since a hand on an arm's busy link leaves some questions unanswered. A link that cannot be opened is passed over, and a host without CAN has none. Takes up to a second for each link and hand that does not answer.
Returns:
| Type | Description |
|---|---|
list[FoundRealHand]
|
Each hand found, by link name then left before right. |
answers
answers() -> bool
Returns whether the hand answers on its link, asked without moving it.
set_speed
set_speed(speed: float) -> None
Sets every motor's speed, from 0 to 100; at 0 the fingers do not move.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
speed
|
float
|
The speed, from 0 to 100; a value outside that is clamped to it. |
required |
Raises:
| Type | Description |
|---|---|
OperationError
|
The frame could not be sent, as on a link that is down. |
send
send(angles: Sequence[float]) -> None
Commands the hand's six angles, each from 0 closed to 100 open.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angles
|
Sequence[float]
|
Thumb flexion, thumb abduction, index, middle, ring and pinky, in that order; a value outside 0 to 100 is clamped to it. |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
OperationError
|
The frame could not be sent, as on a link that is down. |
close
close() -> None
Frees the handle now rather than once this object is collected. Closing twice does nothing.
torque_full_scales_nm
torque_full_scales_nm(robot: RobotConfig) -> list[float]
Returns each actuator's torque full scale in newton meters, actuator order: what a tau reading is a share of.
Each is the maximum its motor model scales torques against as shipped. Nothing touches a bus.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
robot
|
RobotConfig
|
The robot whose actuators to read. |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
buses
buses(robot: RobotConfig) -> list[ResolvedBus]
Returns robot's buses as DamiaoHw takes them, one per arm in actuator order, each on the link it answers on.
An arm whose drives' identity records name it is put on the link they answer on, whatever the workcell declares. In
a workcell with leaders the role on each gripper's record places an arm. An arm no record names keeps its declared
bus where drives answer there, and is otherwise guessed onto a link with drives that no other arm took, sorted, the
earlier to the leaders, so links named anything run; only where those links are as many as the arms guessed, and
never onto one whose records name another arm. A gripper whose description names an
anvil.workcell.Gripper.alternate_motor is addressed as that motor where, on its arm's link, its own address is
silent and the alternate's answers. To find the records it opens each CAN link this host has and reads one drive
per arm, and the gripper of such an arm on its link; it writes nothing, and a link another program holds is passed
over.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
robot
|
RobotConfig
|
The robot whose buses to find. |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
check_readiness
check_readiness(
workcell: WorkcellConfig,
configure_buses: bool = True,
strict: bool = True,
) -> Report
Returns whether the machine on the bench is workcell, and fit to run, as a Report.
One read-only pass over the drives and the cameras, about half a second, before anything is commanded: nothing is
written, no drive is enabled and nothing moves, and a camera is asked what modes it offers, which it answers
whatever else has it open. Print the report, and read Report.ok to decide whether to go on. Only for a run on the
drives: the sim has no buses to ask.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
workcell
|
WorkcellConfig
|
The workcell the machine should be. |
required |
configure_buses
|
bool
|
Whether to bring every CAN link the host has up first, the same way on every machine, since a
drive's record may put an arm on any of them: down if it is up, then up at 1 Mbit/s, sample point 0.75 and
a transmit queue of 8. That needs |
True
|
strict
|
bool
|
Whether a part that did not answer, a motor, a bus or a camera, holds the run up. False lets the robot come up without it. Anything saying the machine is not this workcell holds it up either way, a drive not on Anvil firmware among it. |
True
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
status_names
status_names(
status: Sequence[int], num_actuators: int
) -> list[str]
Returns the first num_actuators drive status codes by name, e.g. "Enabled" or "Overcurrent".
The names are Disabled, Enabled, Overvoltage, Undervoltage, Overcurrent, MosOvertemperature,
CoilOvertemperature, CommunicationLoss and Overload; a code this build does not know comes back in hex, e.g.
"0x0f".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
status
|
Sequence[int]
|
Drive status codes, e.g. |
required |
num_actuators
|
int
|
How many of them to name. |
required |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
status_faults
status_faults(
status: Sequence[int], num_actuators: int
) -> list[bool]
Returns whether each of the first num_actuators drive status codes is a fault.
A fault is anything but Enabled or Disabled, a code this build does not know included. A faulted drive will not
enable until the fault clears.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
status
|
Sequence[int]
|
Drive status codes, e.g. |
required |
num_actuators
|
int
|
How many of them to judge. |
required |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|