Skip to content

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 anvil.control.Runtime drives the arms through: an anvil.hw.SimHw or an anvil.hw.DamiaoHw.

ResolvedMotor

One motor of a ResolvedBus: the actuator it drives and the addresses it listens and replies on.

ResolvedBus

One arm's bus as DamiaoHw takes it: the CAN interface the arm answers on, and its motors.

Finding

One line of a Report: what was looked at, and what was found.

Report

What a readiness check found, from check_readiness or Survey.check: a verdict and every finding behind it.

SimHw

The sim hw interface: the robot of an anvil.sim.World, driven there, standing where a DamiaoHw stands.

DamiaoHw

Real Damiao drives over SocketCAN, one CAN interface per arm.

DamiaoMotor

A motor on a bus, by the address it listens on and the one it replies from.

BusScan

What DamiaoBus.scan found on a bus.

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.scan finds it.

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 tau reading is a share of.

buses

Returns robot's buses as DamiaoHw takes them, one per arm in actuator order, each on the link it answers on.

check_readiness

Returns whether the machine on the bench is workcell, and fit to run, as a Report.

status_names

Returns the first num_actuators drive status codes by name, e.g. "Enabled" or "Overcurrent".

status_faults

Returns whether each of the first num_actuators drive status codes is a fault.

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 anvil.control.JointState and an anvil.control.RobotCommand.

joint str

The joint's name in the arm's description, e.g. "joint_3" or "gripper".

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. "can0", or a bus name a workcell declares, e.g. "follower_l".

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: "pass", "warn", "missing" (a part that did not answer) or "fail".

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. "left" or "left joint_3".

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 None where it concerns no arm.

joint str | None

The joint it concerns, by its name in the arm's description, or None where it concerns no joint.

interface str | None

The CAN interface it concerns, by its own name, e.g. "can0", or None where it concerns none.

camera str | None

The camera it concerns, by its name in the workcell, or None where it concerns no camera.

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

world is closed, its server would not take a connection, is from another release of anvil, which the message names, or stands another robot than world.robot, which only a server shared with another world can.

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 (t_ns, q, qd), without waiting for one.

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 None for a world that keeps its own clock.

motors list[str]

The motors the sim drives, in actuator order, each <arm>_<joint> by the workcell's and description's names.

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.

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 num_actuators joint commands is NaN or infinite; nothing is sent.

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 buses returns them, edited or not.

required

Raises:

Type Description
InvalidArgumentError

An actuator that is negative, or an address outside 0 to 255; buses is empty, two arms land on one interface, or an actuator is used twice or is not below anvil.control.MAX_ACTUATORS.

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 write collected, as (t_ns, q, qd), without touching the bus.

state

Returns the state read returns, whole: torques, each actuator's drive status and its temperatures too.

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 (bus, slave_id, master_id) tuple per motor this drives, in the order the buses were given.

aliased list[str]

Each bus this host had under another name, as "follower_l -> can0"; empty where every name was a link.

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 state()'s status, and a warning is logged naming them.

True
stop_on_dropout bool

Whether a drive that stops reading Enabled while the loop runs, as one unplugged, unpowered or faulted does, stops the loop driving every arm: the runtime then raises anvil.control.HwFaultError naming it. False drives the rest on, and the dropped drive's actuator says what it reads in each snapshot's status_names and status_faults.

False

Raises:

Type Description
OperationError

With require_enabled, a drive is not enabled afterwards; the message names each one and what it reported, or the bus it is on and why that would not open.

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 num_actuators joint commands is NaN or infinite; nothing is sent.

UnreachableError

A drive that was enabled no longer reads Enabled, found by this write or an earlier one since the last enable.

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 None where it did not.

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. "can0", or a bus name a workcell declares, e.g. "follower_l", which opens the link this host has for it.

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 /sys and opening nothing.

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 motors limp, lights green, commanded no stiffness, no damping and no torque, so nothing moves.

disable

Disables motors, each as far as it answers: one that does not is off already, or gone.

zero_limit_sweep

Returns a ZeroLimitSweep of the arm on this bus, which takes the bus over; see that class.

zero_auto

Returns a ZeroAuto run of the arm on this bus, which takes the bus over; see that class.

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. "can0".

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 joints, one dict per joint in description order, and stopped_by, as BusScan.stopped_by. A joint holds motor, its name, e.g. "J3"; slave and master, the addresses the description gives it; answered, the address something at slave replied from, or None; and limits, the scaling maxima it holds as {"ok": {"q_max", "dq_max", "tau_max"}}, in radians, radians per second and newton meters, or why they could not be read as {"error": ...}.

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 software, which names the firmware image flashed, e.g. "5017"; sub, the firmware revision; and hardware, the hardware revision.

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 outcome is "stock_firmware", which has no record; "invalid" or "unread", with the error; or "named", with the record, its uuid, the versions and control mode as {"ok": ...} or {"error": ...}, and calibration, whose outcome is "matches", "drifted" or "unread": whether the motor's registers still hash to the record's fingerprint.

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: name, e.g. "position_limit"; wire_name, the Damiao documentation's, e.g. "PMAX"; writable; and value, as {"ok": ...} or why it did not read as {"error": ...}.

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 status, e.g. "disabled"; slave, the address it identified itself as; position in radians; velocity in radians per second; torque in newton meters; and temp_mos and temp_rotor, its driver stage and winding temperatures in degrees Celsius.

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 mode, {"ok": "mit"} or {"error": ...}; and, where a save was asked for and the mode took, saved, whose outcome is "saved", "not_acknowledged" or "failed", with the error.

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

"auto", the factory zero where its firmware has one and the working zero where it does not, with the identity record's fingerprint re-stamped; "working", the working zero only; or "restore", the working zero back to the factory baseline.

required

Returns:

Type Description
dict[str, Any]

A dict whose outcome is "disable_failed" or "failed", with the error, or "zeroed", with zeroed: the angle_offset the zero landed on, in radians, as {"ok": ...} or {"error": ...}, and the restamp. stock_firmware says, for "auto", whether the motor runs stock firmware.

Raises:

Type Description
InvalidArgumentError

kind is not one of the three.

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 for the joint's.

None
slave_id int | None

The listening address to set, 0 to 255, or None for the joint's.

None
description ArmDescription | None

The arm the motor belongs to, or None to set addresses only.

None
motor str | None

The joint to configure the motor as, e.g. "J3"; required with description, ignored without.

None
side str | None

"left" or "right", which the record names; None is the left for a symmetric build, and is refused for a mirrored one.

None
role str | None

"leader" or "follower", which J8's record carries; None is "leader" for any other joint, and is refused for J8.

None

Returns:

Type Description
dict[str, Any]

A dict of motors, one dict per motor in scan order: what was found, the writes made in order up to the first that failed, and the record where one was owed and every write took.

Raises:

Type Description
InvalidArgumentError

master_id or slave_id is outside 0 to 255; description names no such motor, motor is missing, side or role is not one of its two or is missing where it is required, the description's family has no identity record, or gripper_only names an arm whose gripper has no motor of its own.

UnreachableError

No motor answered on the bus.

OperationError

More than one answered without allow_multiple, or the bus stopped accepting frames part-way through the scan.

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 motor, e.g. "J3", and outcome: "no_record", stock firmware or no record to stamp; "own_gripper"; "already", carrying this version; "failed", with the error; or "stamped", with the version it carried from.

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 check_readiness takes it.

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 workcell, and fit to run, as check_readiness reports it.

close

Frees the handle now rather than once this object is collected. Closing twice does nothing.

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: interface; named_for, the bus a workcell declares that the link's name stands for, or None; error, why it could not be read, or None; can_state; bit_rate_bps; rx_errors and tx_errors; and motors. A motor is slave, master, model, firmware, record, the identity record as the bench tools print it, its uuid, mode, timeout_ms, status, position_deg, temp_mos_c and temp_rotor_c, each read that failed None.

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 presets and links. A preset is name, records_ignored, and arms, one {"arm", "interface", "placed_by"} each, placed_by being "records", "only", "link_name" or "guess". A link, one per link carrying motors, is interface, family, side, role and candidates, each candidate {"variant", "family", "missing"}.

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 check_readiness takes it.

True

Raises:

Type Description
TypeError

workcell is not an anvil.workcell.WorkcellConfig.

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

"leader" or "follower", which J8's record carries, so it must be given.

None
side str | None

"left" or "right"; None is the left for a symmetric build, and is refused for a mirrored one.

None

Raises:

Type Description
InvalidArgumentError

role is missing, side is missing for a mirrored build, either is not one of its two, the description's family has no identity record, or gripper_only names an arm whose gripper has no motor of its own.

Methods:

Name Description
joints

Returns every joint the walk configures, in order, e.g. ["J1", "J2"].

remaining

Returns the joints not yet configured, the one configure() writes first; empty once all are.

configure

Configures the one motor on bus as the current joint, writing its flash but moving nothing.

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 motor, the joint; found, the motor as it answered; custom_firmware, as {"ok": ...} or {"error": ...}; firmware_mismatch, set where it disagrees with the motors before it; the writes; and the record. The joint is configured, and advance() will move on, only where every one of those took.

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 bus is closed or taken by a zeroing run. Nothing is written on any of these: fix the bus and call again.

advance

advance() -> None

Moves on to the next joint.

Raises:

Type Description
InvalidArgumentError

The last configure() did not configure the current joint.

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

"left" or "right", required for a mirrored build.

None
motors Sequence[str] | None

The joints to sweep, e.g. ["J1", "J2"], or None for all of them.

None

Raises:

Type Description
InvalidArgumentError

side is missing for a mirrored build or not one of its two, or motors names one the description does not have.

UnreachableError

A joint that has to answer did not, or no joint answered at all.

OperationError

bus is closed or already taken by a zeroing run.

Methods:

Name Description
armed

Returns each swept joint and whether arming it passive took, as {"motor", "armed"}.

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 {"motor", "reading"} per joint, reading being {"error": ...} where it did not read, else {"ok": ...} holding current_deg; min_deg and max_deg, how far it has been each way; limits, its configured travel; and readiness, once it is read: coverage_percent, target_deg, error_deg and the blocker that stops it being committed, or None where nothing does.

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. "J3".

required

Returns:

Type Description
dict[str, Any]

A dict whose outcome is "no_data", nothing read from it yet; "blocked", with the blocker; "failed", with the error, the joint disabled; or "zeroed", with what the zero left and whether it was rearmed.

Raises:

Type Description
InvalidArgumentError

The sweep has no joint motor, or has ended.

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 {"motor", "zeroed"} per joint, zeroed as commit_joint returns it.

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

"left" or "right", required for a mirrored build.

None
motors Sequence[str] | None

["J1"] for an openyam's base alone, every other joint driven to stand the arm up but keeping its zero and the gripper not driven, or None for every joint.

None

Raises:

Type Description
InvalidArgumentError

The arm has no run, or no run of the joints motors names; or side is missing for a mirrored build or not one of its two.

UnreachableError

A chain motor did not answer, or not on the address the description gives it.

OperationError

bus is closed or already taken by a zeroing run.

Methods:

Name Description
plan

Returns the plan of the run ZeroAuto would make of the same arguments, without a bus or a motor moving.

gripper_left_out

Returns why the gripper is not in the run, or None where it is.

start

Enables every motor where it stands, taken as parked at rest; the arm moves from the next poll().

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 abort() lets it go.

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 ZeroAuto takes them.

None

Raises:

Type Description
InvalidArgumentError

The arm has no run, or no run of the joints motors names.

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 phase, "ready", "running", "finished", "held" with the why, or "released"; step, the step it is on, counting from 0; steps, how many the run has; parked, what each chain joint read as the arm stood parked, as {"motor", "deg"}, in degrees, where the run puts its zero until it finds the joint's stops; and found, every stop met so far, as {"motor", "end", "stop_deg", "zero_deg"}, in degrees, end being "min" or "max".

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 {"motor", "outcome"} per joint, the gripper last, outcome being "blocked" with the reason, "unread" or "failed" with the error, or "zeroed" with what the zero left, off_deg, how far from the found zero it was held when zeroed, in degrees, and a warning, or None, saying how far the zero may be off where the joint's stops disagree with its description.

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)

A RealHand L6 answering on a CAN link, as RealHand.scan finds it.

Attributes:

Name Type Description
side Side

Which hand, by the CAN ID it answered on.

link str

The CAN link it answered on, e.g. anvil_can1.

side instance-attribute

side: Side

Which hand, by the CAN ID it answered on.

link: str

The CAN link it answered on, e.g. anvil_can1.

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

required
side Side

Which hand: each answers on its own CAN ID.

required

Raises:

Type Description
ValueError

side is not a side.

OperationError

link cannot be opened, as one the host does not have cannot, or the host has no CAN.

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

angles is not a flat sequence of numbers.

InvalidArgumentError

angles is not six values.

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

robot is not an anvil.workcell.RobotConfig.

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

robot is not an anvil.workcell.RobotConfig.

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 sudo without a password for ip link set, which the installer arranges, and is best effort: a link it could not set up is a warning, and the pass after it decides whether the bus is fit to run. False takes the links as the machine has them.

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

workcell is not an anvil.workcell.WorkcellConfig.

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. anvil.control.JointState.status.

required
num_actuators int

How many of them to name.

required

Raises:

Type Description
InvalidArgumentError

status holds fewer than num_actuators codes, or a code outside 0 to 255.

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. anvil.control.JointState.status.

required
num_actuators int

How many of them to judge.

required

Raises:

Type Description
InvalidArgumentError

status holds fewer than num_actuators codes, or a code outside 0 to 255.