Skip to content

anvil.control

anvil.control

The controllers, what they are commanded with, what they report, how they are tuned, and the loop that runs them.

There are four controllers: JointController tracks streamed joint targets, JointTrajectoryController plays timed waypoints, EePoseController tracks end-effector poses through IK, and LeaderFollowerController mirrors leader arms on followers moved by hand. Each is built for an anvil.workcell.RobotConfig and is usually handed to a Runtime, which ticks it at a fixed rate against a hw interface; one can also be ticked by hand with activate and tick.

Every per-actuator list is indexed by actuator: each arm's chain joints in chain order, then its gripper, arm after arm in the order the robot declares them. MAX_ACTUATORS, MAX_ARMS and MAX_WAYPOINTS bound what one state, command or trajectory carries.

Classes:

Name Description
JointState

Measured state of every actuator for one tick, as the hw interface reads it.

MotorCommand

One actuator's command for one tick: the targets and gains the drive's own loop runs on.

RobotCommand

The active controller's output for one tick: one MotorCommand per actuator, indexed as a JointState is.

CorrectionOverride

What a command replaces of one feedforward term's correction while that command is tracked.

JointOverride

What a command replaces of one chain joint's tuning while that command is tracked.

GripperOverride

What a command replaces of an arm's gripper tuning while that command is tracked.

ArmOverride

What a command replaces of one arm's tuning: its chain joints in chain order, then its gripper.

TuningOverride

What a command replaces of its arms' tunings, for as long as that command is the one tracked.

GripperTarget

The gripper motor's own target: an angle, and the rate it is to be turning at as it passes it.

GripperCommand

What a command asks of one arm's gripper: a jaw opening and the rate it opens at, or the motor's own target.

JointCommand

The JointController's command: a position and velocity target per actuator, and one GripperCommand per arm.

Provenance

Where each actuator's gains and feedforward came from on one tick, as every trace carries it.

JointTrace

The JointController's trace of one tick: the command it tracked, and why each actuator was sent what it was.

LeaderFollowerTrace

The LeaderFollowerController's trace of one tick: what each follower was sent from, and why.

Waypoint

One point of a JointTrajectoryCommand: where every actuator is at one time, and how fast it passes there.

JointTrajectoryCommand

The JointTrajectoryController's command: timed waypoints for every actuator, played once from when it is taken.

JointTrajectoryTrace

The JointTrajectoryController's trace of one tick: its progress along the trajectory and the tracking error.

ArmTarget

One arm's target for the EePoseController: a world-frame pose for its tool frame, and what to ask its gripper.

EePoseCommand

The EePoseController's command: one ArmTarget per arm, in the robot's arm order.

EePoseTrace

The EePoseController's trace of one tick: the command it tracked, and the IK behind its targets.

GripperMap

A gripper's linear map from jaw opening to motor angle, as its description states it.

GripperTuning

The gripper motor's tuning: its gains, the grip assist, and the map from a jaw opening to its motor angle.

LimitGuards

Which of the limits an arm's description states the tick holds the arm's chain targets to.

SpeedLimits

How fast a controller moves an arm's chain targets: each joint's, and the tool frame's.

JointGuards

What the tick clamps an arm to, whatever asked for it: the arm's own tuning, or a command's override.

ArmTuning

One arm's tuning: gains and gravity correction per chain joint, the gripper's tuning, speed limits and guards.

JointControllerSettings

What the JointController is configured with: one ArmTuning per arm.

JointTrajectoryControllerSettings

What the JointTrajectoryController is configured with: one ArmTuning per arm.

ForceFeedback

How hard each leader is pulled toward its follower, so the operator's hand feels what holds the follower back.

LeaderFollowerControllerSettings

What the LeaderFollowerController is configured with: a tuning per arm, the force feedback, and the engagement.

EePoseControllerSettings

What the EePoseController is configured with: one ArmTuning and one set of IK parameters per arm.

ControlArm

One arm as a controller drives it: the actuators it owns, its chain, and the tuning its description states.

CollisionClosing

The link whose closing the collision stop held the arms for, what on, and how near, from CollisionHold.

CollisionHold

Why the collision stop held the arms, as CollisionStop.last_stop reports it.

CollisionStop

A guard that holds the arms short of hitting themselves or a keep-out box, whichever controller drives them.

Controller

What the four controllers share: built for a robot, warm-started, ticked and retuned the same way.

JointController

The controller that tracks streamed joint targets, one JointCommand at a time, for every arm of a robot.

JointTrajectoryController

The controller that plays a JointTrajectoryCommand's timed waypoints and holds the last one.

EePoseController

The controller that tracks streamed end-effector poses, one EePoseCommand at a time, solving each with IK.

LeaderFollowerController

The controller that mirrors each leader arm, moved by hand, on the follower on its side, every tick.

ActuatorSnapshot

One actuator of a Snapshot, by the joint it drives: what the snapshot's per-actuator lists hold for it.

ArmSnapshot

One arm of a Snapshot by name: its chain joints' actuators and its gripper's, each by the joint it drives.

ControllerHandle

A controller a Runtime holds, reached through that runtime.

GripperSnapshot

The gripper of an ArmSnapshot: its actuator, and the jaw opening the motor's measured angle comes to.

HwFaultError

Raised by an anvil.control.Runtime call that would drive the arms or read their state, once the loop faulted.

JointTrajectoryHandle

The handle to a runtime's JointTrajectoryController, which can also play a trajectory through to its end.

LinkCounts

Frames the hw interface sent each drive, the replies that came back and the sends the kernel refused.

LoopTiming

Where one tick of the loop's time went, as marks and the spans between them.

PreemptedError

Raised by anvil.control.JointTrajectoryHandle.play when something took over before the trajectory played out.

Runtime

The control loop, running on a thread of its own at a fixed rate and holding every controller it was given.

Snapshot

A copy of what the loop last measured, wrote and did, taken when Runtime.latest was called.

StoppedShortError

Raised by anvil.control.JointTrajectoryHandle.play when the collision stop slowed the trajectory, ending it.

Functions:

Name Description
control_arms

Returns the arms of robot as the controller class kind drives them, in actuator order.

Attributes:

Name Type Description
MAX_ACTUATORS int

Most actuators a state, a command or a trace carries: every arm's chain joints, then its gripper.

MAX_ARMS int

Most arms one robot drives, and so the most entries a per-arm list takes.

MAX_WAYPOINTS int

Most waypoints one JointTrajectoryCommand carries.

MAX_ACTUATORS module-attribute

MAX_ACTUATORS: int = 32

Most actuators a state, a command or a trace carries: every arm's chain joints, then its gripper.

MAX_ARMS module-attribute

MAX_ARMS: int = 4

Most arms one robot drives, and so the most entries a per-arm list takes.

MAX_WAYPOINTS module-attribute

MAX_WAYPOINTS: int = 16

Most waypoints one JointTrajectoryCommand carries.

ControllerSettings

Any of the four controllers' settings: what a controller's settings() returns and its configure takes.

JointState

JointState(
    q: Sequence[float],
    qd: Sequence[float] = (),
    tau: Sequence[float] = (),
    num_actuators: int | None = None,
)

Bases: Struct

Measured state of every actuator for one tick, as the hw interface reads it.

Each list is one entry per actuator, num_actuators of them, in actuator order. One comes from a Snapshot, or is built here to tick a controller by hand.

Builds a state to tick a controller with by hand; its status codes and temperatures read zero.

Parameters:

Name Type Description Default
q Sequence[float]

Position per actuator, in radians.

required
qd Sequence[float]

Velocity per actuator, in radians per second; zero for an actuator past the list.

()
tau Sequence[float]

Torque per actuator, in newton meters; zero for an actuator past the list.

()
num_actuators int | None

Actuators the state counts, 0 to MAX_ACTUATORS; None for len(q).

None

Raises:

Type Description
TypeError

A list holds something other than numbers.

InvalidArgumentError

A list has more than MAX_ACTUATORS entries, or num_actuators is negative or past MAX_ACTUATORS.

Attributes:

Name Type Description
num_actuators int

Actuators the state counts.

q list[float]

Position per actuator, in radians.

qd list[float]

Velocity per actuator, in radians per second.

tau list[float]

Torque per actuator as the drive estimates it, in newton meters.

status list[int]

Drive status code per actuator, verbatim from the drive; status_names decodes them.

status_names list[str]

Drive status name per actuator: Enabled, Disabled, CommunicationLoss, or a fault such as Overcurrent.

status_faults list[bool]

Whether each actuator's drive reports a fault: any status but Enabled or Disabled, unknown ones too.

temp_mos_c list[int]

Driver-stage temperature per actuator, in degrees Celsius.

temp_rotor_c list[int]

Winding temperature per actuator, in degrees Celsius.

num_actuators property

num_actuators: int

Actuators the state counts.

q property

q: list[float]

Position per actuator, in radians.

qd property

qd: list[float]

Velocity per actuator, in radians per second.

tau property

tau: list[float]

Torque per actuator as the drive estimates it, in newton meters.

status property

status: list[int]

Drive status code per actuator, verbatim from the drive; status_names decodes them.

status_names property

status_names: list[str]

Drive status name per actuator: Enabled, Disabled, CommunicationLoss, or a fault such as Overcurrent.

A code the drive family does not define reads as hex.

status_faults property

status_faults: list[bool]

Whether each actuator's drive reports a fault: any status but Enabled or Disabled, unknown ones too.

temp_mos_c property

temp_mos_c: list[int]

Driver-stage temperature per actuator, in degrees Celsius.

temp_rotor_c property

temp_rotor_c: list[int]

Winding temperature per actuator, in degrees Celsius.

MotorCommand

MotorCommand(
    position: float = 0.0,
    velocity: float = 0.0,
    kp: float = 0.0,
    kd: float = 0.0,
    feedforward_torque: float = 0.0,
)

Bases: Struct

One actuator's command for one tick: the targets and gains the drive's own loop runs on.

The drive applies kp * (position - measured) + kd * (velocity - measured) + feedforward_torque. One read from a RobotCommand's actuators is that command's own entry, so assigning a field changes what the actuator is sent.

Builds one actuator's command; every field left out is zero, which leaves the drive limp.

Parameters:

Name Type Description Default
position float

Position target, in radians.

0.0
velocity float

Velocity target, in radians per second.

0.0
kp float

Position gain, in newton meters per radian.

0.0
kd float

Velocity gain, in newton meters per radian per second.

0.0
feedforward_torque float

Torque added on top of the gains' pull, in newton meters.

0.0

Attributes:

Name Type Description
position float

Position target, in radians.

velocity float

Velocity target, in radians per second.

kp float

Position gain, in newton meters per radian.

kd float

Velocity gain, in newton meters per radian per second.

feedforward_torque float

Torque added on top of the gains' pull, in newton meters.

position property writable

position: float = 0.0

Position target, in radians.

velocity property writable

velocity: float = 0.0

Velocity target, in radians per second.

kp property writable

kp: float = 0.0

Position gain, in newton meters per radian.

kd property writable

kd: float = 0.0

Velocity gain, in newton meters per radian per second.

feedforward_torque property writable

feedforward_torque: float = 0.0

Torque added on top of the gains' pull, in newton meters.

RobotCommand

RobotCommand(
    num_actuators: int,
    actuators: Sequence[MotorCommand] = (),
)

Bases: Struct

The active controller's output for one tick: one MotorCommand per actuator, indexed as a JointState is.

One comes from a controller's tick or a Snapshot's robot_command, or is built here to hand activate what was last sent. actuators reads as the command's own entries, so robot_cmd.actuators[3].kp = 20.0 changes what that actuator is sent; assigning a shorter list back leaves the actuators past it limp.

Builds a command for num_actuators actuators.

Parameters:

Name Type Description Default
num_actuators int

Actuators commanded, 0 to MAX_ACTUATORS.

required
actuators Sequence[MotorCommand]

One MotorCommand per actuator; an actuator past the list is limp: zero gains, targets and torque.

()

Raises:

Type Description
InvalidArgumentError

num_actuators is negative or past MAX_ACTUATORS, or actuators has more than MAX_ACTUATORS entries.

Attributes:

Name Type Description
num_actuators int

Actuators commanded, 0 to MAX_ACTUATORS.

actuators list[MotorCommand]

One MotorCommand per actuator, the command's own: setting a field changes what that actuator is sent.

num_actuators property writable

num_actuators: int

Actuators commanded, 0 to MAX_ACTUATORS.

Raises:

Type Description
InvalidArgumentError

On assignment, a count that is negative or past MAX_ACTUATORS.

actuators property writable

actuators: list[MotorCommand]

One MotorCommand per actuator, the command's own: setting a field changes what that actuator is sent.

Raises:

Type Description
InvalidArgumentError

On assignment, a list of more than MAX_ACTUATORS entries.

CorrectionOverride dataclass

CorrectionOverride(
    scale: float | None = None,
    offset_nm: float | None = None,
)

What a command replaces of one feedforward term's correction while that command is tracked.

The term is sent as scale times the term as computed, plus offset_nm; a field left None keeps the tuning's. A command refuses a scale that is negative or not finite, and an offset that is not finite, when it is built.

Attributes:

Name Type Description
scale float | None

What the term is multiplied by: 1 sends it as computed, 0 none of it; None keeps the tuning's.

offset_nm float | None

What is added to the term after, in newton meters; None keeps the tuning's.

scale class-attribute instance-attribute

scale: float | None = None

What the term is multiplied by: 1 sends it as computed, 0 none of it; None keeps the tuning's.

offset_nm class-attribute instance-attribute

offset_nm: float | None = None

What is added to the term after, in newton meters; None keeps the tuning's.

JointOverride dataclass

JointOverride(
    kp: float | None = None,
    kd: float | None = None,
    gravity: CorrectionOverride = CorrectionOverride(
        scale=None, offset_nm=None
    ),
    tau_add_nm: float | None = None,
)

What a command replaces of one chain joint's tuning while that command is tracked.

A field left None keeps the tuning's. A command refuses a gain that is negative or not finite, and a torque that is not finite, when it is built.

Attributes:

Name Type Description
kp float | None

Position gain in place of the tuning's, stepped to at once rather than slewed, and still capped by the guards.

kd float | None

Velocity gain in place of the tuning's, as kp is.

gravity CorrectionOverride

What replaces the fields of the joint's gravity correction it sets.

tau_add_nm float | None

Torque added to the feedforward after every term, in newton meters: it rides on top of the system's own.

kp class-attribute instance-attribute

kp: float | None = None

Position gain in place of the tuning's, stepped to at once rather than slewed, and still capped by the guards.

kd class-attribute instance-attribute

kd: float | None = None

Velocity gain in place of the tuning's, as kp is.

gravity class-attribute instance-attribute

gravity: CorrectionOverride = CorrectionOverride(
    scale=None, offset_nm=None
)

What replaces the fields of the joint's gravity correction it sets.

tau_add_nm class-attribute instance-attribute

tau_add_nm: float | None = None

Torque added to the feedforward after every term, in newton meters: it rides on top of the system's own.

GripperOverride dataclass

GripperOverride(
    kp: float | None = None,
    kd: float | None = None,
    assist: CorrectionOverride = CorrectionOverride(
        scale=None, offset_nm=None
    ),
    tau_add_nm: float | None = None,
)

What a command replaces of an arm's gripper tuning while that command is tracked.

A field left None keeps the tuning's. A command refuses a gain that is negative or not finite, and a torque that is not finite, when it is built.

Attributes:

Name Type Description
kp float | None

Gripper position gain in place of the tuning's.

kd float | None

Gripper velocity gain in place of the tuning's.

assist CorrectionOverride

A correction of the grip assist's torque, which the tuning sends as computed: scale = 0.0 is no assist.

tau_add_nm float | None

Torque added to the gripper's feedforward after the assist, in newton meters.

kp class-attribute instance-attribute

kp: float | None = None

Gripper position gain in place of the tuning's.

kd class-attribute instance-attribute

kd: float | None = None

Gripper velocity gain in place of the tuning's.

assist class-attribute instance-attribute

assist: CorrectionOverride = CorrectionOverride(
    scale=None, offset_nm=None
)

A correction of the grip assist's torque, which the tuning sends as computed: scale = 0.0 is no assist.

tau_add_nm class-attribute instance-attribute

tau_add_nm: float | None = None

Torque added to the gripper's feedforward after the assist, in newton meters.

ArmOverride dataclass

ArmOverride(
    name: str,
    side: str | None,
    role: str,
    joints: tuple[JointOverride, ...],
    gripper: GripperOverride = GripperOverride(
        kp=None,
        kd=None,
        assist=CorrectionOverride(
            scale=None, offset_nm=None
        ),
        tau_add_nm=None,
    ),
)

What a command replaces of one arm's tuning: its chain joints in chain order, then its gripper.

One comes from a TuningOverride, which builds one for every arm of a robot.

Attributes:

Name Type Description
name str

The arm's name, as its anvil.workcell.ArmConfig states it.

side str | None

The arm's side, as its anvil.workcell.ArmConfig states it; None for an arm with no side.

role str

The arm's role, as its anvil.workcell.ArmConfig states it.

joints tuple[JointOverride, ...]

One JointOverride per chain joint, in chain order.

gripper GripperOverride

What the command replaces of the gripper's tuning.

name instance-attribute

name: str

The arm's name, as its anvil.workcell.ArmConfig states it.

side instance-attribute

side: str | None

The arm's side, as its anvil.workcell.ArmConfig states it; None for an arm with no side.

role instance-attribute

role: str

The arm's role, as its anvil.workcell.ArmConfig states it.

joints instance-attribute

joints: tuple[JointOverride, ...]

One JointOverride per chain joint, in chain order.

gripper class-attribute instance-attribute

gripper: GripperOverride = GripperOverride(
    kp=None,
    kd=None,
    assist=CorrectionOverride(scale=None, offset_nm=None),
    tau_add_nm=None,
)

What the command replaces of the gripper's tuning.

TuningOverride

TuningOverride(robot: RobotConfig)

What a command replaces of its arms' tunings, for as long as that command is the one tracked.

Built for a robot, it is shaped like it and addressed the way its configuration is: arms in actuator order, arms_where and arm picking arms as anvil.workcell.RobotConfig does, and each arm's joints in chain order. Every field starts None, which keeps the tuning's; a joint the arm does not have is an IndexError where it is asked for. A JointCommand and an EePoseCommand carry one; a JointTrajectoryCommand does not.

It lasts while the command carrying it is tracked, so every command carries it: one without hands the actuators straight back to their tunings, and a stream that goes stale eases them back over the tuning's slew.

Example
overrides = anvil.control.TuningOverride(robot=robot)
overrides.arms[0].joints[2].kp = 2.0
overrides.arm(name="left").gripper.assist.scale = 0.0
runtime.send_command(command=anvil.control.EePoseCommand(targets=arm_targets, overrides=overrides))

Builds an override that replaces nothing, for every arm of robot and every chain joint each has.

Parameters:

Name Type Description Default
robot RobotConfig

The robot whose arms the override addresses.

required

Methods:

Name Description
arms_where

Returns the arms of the side and the role asked for, in actuator order.

arm

Returns the arm named name, or None for a name the robot has none of.

Attributes:

Name Type Description
arms

One ArmOverride per arm, in actuator order.

arms instance-attribute

arms

One ArmOverride per arm, in actuator order.

arms_where

arms_where(
    side: str | None = None, role: str | None = None
) -> list[ArmOverride]

Returns the arms of the side and the role asked for, in actuator order.

Picks them as anvil.workcell.RobotConfig.arms_where does.

Parameters:

Name Type Description Default
side str | None

The side to pick; None for any.

None
role str | None

The role to pick; None for any.

None

Returns:

Type Description
list[ArmOverride]

The arms that match; an empty list where none does.

arm

arm(name: str) -> ArmOverride | None

Returns the arm named name, or None for a name the robot has none of.

Parameters:

Name Type Description Default
name str

The arm's name, as its anvil.workcell.ArmConfig states it.

required

GripperTarget

GripperTarget(
    position_rad: float, velocity_radps: float = 0.0
)

Bases: Struct

The gripper motor's own target: an angle, and the rate it is to be turning at as it passes it.

A GripperCommand carries one for an application that would rather drive the motor than ask for a jaw opening. An angle past the gripper's travel is held at the end of it. Read-only once built.

Builds a motor target; a value that is not finite is refused when a command carrying it is built.

Parameters:

Name Type Description Default
position_rad float

The motor angle asked for, in radians.

required
velocity_radps float

The rate the motor is to be turning at as it passes the angle, in radians per second, sent to the drive beside it; zero asks it to arrive at rest, not to hold where it is.

0.0

Attributes:

Name Type Description
position_rad float

The motor angle asked for, in radians.

velocity_radps float

The rate the motor is to be turning at as it passes the angle, in radians per second; 0 arrives at rest.

position_rad property

position_rad: float

The motor angle asked for, in radians.

velocity_radps property

velocity_radps: float

The rate the motor is to be turning at as it passes the angle, in radians per second; 0 arrives at rest.

GripperCommand

GripperCommand(
    opening_m: float = 0.0,
    speed_mps: float = 0.0,
    motor: GripperTarget | None = None,
)

Bases: Struct

What a command asks of one arm's gripper: a jaw opening and the rate it opens at, or the motor's own target.

Every command that drives a gripper carries the same ask, so a gripper is asked for the same way everywhere: an ArmTarget holds one, and a JointCommand one per arm. An opening is the width between the jaws, which the gripper's map turns into a motor angle; one past the gripper's travel is held at the end of it. Read-only once built.

Builds the ask; a value that is read and not finite is refused when a command carrying it is built.

Parameters:

Name Type Description Default
opening_m float

The jaw opening asked for, in meters; not read when motor is given.

0.0
speed_mps float

The rate the jaws are to be opening at as they pass opening_m, in meters per second, sent to the drive beside it; zero asks them to arrive at rest, not to hold where they are. Not read when motor is given.

0.0
motor GripperTarget | None

The motor's own angle and rate, driven instead of the opening; None drives the opening.

None

Attributes:

Name Type Description
opening_m float

The jaw opening asked for, in meters.

speed_mps float

The rate the jaws are to be opening at as they pass opening_m, in meters per second; 0 arrives at rest.

motor GripperTarget | None

The motor target driven instead of the opening; None where the opening is what was asked.

opening_m property

opening_m: float = 0.0

The jaw opening asked for, in meters.

speed_mps property

speed_mps: float = 0.0

The rate the jaws are to be opening at as they pass opening_m, in meters per second; 0 arrives at rest.

motor property

motor: GripperTarget | None = None

The motor target driven instead of the opening; None where the opening is what was asked.

JointCommand

JointCommand(
    q: Sequence[float],
    qd: Sequence[float],
    grips: Sequence[GripperCommand],
    overrides: TuningOverride | None = None,
)

Bases: Struct

The JointController's command: a position and velocity target per actuator, and one GripperCommand per arm.

The per-actuator lists are indexed as a JointState is. A gripper's actuator in q and qd is not read: the gripper is asked for by its arm's GripperCommand, since what an application has is a jaw opening, not a motor angle. A position past a joint's limit is commanded at the limit, at rest, unless the arm's LimitGuards let it past. Read-only once built.

The controller takes a command only when it covers every actuator and every arm of the robot; Runtime.send_command refuses one that does not, and a controller ticked by hand holds its last target instead.

Builds a command, checking that a drive can be sent it.

Parameters:

Name Type Description Default
q Sequence[float]

Position target per actuator, in radians; its length is the actuator count, num_actuators.

required
qd Sequence[float]

Velocity target per actuator, in radians per second, one per entry of q.

required
grips Sequence[GripperCommand]

One GripperCommand per arm, in the robot's arm order. It must speak for every arm, or the controller does not take the command: an arm left out would be asked shut.

required
overrides TuningOverride | None

What this command replaces of the arms' tunings while it is tracked; None replaces nothing.

None

Raises:

Type Description
TypeError

grips is one GripperCommand rather than a list, or q or qd holds something other than numbers.

InvalidArgumentError

q is empty or longer than MAX_ACTUATORS, qd is not one entry per entry of q, an entry is not finite, grips has more than MAX_ARMS entries or one whose value that is read is not finite, or overrides sets a gain or scale that is negative or not finite or a torque or offset that is not finite.

Attributes:

Name Type Description
num_actuators int

Actuators commanded: len(q) at construction.

num_arms int

Arms whose grippers the command speaks for: len(grips) at construction.

q list[float]

Position target per actuator, in radians.

qd list[float]

Velocity target per actuator, in radians per second.

grips list[GripperCommand]

What each arm's gripper is asked, in the robot's arm order.

num_actuators property

num_actuators: int

Actuators commanded: len(q) at construction.

num_arms property

num_arms: int

Arms whose grippers the command speaks for: len(grips) at construction.

q property

q: list[float]

Position target per actuator, in radians.

qd property

qd: list[float]

Velocity target per actuator, in radians per second.

grips property

grips: list[GripperCommand]

What each arm's gripper is asked, in the robot's arm order.

Provenance

Provenance()

Bases: Counted

Where each actuator's gains and feedforward came from on one tick, as every trace carries it.

Each feedforward term as computed and as sent, what a command added after them, and the gains the tuning's slew asked for before a command's override or a guard. What was sent in the end is the tick's RobotCommand; between the two, a recording says which layer moved a joint. Each list is one entry per actuator, and an actuator the controller does not drive reads zero. One comes from a trace's provenance.

Get one from a trace's provenance, never by calling the class.

Attributes:

Name Type Description
tau_gravity list[float]

Chain gravity per actuator at the measured configuration, uncorrected, in newton meters.

tau_gravity_sent list[float]

The gravity term per actuator as sent, after the tuning's correction or a command's, in newton meters.

tau_assist list[float]

The grip assist on a gripper's actuator as its law computes it, in newton meters; zero on other actuators.

tau_assist_sent list[float]

The grip assist on a gripper's actuator as sent, after a command's correction, in newton meters.

tau_added list[float]

What a command added to each actuator's feedforward after every term, in newton meters.

kp_tuned list[float]

The position gain per actuator the tuning's slew asked for, before any override or guard.

kd_tuned list[float]

The velocity gain per actuator the tuning's slew asked for, before any override or guard.

tau_gravity property

tau_gravity: list[float]

Chain gravity per actuator at the measured configuration, uncorrected, in newton meters.

tau_gravity_sent property

tau_gravity_sent: list[float]

The gravity term per actuator as sent, after the tuning's correction or a command's, in newton meters.

tau_assist property

tau_assist: list[float]

The grip assist on a gripper's actuator as its law computes it, in newton meters; zero on other actuators.

tau_assist_sent property

tau_assist_sent: list[float]

The grip assist on a gripper's actuator as sent, after a command's correction, in newton meters.

tau_added property

tau_added: list[float]

What a command added to each actuator's feedforward after every term, in newton meters.

kp_tuned property

kp_tuned: list[float]

The position gain per actuator the tuning's slew asked for, before any override or guard.

kd_tuned property

kd_tuned: list[float]

The velocity gain per actuator the tuning's slew asked for, before any override or guard.

JointTrace

JointTrace()

Bases: Counted

The JointController's trace of one tick: the command it tracked, and why each actuator was sent what it was.

One comes from JointController.tick or a Snapshot's trace; each per-actuator list is one entry per actuator. Read-only.

Get one from JointController.tick or a Snapshot's trace, never by calling the class.

Attributes:

Name Type Description
command_seq int

The sequence number of the command tracked this tick; 0 until the first command is taken.

command_age_ns int

Age of the command tracked at this tick, in nanoseconds, on the state's clock.

provenance Provenance

Where each actuator's gains and feedforward came from this tick.

gain_ramp float

How far the gains have slewed toward what the tunings ask for: 0 at the request, 1 once they are there.

guards_fired int

The actuators a guard clamped this tick, as a bit mask: bit n is actuator n.

guards_fired_actuators list[int]

The actuators a guard clamped this tick, lowest first; the robot's layout().actuator_names names them.

limit_clamped int

Actuators held at a limit this tick because the command asked past it, a bit mask: bit n is actuator n.

limit_clamped_actuators list[int]

The actuators held at a limit this tick, lowest first; the robot's layout().actuator_names names them.

command_seq property

command_seq: int

The sequence number of the command tracked this tick; 0 until the first command is taken.

command_age_ns property

command_age_ns: int

Age of the command tracked at this tick, in nanoseconds, on the state's clock.

Keeps growing while the stream is stale and the controller holds; before the first command, the time since activate.

provenance property

provenance: Provenance

Where each actuator's gains and feedforward came from this tick.

gain_ramp property

gain_ramp: float

How far the gains have slewed toward what the tunings ask for: 0 at the request, 1 once they are there.

guards_fired property

guards_fired: int

The actuators a guard clamped this tick, as a bit mask: bit n is actuator n.

guards_fired_actuators property

guards_fired_actuators: list[int]

The actuators a guard clamped this tick, lowest first; the robot's layout().actuator_names names them.

limit_clamped property

limit_clamped: int

Actuators held at a limit this tick because the command asked past it, a bit mask: bit n is actuator n.

A limit is a chain joint's, or the gripper's travel.

limit_clamped_actuators property

limit_clamped_actuators: list[int]

The actuators held at a limit this tick, lowest first; the robot's layout().actuator_names names them.

LeaderFollowerTrace

LeaderFollowerTrace()

Bases: Counted

The LeaderFollowerController's trace of one tick: what each follower was sent from, and why.

One comes from LeaderFollowerController.tick or a Snapshot's trace; each per-actuator list is one entry per actuator. Read-only.

Get one from LeaderFollowerController.tick or a Snapshot's trace, never by calling the class.

Attributes:

Name Type Description
q_leader list[float]

By follower actuator, the position measured this tick on the leader actuator it tracks, in radians.

provenance Provenance

Where each actuator's gains and feedforward came from this tick.

gain_ramp float

How far the gains have slewed toward what the tunings ask for: 0 at the request, 1 once they are there.

guards_fired int

The actuators a guard clamped this tick, as a bit mask: bit n is actuator n.

guards_fired_actuators list[int]

The actuators a guard clamped this tick, lowest first; the robot's layout().actuator_names names them.

limit_clamped int

Actuators held at a limit this tick because the leader stood past it, as a bit mask: bit n is actuator n.

limit_clamped_actuators list[int]

The actuators held at a limit this tick, lowest first; the robot's layout().actuator_names names them.

q_leader property

q_leader: list[float]

By follower actuator, the position measured this tick on the leader actuator it tracks, in radians.

On a gripper's actuator it is the leader handle's motor angle, whose share of its travel the follower's gripper opens to; zero on a leader's actuators. While engaged, the follower's target is this clamped to its limits.

provenance property

provenance: Provenance

Where each actuator's gains and feedforward came from this tick.

gain_ramp property

gain_ramp: float

How far the gains have slewed toward what the tunings ask for: 0 at the request, 1 once they are there.

guards_fired property

guards_fired: int

The actuators a guard clamped this tick, as a bit mask: bit n is actuator n.

guards_fired_actuators property

guards_fired_actuators: list[int]

The actuators a guard clamped this tick, lowest first; the robot's layout().actuator_names names them.

limit_clamped property

limit_clamped: int

Actuators held at a limit this tick because the leader stood past it, as a bit mask: bit n is actuator n.

A limit is a chain joint's, or the gripper's travel.

limit_clamped_actuators property

limit_clamped_actuators: list[int]

The actuators held at a limit this tick, lowest first; the robot's layout().actuator_names names them.

Waypoint

Waypoint(
    time_from_start_s: float,
    q: Sequence[float],
    qd: Sequence[float] | None = None,
)

Bases: Struct

One point of a JointTrajectoryCommand: where every actuator is at one time, and how fast it passes there.

Indexed by actuator as a JointState is; a gripper's actuator is its motor angle. Read-only once built.

Builds a waypoint.

Parameters:

Name Type Description Default
time_from_start_s float

When the trajectory reaches it, in seconds after the trajectory starts, rounded to the nanosecond. A first waypoint at 0 is refused when the trajectory is built.

required
q Sequence[float]

Position per actuator, in radians.

required
qd Sequence[float] | None

Velocity per actuator as the trajectory passes through, in radians per second, one per entry of q; None passes it at rest, stopping there before moving on.

None

Raises:

Type Description
TypeError

q or qd holds something other than numbers.

InvalidArgumentError

time_from_start_s is negative or not a number, q is empty or longer than MAX_ACTUATORS, qd is not one entry per entry of q, or an entry is not finite.

Attributes:

Name Type Description
time_from_start_s float

When the trajectory reaches the waypoint, in seconds after it starts.

num_actuators int

Actuators the waypoint was written for: len(q) at construction.

q list[float]

Position per actuator, in radians.

qd list[float]

Velocity per actuator as the trajectory passes through, in radians per second.

time_from_start_s property

time_from_start_s: float

When the trajectory reaches the waypoint, in seconds after it starts.

num_actuators property

num_actuators: int

Actuators the waypoint was written for: len(q) at construction.

q property

q: list[float]

Position per actuator, in radians.

qd property

qd: list[float]

Velocity per actuator as the trajectory passes through, in radians per second.

JointTrajectoryCommand

JointTrajectoryCommand(
    num_actuators: int, points: Sequence[Waypoint]
)

Bases: Struct

The JointTrajectoryController's command: timed waypoints for every actuator, played once from when it is taken.

The controller starts it on the tick it takes the command, from the targets it was sending then, so a trajectory never steps, even one replacing another mid-run. Between waypoints each actuator follows a cubic through both ends at their stated velocities; past the last, the controller holds it. A position past a joint's limit or the gripper's travel is commanded at the limit, at rest. It carries no TuningOverride: it plays under the tuning the controller is configured with. Read-only once built.

Builds a trajectory, checking that it can be played.

Parameters:

Name Type Description Default
num_actuators int

Actuators the trajectory drives: the robot's actuator count, or the controller does not take it.

required
points Sequence[Waypoint]

The waypoints, in time order, each written for num_actuators actuators.

required

Raises:

Type Description
InvalidArgumentError

num_actuators is 0 or past MAX_ACTUATORS; there is no waypoint, or more than MAX_WAYPOINTS; a waypoint was written for another actuator count; the first waypoint is at time 0, or one is not later than the one before it; or the last is not at rest.

Methods:

Name Description
from_waypoints

Builds the trajectory that plays waypoints, a run a robot planned, waypoint for waypoint.

Attributes:

Name Type Description
num_actuators int

Actuators the trajectory drives.

points list[Waypoint]

The waypoints, in time order.

duration_s float

Time from the start to the last waypoint, in seconds.

num_actuators property

num_actuators: int

Actuators the trajectory drives.

points property

points: list[Waypoint]

The waypoints, in time order.

duration_s property

duration_s: float

Time from the start to the last waypoint, in seconds.

from_waypoints classmethod

from_waypoints(waypoints: TimedWaypoints) -> Self

Builds the trajectory that plays waypoints, a run a robot planned, waypoint for waypoint.

Parameters:

Name Type Description Default
waypoints TimedWaypoints

The run, such as anvil.workcell.RobotConfig.home_trajectory's.

required

Raises:

Type Description
TypeError

A waypoint's q or qd holds something other than numbers.

InvalidArgumentError

As the constructor and Waypoint refuse them, a run of more than MAX_WAYPOINTS waypoints among them.

JointTrajectoryTrace

JointTrajectoryTrace()

Bases: Counted

The JointTrajectoryController's trace of one tick: its progress along the trajectory and the tracking error.

One comes from JointTrajectoryController.tick or a Snapshot's trace; each per-actuator list is one entry per actuator. Read-only.

Get one from JointTrajectoryController.tick or a Snapshot's trace, never by calling the class.

Attributes:

Name Type Description
command_seq int

The sequence number of the trajectory tracked this tick; 0 until the first is taken.

elapsed_ns int

Time into the tracked trajectory, in nanoseconds; 0 until the first is taken.

segment int

The index of the waypoint being approached; the waypoint count once the last is reached.

done bool

Whether the last waypoint's time has passed and the targets sent are there, so the controller holds it.

stopped_short bool

Whether the collision stop slowed a step of the trajectory, which ended it.

q_error list[float]

Sampled position target minus measured position per actuator, in radians.

provenance Provenance

Where each actuator's gains and feedforward came from this tick.

gain_ramp float

How far the gains have slewed toward what the tunings ask for: 0 at the request, 1 once they are there.

guards_fired int

The actuators a guard clamped this tick, as a bit mask: bit n is actuator n.

guards_fired_actuators list[int]

The actuators a guard clamped this tick, lowest first; the robot's layout().actuator_names names them.

limit_clamped int

Actuators held at a limit this tick because the trajectory ran past it, a bit mask: bit n is actuator n.

limit_clamped_actuators list[int]

The actuators held at a limit this tick, lowest first; the robot's layout().actuator_names names them.

command_seq property

command_seq: int

The sequence number of the trajectory tracked this tick; 0 until the first is taken.

elapsed_ns property

elapsed_ns: int

Time into the tracked trajectory, in nanoseconds; 0 until the first is taken.

Keeps growing while its last waypoint is held.

segment property

segment: int

The index of the waypoint being approached; the waypoint count once the last is reached.

done property

done: bool

Whether the last waypoint's time has passed and the targets sent are there, so the controller holds it.

stopped_short property

stopped_short: bool

Whether the collision stop slowed a step of the trajectory, which ended it.

The controller holds where the stop sent the arms until it takes a new trajectory.

q_error property

q_error: list[float]

Sampled position target minus measured position per actuator, in radians.

provenance property

provenance: Provenance

Where each actuator's gains and feedforward came from this tick.

gain_ramp property

gain_ramp: float

How far the gains have slewed toward what the tunings ask for: 0 at the request, 1 once they are there.

guards_fired property

guards_fired: int

The actuators a guard clamped this tick, as a bit mask: bit n is actuator n.

guards_fired_actuators property

guards_fired_actuators: list[int]

The actuators a guard clamped this tick, lowest first; the robot's layout().actuator_names names them.

limit_clamped property

limit_clamped: int

Actuators held at a limit this tick because the trajectory ran past it, a bit mask: bit n is actuator n.

A limit is a chain joint's, or the gripper's travel.

limit_clamped_actuators property

limit_clamped_actuators: list[int]

The actuators held at a limit this tick, lowest first; the robot's layout().actuator_names names them.

ArmTarget

ArmTarget(
    pose: Pose,
    grip_m: float,
    reseed_ik: bool = False,
    hand_speed_mps: float = 0.0,
    hold: bool = False,
    vel_ff: float = 0.0,
    grip_rad: float | None = None,
)

Bases: Struct

One arm's target for the EePoseController: a world-frame pose for its tool frame, and what to ask its gripper.

Unless it holds, the controller solves the pose with IK and walks the arm toward the solution, its links moving no faster than the hand driving the target plus a floor, so a still hand has them settle rather than snap. A target that holds stops the arm where the controller has it and leaves the pose untracked: an arm nobody is driving. Read-only once built.

Builds a target; what is wrong with it is refused when an EePoseCommand carrying it is built.

Parameters:

Name Type Description Default
pose Pose

Where the arm's tool frame goes, in the world frame.

required
grip_m float

The jaw opening to ask for, in meters, at rest; not read when grip_rad is given.

required
reseed_ik bool

Whether the controller solves this target from the measured joints instead of its previous solution, as when a clutch closes.

False
hand_speed_mps float

How fast the hand driving the target is translating, in meters per second: the controller lets the arm's links move that fast, plus a floor. Zero for a hand that is still or only turning.

0.0
hold bool

Whether the arm holds where the controller has it, the pose untracked; reseed_ik, hand_speed_mps and vel_ff are then not read.

False
vel_ff float

The share of the rate the arm's own position target moves at that goes out as its velocity target: 0 none, 1 all of it. At 0, kd damps measured velocity and brakes a move that was asked for; at 1 it damps only what the arm does that its target does not.

0.0
grip_rad float | None

The gripper motor's own angle to drive instead of grip_m, in radians; None drives the opening.

None

Attributes:

Name Type Description
pose Pose

Where the arm's tool frame goes, in the world frame.

grip GripperCommand

What the gripper is asked: the same GripperCommand a JointCommand carries per arm.

grip_m float

The jaw opening asked for, in meters.

grip_rad float | None

The motor angle driven instead of the opening, in radians; None where the target asks for the opening.

reseed_ik bool

Whether the controller solves this target from the measured joints instead of its previous solution.

hold bool

Whether the arm holds where the controller has it, the pose untracked.

hand_speed_mps float

How fast the hand driving the target is translating, in meters per second.

vel_ff float

The share of the position target's own rate sent as the velocity target: 0 none, 1 all of it.

pose property

pose: Pose

Where the arm's tool frame goes, in the world frame.

grip property

What the gripper is asked: the same GripperCommand a JointCommand carries per arm.

grip_m property

grip_m: float

The jaw opening asked for, in meters.

grip_rad property

grip_rad: float | None

The motor angle driven instead of the opening, in radians; None where the target asks for the opening.

reseed_ik property

reseed_ik: bool

Whether the controller solves this target from the measured joints instead of its previous solution.

hold property

hold: bool

Whether the arm holds where the controller has it, the pose untracked.

hand_speed_mps property

hand_speed_mps: float

How fast the hand driving the target is translating, in meters per second.

vel_ff property

vel_ff: float

The share of the position target's own rate sent as the velocity target: 0 none, 1 all of it.

EePoseCommand

EePoseCommand(
    targets: Sequence[ArmTarget],
    overrides: TuningOverride | None = None,
    ik: Sequence[IkParams | None] | None = None,
)

Bases: Struct

The EePoseController's command: one ArmTarget per arm, in the robot's arm order.

Entry 0 targets the arm the workcell declares first, so a command never names a side. It may also carry what it replaces of the arms' tunings and the IK parameters each arm solves with, both for as long as it is tracked. The velocity target is the controller's to derive, from each target's vel_ff. The controller takes a command only when it targets every arm of the robot; Runtime.send_command refuses one that does not. Read-only once built.

Builds a command, checking that a drive can be sent it.

Parameters:

Name Type Description Default
targets Sequence[ArmTarget]

One ArmTarget per arm, in the robot's arm order.

required
overrides TuningOverride | None

What this command replaces of the arms' tunings while it is tracked; None replaces nothing.

None
ik Sequence[IkParams | None] | None

One anvil.kinematics.IkParams or None per arm; a set one replaces the controller's configured parameters for that arm while this command is tracked. None, or a list shorter than targets, leaves the rest on the configured ones.

None

Raises:

Type Description
TypeError

targets is one ArmTarget rather than a list.

InvalidArgumentError

targets is empty or longer than MAX_ARMS; a target's pose is not finite or its orientation not a unit quaternion; a grip value that is read is not finite; a tracking target's hand_speed_mps or vel_ff is negative or not finite; overrides sets a gain or scale that is negative or not finite or a torque or offset that is not finite; or ik is longer than targets or holds parameters that are refused.

Attributes:

Name Type Description
num_arms int

Arms targeted: len(targets) at construction.

arms list[ArmTarget]

The targets, in the robot's arm order.

ik list[IkParams | None]

The IK parameters per arm; None for an arm solving with the controller's configured ones.

num_arms property

num_arms: int

Arms targeted: len(targets) at construction.

arms property

arms: list[ArmTarget]

The targets, in the robot's arm order.

ik property

ik: list[IkParams | None]

The IK parameters per arm; None for an arm solving with the controller's configured ones.

EePoseTrace

EePoseTrace()

Bases: Counted

The EePoseController's trace of one tick: the command it tracked, and the IK behind its targets.

One comes from EePoseController.tick or a Snapshot's trace; each per-actuator list is one entry per actuator, and each per-arm list MAX_ARMS entries. Read-only.

Get one from EePoseController.tick or a Snapshot's trace, never by calling the class.

Attributes:

Name Type Description
command_seq int

The sequence number of the command tracked this tick; 0 until the first command is taken.

command_age_ns int

Age of the command tracked at this tick, in nanoseconds, on the state's clock.

q_ik list[float]

The IK solution for the commanded poses per actuator, in radians.

provenance Provenance

Where each actuator's gains and feedforward came from this tick.

gain_ramp float

How far the gains have slewed toward what the tunings ask for: 0 at the request, 1 once they are there.

guards_fired int

The actuators a guard clamped this tick, as a bit mask: bit n is actuator n.

guards_fired_actuators list[int]

The actuators a guard clamped this tick, lowest first; the robot's layout().actuator_names names them.

limit_clamped int

Actuators held at a limit this tick because the command asked past it, a bit mask: bit n is actuator n.

limit_clamped_actuators list[int]

The actuators held at a limit this tick, lowest first; the robot's layout().actuator_names names them.

ik_solved int

The arms IK ran for this tick, on a fresh target, as a bit mask: bit n is arm n.

ik_converged int

The arms whose latest IK solution converged, as a bit mask; 0 before the first solve.

ik_iterations list[int]

Steps the latest IK solve took per arm; 0 before the first.

ik_residual_m list[float]

Tip position error of the latest IK solution per arm, in meters; 0 before the first.

command_seq property

command_seq: int

The sequence number of the command tracked this tick; 0 until the first command is taken.

command_age_ns property

command_age_ns: int

Age of the command tracked at this tick, in nanoseconds, on the state's clock.

Keeps growing while the stream is stale and the controller holds; before the first command, the time since activate.

q_ik property

q_ik: list[float]

The IK solution for the commanded poses per actuator, in radians.

provenance property

provenance: Provenance

Where each actuator's gains and feedforward came from this tick.

gain_ramp property

gain_ramp: float

How far the gains have slewed toward what the tunings ask for: 0 at the request, 1 once they are there.

guards_fired property

guards_fired: int

The actuators a guard clamped this tick, as a bit mask: bit n is actuator n.

guards_fired_actuators property

guards_fired_actuators: list[int]

The actuators a guard clamped this tick, lowest first; the robot's layout().actuator_names names them.

limit_clamped property

limit_clamped: int

Actuators held at a limit this tick because the command asked past it, a bit mask: bit n is actuator n.

A limit is a chain joint's, or the gripper's travel.

limit_clamped_actuators property

limit_clamped_actuators: list[int]

The actuators held at a limit this tick, lowest first; the robot's layout().actuator_names names them.

ik_solved property

ik_solved: int

The arms IK ran for this tick, on a fresh target, as a bit mask: bit n is arm n.

ik_converged property

ik_converged: int

The arms whose latest IK solution converged, as a bit mask; 0 before the first solve.

ik_iterations property

ik_iterations: list[int]

Steps the latest IK solve took per arm; 0 before the first.

ik_residual_m property

ik_residual_m: list[float]

Tip position error of the latest IK solution per arm, in meters; 0 before the first.

GripperMap

GripperMap(
    closed_rad: float, open_rad: float, opening_m: float
)

Bases: Struct

A gripper's linear map from jaw opening to motor angle, as its description states it.

Every width here is the opening between the jaws, both fingers, which is what a person means by how far a gripper is open. One comes from a GripperTuning's map, or from an anvil.workcell.Gripper's gripper_map.

Builds a map.

Parameters:

Name Type Description Default
closed_rad float

Motor angle with the jaws shut, in radians.

required
open_rad float

Motor angle at opening_m, in radians.

required
opening_m float

The jaw opening at full travel, in meters.

required

Methods:

Name Description
width_m

Returns the jaw opening a motor angle corresponds to, in meters, for seeding an application from state.

Attributes:

Name Type Description
closed_rad float

Motor angle with the jaws shut, in radians.

open_rad float

Motor angle at opening_m, in radians.

opening_m float

The jaw opening at full travel, in meters.

closed_rad property

closed_rad: float

Motor angle with the jaws shut, in radians.

open_rad property

open_rad: float

Motor angle at opening_m, in radians.

opening_m property

opening_m: float

The jaw opening at full travel, in meters.

width_m

width_m(position_rad: float) -> float

Returns the jaw opening a motor angle corresponds to, in meters, for seeding an application from state.

The inverse of the map a commanded opening goes through. It is linear and not clamped, so an angle past the travel gives an opening past it.

Parameters:

Name Type Description Default
position_rad float

The motor angle, in radians, as a JointState measures it.

required

GripperTuning

GripperTuning(
    kp: float, kd: float, gripper_map: GripperMap
)

Bases: Struct

The gripper motor's tuning: its gains, the grip assist, and the map from a jaw opening to its motor angle.

One comes from an ArmTuning's gripper. The one a controller's settings() hands back is the settings' own, so an assignment here, then configure, is what the gripper runs with from then on. configure refuses a gain or an assist number that is negative or not finite.

The grip assist is a feedforward torque toward the commanded opening, on top of the gains. Closing pulls max_torque_nm * tanh(stiffness_nm_per_m * x / max_torque_nm) on the jaw-opening error x, in meters; opening pulls opening_scale times the same curve the other way; and a command that asks the jaws shut holds at least shut_torque_nm. max_torque_nm is the squeeze and, against kd, the closing speed; stiffness_nm_per_m is how sharply the jaws stop, and the number to lower for a gripper that hunts.

Builds a tuning with the grip assist off, as a description without one leaves it.

Each of the assist's numbers is assignable afterward; opening_scale starts at 1.

Parameters:

Name Type Description Default
kp float

Position gain, in newton meters per radian of motor angle error.

required
kd float

Velocity gain, in newton meters per radian per second of motor speed error.

required
gripper_map GripperMap

The gripper's map from jaw opening to motor angle.

required

Attributes:

Name Type Description
kp float

Position gain, in newton meters per radian of motor angle error.

kd float

Velocity gain, in newton meters per radian per second of motor speed error.

max_torque_nm float

The torque the assist curve saturates at, in newton meters.

stiffness_nm_per_m float

The assist curve's slope at zero error, in newton meters per meter of jaw opening.

opening_scale float

Opening as a fraction of closing, torque, speed and slope together; 0 turns the opening assist off.

shut_torque_nm float

The least closing torque while a command asks the jaws shut, in newton meters.

gripper_map GripperMap

The map from a commanded jaw opening to the motor angle: the gripper's geometry, from its description.

kp property writable

kp: float

Position gain, in newton meters per radian of motor angle error.

kd property writable

kd: float

Velocity gain, in newton meters per radian per second of motor speed error.

Against the assist's pull, it is what caps the jaws' speed in free air.

max_torque_nm property writable

max_torque_nm: float

The torque the assist curve saturates at, in newton meters.

0 turns the assist off, leaving kp and kd to move the jaws: where kp is a few per cent of the assist, as on the Anvil gripper, they creep.

stiffness_nm_per_m property writable

stiffness_nm_per_m: float

The assist curve's slope at zero error, in newton meters per meter of jaw opening.

opening_scale property writable

opening_scale: float

Opening as a fraction of closing, torque, speed and slope together; 0 turns the opening assist off.

shut_torque_nm property writable

shut_torque_nm: float

The least closing torque while a command asks the jaws shut, in newton meters.

gripper_map property

gripper_map: GripperMap

The map from a commanded jaw opening to the motor angle: the gripper's geometry, from its description.

LimitGuards

LimitGuards(position: bool = True)

Bases: Struct

Which of the limits an arm's description states the tick holds the arm's chain targets to.

One comes from a JointGuards' limits.

Builds the guards, the travel held unless asked otherwise.

Parameters:

Name Type Description Default
position bool

Whether every chain joint's target is clamped to the arm's travel; off, a command past a joint's limit is driven there, into the hardstop if that is where it lies.

True

Attributes:

Name Type Description
position bool

Whether every chain joint's target is clamped to the arm's travel.

position property writable

position: bool = True

Whether every chain joint's target is clamped to the arm's travel.

SpeedLimits

SpeedLimits(
    max_dps: Sequence[float] | None = None,
    tcp_max_mps: float | None = None,
)

Bases: Counted

How fast a controller moves an arm's chain targets: each joint's, and the tool frame's.

A step either would outrun slows the whole arm alike, so it keeps the path it was asked along, and a velocity target either would outrun is slowed alike; no joint's target ever moves faster than the top speed its description states. A step is measured from the target last sent, so an arm lagging or overshooting it never widens the next.

One comes from an ArmTuning's speed, where each field is assignable on its own: tuning.speed.tcp_max_mps = 0.1 keeps the joints' speeds, and max_dps reads one entry per chain joint of the arm. One built here reads as many entries as it was last given. configure refuses a speed that is not positive.

Builds the limits, every joint at up to its top speed and the tool frame at any unless asked otherwise.

Parameters:

Name Type Description Default
max_dps Sequence[float] | None

The fastest each chain joint's target moves, in degrees per second, chain order; None moves each at up to its top speed, as does a joint past the list.

None
tcp_max_mps float | None

The fastest the tool frame moves, in meters per second; None for any speed.

None

Raises:

Type Description
TypeError

max_dps holds something other than numbers.

InvalidArgumentError

max_dps has more entries than anvil.kinematics.MAX_DOF.

Attributes:

Name Type Description
max_dps list[float] | None

The fastest each chain joint's target moves, in degrees per second; None for each at up to its top speed.

tcp_max_mps float | None

The fastest the tool frame moves, meters per second; None for any speed.

max_dps property writable

max_dps: list[float] | None = None

The fastest each chain joint's target moves, in degrees per second; None for each at up to its top speed.

One entry per chain joint, chain order: the arm's joints for limits read from an ArmTuning, as many as were last given for limits built here. A shorter list assigned leaves the joints past it at up to their top speed, and reads back infinite there.

Raises:

Type Description
TypeError

On assignment, a list holding something other than numbers.

InvalidArgumentError

On assignment, a list with more entries than the arm has chain joints, or for limits built here, than anvil.kinematics.MAX_DOF.

tcp_max_mps property writable

tcp_max_mps: float | None = None

The fastest the tool frame moves, meters per second; None for any speed.

JointGuards

JointGuards(
    max_kp: Sequence[float] | None = None,
    max_kd: Sequence[float] | None = None,
    gain_slew_s: float | None = _DEFAULT_GAIN_SLEW_S,
    limits: LimitGuards | None = None,
)

Bases: Counted

What the tick clamps an arm to, whatever asked for it: the arm's own tuning, or a command's override.

None is a guard that is off. The gain ceilings are chain order, one entry per chain joint; the gripper's actuator is not guarded here. A tuning read from a description brings the guards its gains imply; one built from bare numbers slews its gains, holds the arm to its travel, and bounds nothing else. A guard clamps silently inside the tick, and the trace's guards_fired reports it; configure refuses a tuning whose gains are past its own ceilings. One comes from an ArmTuning's guards, whose ceilings read one entry per chain joint of the arm; one built here reads as many entries as the longer ceiling it was given.

Builds guards with every gain ceiling off and the gains slewing, unless asked otherwise.

Parameters:

Name Type Description Default
max_kp Sequence[float] | None

Ceiling on the position gain per chain joint, in newton meters per radian, chain order; None for none. A joint past the list is not bounded.

None
max_kd Sequence[float] | None

Ceiling on the velocity gain per chain joint, in newton meters per radian per second, as max_kp is.

None
gain_slew_s float | None

How long a gain takes to reach what it is asked for, in seconds, zero or more; None steps it there. The default is the slew a tuning built from bare numbers carries.

_DEFAULT_GAIN_SLEW_S
limits LimitGuards | None

Which of the arm's described limits its targets are held to; None for LimitGuards(), the travel held.

None

Raises:

Type Description
TypeError

A ceiling holds something other than numbers.

InvalidArgumentError

gain_slew_s is negative or not finite, or a ceiling has more entries than anvil.kinematics.MAX_DOF.

Attributes:

Name Type Description
max_kp list[float] | None

Ceiling on the position gain per chain joint, in newton meters per radian, chain order; None when off.

max_kd list[float] | None

Ceiling on the velocity gain per chain joint, in newton meters per radian per second; None when off.

gain_slew_s float | None

How long a gain takes to reach what it is asked for, in seconds; None steps it there.

limits LimitGuards

Which of the arm's described limits its targets are held to.

max_kp property

max_kp: list[float] | None = None

Ceiling on the position gain per chain joint, in newton meters per radian, chain order; None when off.

One entry per chain joint, infinite for a joint the ceiling does not bound.

max_kd property

max_kd: list[float] | None = None

Ceiling on the velocity gain per chain joint, in newton meters per radian per second; None when off.

The list reads as max_kp's does.

gain_slew_s property writable

gain_slew_s: float | None = 1.0

How long a gain takes to reach what it is asked for, in seconds; None steps it there.

Raises:

Type Description
InvalidArgumentError

On assignment, a duration that is negative or not finite.

limits property

limits: LimitGuards = LimitGuards(position=True)

Which of the arm's described limits its targets are held to.

ArmTuning

ArmTuning(
    kp: Sequence[float],
    kd: Sequence[float],
    gravity_scale: Sequence[float],
    gravity_offset_nm: Sequence[float] | None = None,
    gripper: GripperTuning | None = None,
    speed: SpeedLimits | None = None,
    guards: JointGuards | None = None,
)

Bases: Struct

One arm's tuning: gains and gravity correction per chain joint, the gripper's tuning, speed limits and guards.

One comes from a controller's settings() or a ControlArm's tuning, or is built here from bare numbers. The per-joint lists read as one entry per chain joint, chain order, and an entry is assignable in place, tuning.kp[3] = 20.0; what a controller runs is the tuning as it stands when it is handed to configure. configure refuses a gain or gravity scale that is negative or not finite, an offset that is not finite, or a gain past its guard's ceiling.

Builds a tuning from bare numbers, one entry per chain joint in chain order.

Parameters:

Name Type Description Default
kp Sequence[float]

Position gain per chain joint, in newton meters per radian.

required
kd Sequence[float]

Velocity gain per chain joint, in newton meters per radian per second.

required
gravity_scale Sequence[float]

What the gravity feedforward is multiplied by per chain joint: 1 sends gravity as computed, 0 none.

required
gravity_offset_nm Sequence[float] | None

What is added to the gravity feedforward after, per chain joint, in newton meters; None adds nothing.

None
gripper GripperTuning | None

The gripper motor's tuning, for an arm driven with one; None for an arm without.

None
speed SpeedLimits | None

How fast the chain's targets move; None moves each joint at up to its top speed and the tool frame at any.

None
guards JointGuards | None

What the tick clamps the arm to; None for JointGuards(), whose gains slew and which bounds nothing but the travel.

None

Raises:

Type Description
TypeError

A list holds something other than numbers.

InvalidArgumentError

The lists are not all one length, or that length is 0 or more than a chain has joints.

Methods:

Name Description
scaled

Returns the same tuning with the gains and the feedforward dialed down, for bringing an arm up in rungs.

Attributes:

Name Type Description
kp list[float]

Position gain per chain joint, in newton meters per radian; an entry is assignable in place.

kd list[float]

Velocity gain per chain joint, in newton meters per radian per second; assignable in place as kp is.

gravity_scale list[float]

What the gravity feedforward is multiplied by per chain joint: 1 sends it as computed, 0 none.

gravity_offset_nm list[float]

What is added to the gravity feedforward after, per chain joint, in newton meters.

gripper GripperTuning | None

The gripper motor's tuning, this tuning's own; None for an arm driven without one.

speed SpeedLimits

How fast the chain's targets move, this tuning's own; assigning one copies it in.

guards JointGuards

What the tick clamps this arm to, a command's overrides included; assigning one copies it in.

kp property

kp: list[float]

Position gain per chain joint, in newton meters per radian; an entry is assignable in place.

Assigning an entry writes it into this tuning; adding or removing one raises anvil.InvalidArgumentError.

kd property

kd: list[float]

Velocity gain per chain joint, in newton meters per radian per second; assignable in place as kp is.

gravity_scale property

gravity_scale: list[float]

What the gravity feedforward is multiplied by per chain joint: 1 sends it as computed, 0 none.

gravity_offset_nm property

gravity_offset_nm: list[float]

What is added to the gravity feedforward after, per chain joint, in newton meters.

gripper property

gripper: GripperTuning | None

The gripper motor's tuning, this tuning's own; None for an arm driven without one.

speed property writable

speed: SpeedLimits

How fast the chain's targets move, this tuning's own; assigning one copies it in.

guards property writable

guards: JointGuards

What the tick clamps this arm to, a command's overrides included; assigning one copies it in.

scaled

scaled(
    kp_scale: float = 1.0,
    kd_scale: float = 1.0,
    gravity_scale: float = 1.0,
) -> Self

Returns the same tuning with the gains and the feedforward dialed down, for bringing an arm up in rungs.

The chain's and the gripper's gains scale by kp_scale and kd_scale. Each joint's gravity correction, scale and offset alike, and the grip assist's max_torque_nm, stiffness_nm_per_m and shut_torque_nm scale by gravity_scale; the gripper's map and the assist's opening_scale do not. A negative scale is not refused here; configure refuses the negative gains it gives.

Parameters:

Name Type Description Default
kp_scale float

Factor on every position gain: 0 is limp, 1 the gain as it stands.

1.0
kd_scale float

Factor on every velocity gain.

1.0
gravity_scale float

Factor on the gravity feedforward and the grip assist.

1.0

Returns:

Type Description
Self

A new tuning; this one is unchanged.

JointControllerSettings

JointControllerSettings(
    tuning: Sequence[ArmTuning | None] | None = None,
)

Bases: _ArmTuningSettings

What the JointController is configured with: one ArmTuning per arm.

The controller's settings() comes back with every arm it drives filled in, and its tunings are the settings' own: settings.tuning[0].kp[3] = 20.0, then configure, is a retune.

Builds settings that retune the arms given and leave the rest as they are.

Parameters:

Name Type Description Default
tuning Sequence[ArmTuning | None] | None

One ArmTuning or None per arm, in actuator order; a None entry, an arm past the list, or a None list leaves that arm's tuning as it is. configure refuses a tuning for an arm the controller does not drive.

None

Raises:

Type Description
TypeError

tuning is one ArmTuning rather than a list.

InvalidArgumentError

tuning has more than MAX_ARMS entries.

Attributes:

Name Type Description
tuning list[ArmTuning | None]

One ArmTuning or None per arm in actuator order, through the last arm given: the settings' own.

tuning property

tuning: list[ArmTuning | None] = []

One ArmTuning or None per arm in actuator order, through the last arm given: the settings' own.

JointTrajectoryControllerSettings

JointTrajectoryControllerSettings(
    tuning: Sequence[ArmTuning | None] | None = None,
)

Bases: _ArmTuningSettings

What the JointTrajectoryController is configured with: one ArmTuning per arm.

The controller's settings() comes back with every arm it drives filled in, and its tunings are the settings' own: settings.tuning[0].kp[3] = 20.0, then configure, is a retune, which a trajectory playing goes on under.

Builds settings that retune the arms given and leave the rest as they are.

Parameters:

Name Type Description Default
tuning Sequence[ArmTuning | None] | None

One ArmTuning or None per arm, in actuator order; a None entry, an arm past the list, or a None list leaves that arm's tuning as it is. configure refuses a tuning for an arm the controller does not drive.

None

Raises:

Type Description
TypeError

tuning is one ArmTuning rather than a list.

InvalidArgumentError

tuning has more than MAX_ARMS entries.

Attributes:

Name Type Description
tuning list[ArmTuning | None]

One ArmTuning or None per arm in actuator order, through the last arm given: the settings' own.

tuning property

tuning: list[ArmTuning | None] = []

One ArmTuning or None per arm in actuator order, through the last arm given: the settings' own.

ForceFeedback

ForceFeedback(enabled: bool, stiffness_share: float)

Bases: Struct

How hard each leader is pulled toward its follower, so the operator's hand feels what holds the follower back.

One comes from a LeaderFollowerControllerSettings' force_feedback. configure refuses a stiffness_share that is not a number from 0 to 0.4.

Builds the feedback.

Parameters:

Name Type Description Default
enabled bool

Whether the leaders are pulled at all; off, they float under the hand but for gravity, whatever the share.

required
stiffness_share float

Each leader joint's position gain as a share of its follower's, from 0 to 0.4.

required

Attributes:

Name Type Description
enabled bool

Whether the leaders are pulled toward their followers at all.

stiffness_share float

Each leader joint's position gain as a share of its follower's, from 0 to 0.4.

enabled property writable

enabled: bool

Whether the leaders are pulled toward their followers at all.

stiffness_share property writable

stiffness_share: float

Each leader joint's position gain as a share of its follower's, from 0 to 0.4.

LeaderFollowerControllerSettings

LeaderFollowerControllerSettings(
    tuning: Sequence[ArmTuning | None] | None = None,
    force_feedback: ForceFeedback | None = None,
    engaged: bool | None = None,
)

Bases: Struct

What the LeaderFollowerController is configured with: a tuning per arm, the force feedback, and the engagement.

A leader's gains are the feedback's, so only its gravity correction and guards are taken from its tuning. Disengaged, the followers hold still and the leaders float; engaging moves each follower to its leader first. The controller's settings() comes back with everything filled, and its objects are the settings' own: settings.force_feedback.enabled = True, then configure, turns the feedback on.

Builds settings that change what is given and leave the rest as it is.

Parameters:

Name Type Description Default
tuning Sequence[ArmTuning | None] | None

One ArmTuning or None per arm, leaders and followers alike, in actuator order; a None entry, an arm past the list, or a None list leaves that arm's tuning as it is.

None
force_feedback ForceFeedback | None

How hard each leader is pulled toward its follower; None leaves the feedback as it is.

None
engaged bool | None

Whether the followers mirror their leaders; None leaves the engagement as it is.

None

Raises:

Type Description
TypeError

tuning is one ArmTuning rather than a list.

InvalidArgumentError

tuning has more than MAX_ARMS entries.

Attributes:

Name Type Description
tuning list[ArmTuning | None]

One ArmTuning or None per arm in actuator order, through the last arm given: the settings' own.

force_feedback ForceFeedback | None

The force feedback, the settings' own; None leaves the controller's as it is.

engaged bool | None

Whether the followers mirror their leaders; None leaves it as it is.

tuning property

tuning: list[ArmTuning | None] = []

One ArmTuning or None per arm in actuator order, through the last arm given: the settings' own.

force_feedback property

force_feedback: ForceFeedback | None = None

The force feedback, the settings' own; None leaves the controller's as it is.

engaged property writable

engaged: bool | None = None

Whether the followers mirror their leaders; None leaves it as it is.

EePoseControllerSettings

EePoseControllerSettings(
    tuning: Sequence[ArmTuning | None] | None = None,
    ik: Sequence[IkParams | None] | None = None,
)

Bases: Struct

What the EePoseController is configured with: one ArmTuning and one set of IK parameters per arm.

The controller's settings() comes back with every arm filled, and its objects are the settings' own: settings.ik[0].rest_q[1] = 0.2, then configure, is a retune. A command's own IK parameters win over these while that command is tracked.

Builds settings that retune the arms given and leave the rest as they are.

Parameters:

Name Type Description Default
tuning Sequence[ArmTuning | None] | None

One ArmTuning or None per arm, in actuator order; a None entry, an arm past the list, or a None list leaves that arm's tuning as it is.

None
ik Sequence[IkParams | None] | None

One anvil.kinematics.IkParams or None per arm, in actuator order, left as tuning is.

None

Raises:

Type Description
TypeError

tuning or ik is one value rather than a list.

InvalidArgumentError

tuning or ik has more than MAX_ARMS entries.

Attributes:

Name Type Description
tuning list[ArmTuning | None]

One ArmTuning or None per arm in actuator order, through the last arm given: the settings' own.

ik list[IkParams | None]

One anvil.kinematics.IkParams or None per arm, in order, through the last given: the settings' own.

tuning property

tuning: list[ArmTuning | None] = []

One ArmTuning or None per arm in actuator order, through the last arm given: the settings' own.

ik property

ik: list[IkParams | None] = []

One anvil.kinematics.IkParams or None per arm, in order, through the last given: the settings' own.

ControlArm

ControlArm()

One arm as a controller drives it: the actuators it owns, its chain, and the tuning its description states.

Its actuators run from its first: its chain joints in chain order, then its gripper when it has one. One comes from control_arms.

Get one from control_arms, never by calling the class.

Methods:

Name Description
tip_pose

Returns where its tool frame is at the actuator positions q, in the world frame.

gripper_width_m

Returns the jaws' opening at the actuator positions q, in meters, read back through the gripper's map.

Attributes:

Name Type Description
chain_actuators list[int]

Its chain joints' actuators, in chain order.

gripper_actuator int | None

Its gripper's actuator — the last it owns — or None without one.

tuning ArmTuning

A copy of the tuning it runs with, as its description states it for the controller it was cut for.

chain_actuators property

chain_actuators: list[int]

Its chain joints' actuators, in chain order.

gripper_actuator property

gripper_actuator: int | None

Its gripper's actuator — the last it owns — or None without one.

tuning property

tuning: ArmTuning

A copy of the tuning it runs with, as its description states it for the controller it was cut for.

tip_pose

tip_pose(q: Sequence[float]) -> Pose

Returns where its tool frame is at the actuator positions q, in the world frame.

Parameters:

Name Type Description Default
q Sequence[float]

Position per actuator of the whole robot, in radians, as a Snapshot's q or q_commanded carries them; entries past the arm's actuators are not read.

required

Returns:

Type Description
Pose

The tool frame's pose.

Raises:

Type Description
TypeError

q holds something other than numbers.

InvalidArgumentError

q stops short of the arm's last actuator.

gripper_width_m

gripper_width_m(q: Sequence[float]) -> float | None

Returns the jaws' opening at the actuator positions q, in meters, read back through the gripper's map.

Parameters:

Name Type Description Default
q Sequence[float]

Position per actuator of the whole robot, in radians, as tip_pose takes it.

required

Returns:

Type Description
float | None

The opening; None for an arm without a gripper.

Raises:

Type Description
TypeError

q holds something other than numbers.

InvalidArgumentError

q stops short of the arm's last actuator.

CollisionClosing

Bases: NamedTuple

The link whose closing the collision stop held the arms for, what on, and how near, from CollisionHold.

Attributes:

Name Type Description
link str

The link that was closing, as the composed URDF names it.

on_link str | None

The other link it was closing on; None where it was closing on a keep-out box.

keep_out str | None

The keep-out box it was closing on, by name; None where it was closing on another link.

distance_m float

How near the two stood as the stop last measured them before it took hold, in meters; negative inside a box.

margin_m float

The margin the stop held them short of, in meters.

link: str

The link that was closing, as the composed URDF names it.

on_link: str | None

The other link it was closing on; None where it was closing on a keep-out box.

keep_out instance-attribute

keep_out: str | None

The keep-out box it was closing on, by name; None where it was closing on another link.

distance_m instance-attribute

distance_m: float

How near the two stood as the stop last measured them before it took hold, in meters; negative inside a box.

margin_m instance-attribute

margin_m: float

The margin the stop held them short of, in meters.

CollisionHold

Bases: NamedTuple

Why the collision stop held the arms, as CollisionStop.last_stop reports it.

Attributes:

Name Type Description
why str

Which link was closing on what, and how near, as a person reads it.

closing CollisionClosing | None

The link that was closing and what on; None where the stop failed, holding the arms wherever they were.

why instance-attribute

why: str

Which link was closing on what, and how near, as a person reads it.

closing instance-attribute

closing: CollisionClosing | None

The link that was closing and what on; None where the stop failed, holding the arms wherever they were.

CollisionStop

CollisionStop(
    workcell: WorkcellConfig,
    within_arm_margin_m: float | None = DEFAULT_MARGIN_M,
    between_arms_margin_m: float | None = DEFAULT_MARGIN_M,
    keep_out_margins_m: Mapping[str, float] | None = None,
)

A guard that holds the arms short of hitting themselves or a keep-out box, whichever controller drives them.

Hand the one stop to every controller of the robot, JointController(robot=robot, collision_stop=stop), and each tick it slows the step a controller is about to send, but only along what that step closes on. Every pair, two links or a link and a box, that stands within its margin and a few centimeters past it is a gap the arms may close only so fast: by a share of what is left of it past the margin each tick, so a pair slows as it nears its margin and stops there, within about 150 milliseconds. The rest of the step goes through, so an arm driven sideways into something slides along it, and one driven away leaves at once. A pair already inside its margin, as the arms start or as a box goes up around them, may only open: the arms may move out of a box, never deeper in. Two links of one arm are held to one margin, a link of one arm and one of another arm or of the torso to another, and each box to its own; anvil.workcell.Clearance.arm_of says whose a link is. A trajectory the stop slows ends there. The gaps are measured on a thread of the stop's own; should it fail, or fall behind where the arms go, every controller holds the arms where they are.

The cut is made joint by joint, so an arm driven by joint targets that run on through what it would hit, as a joint stream or a leader does, comes to rest at the point nearest its target joint by joint, which slides it less than a hand driven by pose. It is a stop, not a guarantee: it measures the collision pieces' hulls, and steps along each gap as a straight line from where it was measured, so an arm can come inside a margin by what that misses. It judges the targets, not the measured arm: an arm too soft to hold its own weight, under a low gain override, sags past where it is held. It lives as long as this object or any controller holding it does.

Builds a stop for the robot of workcell and starts its thread.

Parameters:

Name Type Description Default
workcell WorkcellConfig

The workcell whose robot and keep-out boxes the stop guards.

required
within_arm_margin_m float | None

How near two links of one arm may close, in meters; None lets them touch.

DEFAULT_MARGIN_M
between_arms_margin_m float | None

How near a link of one arm may close on one of another arm, or of the torso the arms stand on, in meters; None lets them touch.

DEFAULT_MARGIN_M
keep_out_margins_m Mapping[str, float] | None

How near a link may close on each keep-out box, in meters, by box name; a box left out is not guarded. None guards every box at DEFAULT_MARGIN_M.

None

Raises:

Type Description
InvalidArgumentError

A margin is negative or not a number, keep_out_margins_m names a box the workcell has none of, or the robot's collision geometry cannot be built.

Methods:

Name Description
configure

Changes the margins and the keep-out boxes guarded, from the stop's next measure.

holding

Returns whether the stop holds the arms short now: it slowed a step in the last 0.3 seconds, or failed.

last_stop

Returns why the stop last held the arms, kept after it lets them go; None until it first has.

Attributes:

Name Type Description
DEFAULT_MARGIN_M float

The margin a stop holds links and boxes at unless asked for another, in meters.

DEFAULT_MARGIN_M class-attribute

DEFAULT_MARGIN_M: float = 0.002

The margin a stop holds links and boxes at unless asked for another, in meters.

configure

configure(
    workcell: WorkcellConfig,
    within_arm_margin_m: float | None = DEFAULT_MARGIN_M,
    between_arms_margin_m: float | None = DEFAULT_MARGIN_M,
    keep_out_margins_m: Mapping[str, float] | None = None,
) -> None

Changes the margins and the keep-out boxes guarded, from the stop's next measure.

The robot stays the one the stop was made for; only workcell's keep-out boxes are read. A box put up around the arms holds them in no more than it must: they may move out of it, never deeper. Settings equal to the ones the stop has change nothing.

Parameters:

Name Type Description Default
workcell WorkcellConfig

The workcell whose keep-out boxes the stop guards from now on.

required
within_arm_margin_m float | None

How near two links of one arm may close, in meters; None lets them touch.

DEFAULT_MARGIN_M
between_arms_margin_m float | None

How near a link of one arm may close on one of another arm, or of the torso the arms stand on, in meters; None lets them touch.

DEFAULT_MARGIN_M
keep_out_margins_m Mapping[str, float] | None

How near a link may close on each keep-out box, in meters, by box name; a box left out is not guarded. None guards every box at DEFAULT_MARGIN_M.

None

Raises:

Type Description
InvalidArgumentError

A margin is negative or not a number, or keep_out_margins_m names a box workcell has none of.

holding

holding() -> bool

Returns whether the stop holds the arms short now: it slowed a step in the last 0.3 seconds, or failed.

last_stop

last_stop() -> CollisionHold | None

Returns why the stop last held the arms, kept after it lets them go; None until it first has.

Controller

Controller(
    robot: RobotConfig,
    collision_stop: CollisionStop | None = None,
)

Bases: Part

What the four controllers share: built for a robot, warm-started, ticked and retuned the same way.

It is the type a Runtime takes its controllers as and Runtime.controller looks one up by. Build one of the four, JointController, JointTrajectoryController, EePoseController or LeaderFollowerController, never this.

Builds the controller for robot, each arm tuned as its description states for this controller.

Its arms are robot's in actuator order, as control_arms cuts them for this class, and a command's actuators are theirs. Hand it to a Runtime, which takes it over; ticked by hand instead, call activate before the first tick.

Parameters:

Name Type Description Default
robot RobotConfig

The robot to drive.

required
collision_stop CollisionStop | None

The stop that holds every target this controller sends short of a collision, built from the workcell whose robot is robot; None sends targets into one.

None

Raises:

Type Description
InvalidArgumentError

robot is refused or its chains cannot be cut from its URDF; collision_stop was built for another robot, its URDF, placement or actuators not robot's; for a LeaderFollowerController, robot has no leader.

Methods:

Name Description
activate

Warm-starts the controller from measured state; call it before the first tick.

tick

Runs one tick on measured state, tracking command, and returns what to send and why.

deactivate

Stops tracking, before another controller takes over; whatever comes next does its own blending.

configure

Retunes the arms; an arm the settings leave None keeps what it runs with.

settings

Returns what the controller is configured with now, every arm it drives filled in.

validate_settings

Checks settings against what the controller runs with now, without applying it.

close

Frees the part now rather than once it is collected.

activate

activate(
    t_ns: int,
    seq: int,
    state: JointState,
    rate_hz: int | None = None,
    last_written: RobotCommand | None = None,
) -> None

Warm-starts the controller from measured state; call it before the first tick.

The gains ramp from those of last_written to what the tunings ask for, over each arm's JointGuards.gain_slew_s; out of idle that is from zero, so the arm comes up limp and takes its gains on. Each actuator's target starts where last_written held it where that had a position gain, and where the actuator measures otherwise, clamped to its limits; the first tick's command moves the chain on from there no faster than the arm's SpeedLimits, as every later one does.

Parameters:

Name Type Description Default
t_ns int

When state was measured, in nanoseconds, on the hw interface's clock.

required
seq int

The state's sequence number.

required
state JointState

Measured state of every actuator.

required
rate_hz int | None

The rate the caller ticks at, which the gain ramp is counted in: 250 or 500; None for the loop's default.

None
last_written RobotCommand | None

What was sent the tick before: another controller's output, at a switch; None for limp, as out of idle.

None

Raises:

Type Description
InvalidArgumentError

rate_hz is not a rate the loop runs at.

tick

tick(
    t_ns: int,
    seq: int,
    state: JointState,
    command: JointCommand
    | JointTrajectoryCommand
    | TimedWaypoints
    | EePoseCommand
    | None = None,
    command_t_ns: int | None = None,
    command_seq: int | None = None,
) -> tuple[
    RobotCommand,
    JointTrace
    | JointTrajectoryTrace
    | EePoseTrace
    | LeaderFollowerTrace,
]

Runs one tick on measured state, tracking command, and returns what to send and why.

A command is taken only on a tick where command_seq differs from the last one taken, and where it covers every actuator and arm the controller drives; the JointController and the EePoseController also skip one older than 100 milliseconds by command_t_ns against t_ns. A command not taken leaves the controller on its last target. The JointTrajectoryController starts a trajectory at t_ns of the tick it takes it, whatever its stamp.

Parameters:

Name Type Description Default
t_ns int

When state was measured, in nanoseconds, on the hw interface's clock.

required
seq int

The state's sequence number.

required
state JointState

Measured state of every actuator.

required
command JointCommand | JointTrajectoryCommand | TimedWaypoints | EePoseCommand | None

The latest command, of this controller's command class, or for the JointTrajectoryController the anvil.workcell.TimedWaypoints it is built from; None for none, as between commands.

None
command_t_ns int | None

When the command was sent, in nanoseconds, on the clock t_ns is on; required with a command.

None
command_seq int | None

The command's sequence number, counting from 1; required with a command.

None

Returns:

Type Description
tuple[RobotCommand, JointTrace | JointTrajectoryTrace | EePoseTrace | LeaderFollowerTrace]

The tick's RobotCommand, and this controller's trace: a JointTrace, JointTrajectoryTrace or EePoseTrace.

Raises:

Type Description
TypeError

command is another controller's command class, or is given without command_t_ns and command_seq.

InvalidArgumentError

command_seq is 0 with a command.

deactivate

deactivate() -> None

Stops tracking, before another controller takes over; whatever comes next does its own blending.

configure

configure(settings: ControllerSettings) -> None

Retunes the arms; an arm the settings leave None keeps what it runs with.

The gains slew to the new tuning over each arm's JointGuards.gain_slew_s. Nothing changes when settings is refused.

Parameters:

Name Type Description Default
settings ControllerSettings

This controller's own settings class, usually its settings(), edited.

required

Raises:

Type Description
TypeError

settings is another controller's settings class.

InvalidArgumentError

settings is one validate_settings refuses.

settings

settings() -> ControllerSettings

Returns what the controller is configured with now, every arm it drives filled in.

The objects in it are the settings' own, so an edit to one, then configure, is a retune.

validate_settings

validate_settings(settings: ControllerSettings) -> None

Checks settings against what the controller runs with now, without applying it.

Parameters:

Name Type Description Default
settings ControllerSettings

This controller's own settings class.

required

Raises:

Type Description
TypeError

settings is another controller's settings class.

InvalidArgumentError

A tuning or IK parameters for an arm the controller does not drive; a tuning whose chain joints or gripper differ from that arm's; a gain or gravity scale that is negative or not finite, an offset that is not finite, or a gain past its guard's ceiling; a speed that is not positive; a grip assist number that is negative or not finite; for the EePoseController, IK parameters that are refused; for the LeaderFollowerController, a stiffness_share that is not from 0 to 0.4.

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.

JointController

JointController(
    robot: RobotConfig,
    collision_stop: CollisionStop | None = None,
)

Bases: Controller

The controller that tracks streamed joint targets, one JointCommand at a time, for every arm of a robot.

Each command's positions are carried on at its velocities until the next arrives, and every command after the first since activate glides in from the targets being sent over the interval commands have been arriving at, so a stream that arrives jittered moves the arm as smoothly as it was sent; one that sends no velocities trails by about one interval. A command older than 100 milliseconds on the state's clock is stale: the controller holds where it carried the arm, at rest, and eases a command's overrides back to the tunings. A position past a joint's limit is commanded at the limit. Each gripper is asked for by its arm's GripperCommand, not by its actuator. configure and settings speak a JointControllerSettings.

Every method but close raises anvil.OperationError once it is closed, or once a Runtime has taken it.

Builds the controller for robot, each arm tuned as its description states for this controller.

Its arms are robot's in actuator order, as control_arms cuts them for this class, and a command's actuators are theirs. Hand it to a Runtime, which takes it over; ticked by hand instead, call activate before the first tick.

Parameters:

Name Type Description Default
robot RobotConfig

The robot to drive.

required
collision_stop CollisionStop | None

The stop that holds every target this controller sends short of a collision, built from the workcell whose robot is robot; None sends targets into one.

None

Raises:

Type Description
InvalidArgumentError

robot is refused or its chains cannot be cut from its URDF; collision_stop was built for another robot, its URDF, placement or actuators not robot's; for a LeaderFollowerController, robot has no leader.

Methods:

Name Description
close

Frees the part now rather than once it is collected.

activate

Warm-starts the controller from measured state; call it before the first tick.

tick

Runs one tick on measured state, tracking command, and returns what to send and why.

deactivate

Stops tracking, before another controller takes over; whatever comes next does its own blending.

configure

Retunes the arms; an arm the settings leave None keeps what it runs with.

settings

Returns what the controller is configured with now, every arm it drives filled in.

validate_settings

Checks settings against what the controller runs with now, without applying it.

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.

activate

activate(
    t_ns: int,
    seq: int,
    state: JointState,
    rate_hz: int | None = None,
    last_written: RobotCommand | None = None,
) -> None

Warm-starts the controller from measured state; call it before the first tick.

The gains ramp from those of last_written to what the tunings ask for, over each arm's JointGuards.gain_slew_s; out of idle that is from zero, so the arm comes up limp and takes its gains on. Each actuator's target starts where last_written held it where that had a position gain, and where the actuator measures otherwise, clamped to its limits; the first tick's command moves the chain on from there no faster than the arm's SpeedLimits, as every later one does.

Parameters:

Name Type Description Default
t_ns int

When state was measured, in nanoseconds, on the hw interface's clock.

required
seq int

The state's sequence number.

required
state JointState

Measured state of every actuator.

required
rate_hz int | None

The rate the caller ticks at, which the gain ramp is counted in: 250 or 500; None for the loop's default.

None
last_written RobotCommand | None

What was sent the tick before: another controller's output, at a switch; None for limp, as out of idle.

None

Raises:

Type Description
InvalidArgumentError

rate_hz is not a rate the loop runs at.

tick

tick(
    t_ns: int,
    seq: int,
    state: JointState,
    command: JointCommand
    | JointTrajectoryCommand
    | TimedWaypoints
    | EePoseCommand
    | None = None,
    command_t_ns: int | None = None,
    command_seq: int | None = None,
) -> tuple[
    RobotCommand,
    JointTrace
    | JointTrajectoryTrace
    | EePoseTrace
    | LeaderFollowerTrace,
]

Runs one tick on measured state, tracking command, and returns what to send and why.

A command is taken only on a tick where command_seq differs from the last one taken, and where it covers every actuator and arm the controller drives; the JointController and the EePoseController also skip one older than 100 milliseconds by command_t_ns against t_ns. A command not taken leaves the controller on its last target. The JointTrajectoryController starts a trajectory at t_ns of the tick it takes it, whatever its stamp.

Parameters:

Name Type Description Default
t_ns int

When state was measured, in nanoseconds, on the hw interface's clock.

required
seq int

The state's sequence number.

required
state JointState

Measured state of every actuator.

required
command JointCommand | JointTrajectoryCommand | TimedWaypoints | EePoseCommand | None

The latest command, of this controller's command class, or for the JointTrajectoryController the anvil.workcell.TimedWaypoints it is built from; None for none, as between commands.

None
command_t_ns int | None

When the command was sent, in nanoseconds, on the clock t_ns is on; required with a command.

None
command_seq int | None

The command's sequence number, counting from 1; required with a command.

None

Returns:

Type Description
tuple[RobotCommand, JointTrace | JointTrajectoryTrace | EePoseTrace | LeaderFollowerTrace]

The tick's RobotCommand, and this controller's trace: a JointTrace, JointTrajectoryTrace or EePoseTrace.

Raises:

Type Description
TypeError

command is another controller's command class, or is given without command_t_ns and command_seq.

InvalidArgumentError

command_seq is 0 with a command.

deactivate

deactivate() -> None

Stops tracking, before another controller takes over; whatever comes next does its own blending.

configure

configure(settings: ControllerSettings) -> None

Retunes the arms; an arm the settings leave None keeps what it runs with.

The gains slew to the new tuning over each arm's JointGuards.gain_slew_s. Nothing changes when settings is refused.

Parameters:

Name Type Description Default
settings ControllerSettings

This controller's own settings class, usually its settings(), edited.

required

Raises:

Type Description
TypeError

settings is another controller's settings class.

InvalidArgumentError

settings is one validate_settings refuses.

settings

settings() -> ControllerSettings

Returns what the controller is configured with now, every arm it drives filled in.

The objects in it are the settings' own, so an edit to one, then configure, is a retune.

validate_settings

validate_settings(settings: ControllerSettings) -> None

Checks settings against what the controller runs with now, without applying it.

Parameters:

Name Type Description Default
settings ControllerSettings

This controller's own settings class.

required

Raises:

Type Description
TypeError

settings is another controller's settings class.

InvalidArgumentError

A tuning or IK parameters for an arm the controller does not drive; a tuning whose chain joints or gripper differ from that arm's; a gain or gravity scale that is negative or not finite, an offset that is not finite, or a gain past its guard's ceiling; a speed that is not positive; a grip assist number that is negative or not finite; for the EePoseController, IK parameters that are refused; for the LeaderFollowerController, a stiffness_share that is not from 0 to 0.4.

JointTrajectoryController

JointTrajectoryController(
    robot: RobotConfig,
    collision_stop: CollisionStop | None = None,
)

Bases: Controller

The controller that plays a JointTrajectoryCommand's timed waypoints and holds the last one.

A trajectory starts from the targets the controller is sending when it takes the command, so nothing steps, and a new one replaces the one playing. It is played once, so it is never stale. The gripper is an actuator like any other: a waypoint states its motor angle. It plays the anvil.workcell.TimedWaypoints a robot plans, such as anvil.workcell.RobotConfig.home_trajectory, as they are. configure and settings speak a JointTrajectoryControllerSettings.

Every method but close raises anvil.OperationError once it is closed, or once a Runtime has taken it.

Builds the controller for robot, each arm tuned as its description states for this controller.

Its arms are robot's in actuator order, as control_arms cuts them for this class, and a command's actuators are theirs. Hand it to a Runtime, which takes it over; ticked by hand instead, call activate before the first tick.

Parameters:

Name Type Description Default
robot RobotConfig

The robot to drive.

required
collision_stop CollisionStop | None

The stop that holds every target this controller sends short of a collision, built from the workcell whose robot is robot; None sends targets into one.

None

Raises:

Type Description
InvalidArgumentError

robot is refused or its chains cannot be cut from its URDF; collision_stop was built for another robot, its URDF, placement or actuators not robot's; for a LeaderFollowerController, robot has no leader.

Methods:

Name Description
close

Frees the part now rather than once it is collected.

activate

Warm-starts the controller from measured state; call it before the first tick.

tick

Runs one tick on measured state, tracking command, and returns what to send and why.

deactivate

Stops tracking, before another controller takes over; whatever comes next does its own blending.

configure

Retunes the arms; an arm the settings leave None keeps what it runs with.

settings

Returns what the controller is configured with now, every arm it drives filled in.

validate_settings

Checks settings against what the controller runs with now, without applying it.

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.

activate

activate(
    t_ns: int,
    seq: int,
    state: JointState,
    rate_hz: int | None = None,
    last_written: RobotCommand | None = None,
) -> None

Warm-starts the controller from measured state; call it before the first tick.

The gains ramp from those of last_written to what the tunings ask for, over each arm's JointGuards.gain_slew_s; out of idle that is from zero, so the arm comes up limp and takes its gains on. Each actuator's target starts where last_written held it where that had a position gain, and where the actuator measures otherwise, clamped to its limits; the first tick's command moves the chain on from there no faster than the arm's SpeedLimits, as every later one does.

Parameters:

Name Type Description Default
t_ns int

When state was measured, in nanoseconds, on the hw interface's clock.

required
seq int

The state's sequence number.

required
state JointState

Measured state of every actuator.

required
rate_hz int | None

The rate the caller ticks at, which the gain ramp is counted in: 250 or 500; None for the loop's default.

None
last_written RobotCommand | None

What was sent the tick before: another controller's output, at a switch; None for limp, as out of idle.

None

Raises:

Type Description
InvalidArgumentError

rate_hz is not a rate the loop runs at.

tick

tick(
    t_ns: int,
    seq: int,
    state: JointState,
    command: JointCommand
    | JointTrajectoryCommand
    | TimedWaypoints
    | EePoseCommand
    | None = None,
    command_t_ns: int | None = None,
    command_seq: int | None = None,
) -> tuple[
    RobotCommand,
    JointTrace
    | JointTrajectoryTrace
    | EePoseTrace
    | LeaderFollowerTrace,
]

Runs one tick on measured state, tracking command, and returns what to send and why.

A command is taken only on a tick where command_seq differs from the last one taken, and where it covers every actuator and arm the controller drives; the JointController and the EePoseController also skip one older than 100 milliseconds by command_t_ns against t_ns. A command not taken leaves the controller on its last target. The JointTrajectoryController starts a trajectory at t_ns of the tick it takes it, whatever its stamp.

Parameters:

Name Type Description Default
t_ns int

When state was measured, in nanoseconds, on the hw interface's clock.

required
seq int

The state's sequence number.

required
state JointState

Measured state of every actuator.

required
command JointCommand | JointTrajectoryCommand | TimedWaypoints | EePoseCommand | None

The latest command, of this controller's command class, or for the JointTrajectoryController the anvil.workcell.TimedWaypoints it is built from; None for none, as between commands.

None
command_t_ns int | None

When the command was sent, in nanoseconds, on the clock t_ns is on; required with a command.

None
command_seq int | None

The command's sequence number, counting from 1; required with a command.

None

Returns:

Type Description
tuple[RobotCommand, JointTrace | JointTrajectoryTrace | EePoseTrace | LeaderFollowerTrace]

The tick's RobotCommand, and this controller's trace: a JointTrace, JointTrajectoryTrace or EePoseTrace.

Raises:

Type Description
TypeError

command is another controller's command class, or is given without command_t_ns and command_seq.

InvalidArgumentError

command_seq is 0 with a command.

deactivate

deactivate() -> None

Stops tracking, before another controller takes over; whatever comes next does its own blending.

configure

configure(settings: ControllerSettings) -> None

Retunes the arms; an arm the settings leave None keeps what it runs with.

The gains slew to the new tuning over each arm's JointGuards.gain_slew_s. Nothing changes when settings is refused.

Parameters:

Name Type Description Default
settings ControllerSettings

This controller's own settings class, usually its settings(), edited.

required

Raises:

Type Description
TypeError

settings is another controller's settings class.

InvalidArgumentError

settings is one validate_settings refuses.

settings

settings() -> ControllerSettings

Returns what the controller is configured with now, every arm it drives filled in.

The objects in it are the settings' own, so an edit to one, then configure, is a retune.

validate_settings

validate_settings(settings: ControllerSettings) -> None

Checks settings against what the controller runs with now, without applying it.

Parameters:

Name Type Description Default
settings ControllerSettings

This controller's own settings class.

required

Raises:

Type Description
TypeError

settings is another controller's settings class.

InvalidArgumentError

A tuning or IK parameters for an arm the controller does not drive; a tuning whose chain joints or gripper differ from that arm's; a gain or gravity scale that is negative or not finite, an offset that is not finite, or a gain past its guard's ceiling; a speed that is not positive; a grip assist number that is negative or not finite; for the EePoseController, IK parameters that are refused; for the LeaderFollowerController, a stiffness_share that is not from 0 to 0.4.

EePoseController

EePoseController(
    robot: RobotConfig,
    collision_stop: CollisionStop | None = None,
)

Bases: Controller

The controller that tracks streamed end-effector poses, one EePoseCommand at a time, solving each with IK.

Each new target is solved from the previous solution, or from the measured joints where it asks to reseed, and the arm walks toward the solution no faster than the hand driving it plus a floor. A command older than 100 milliseconds on the state's clock is stale: the controller holds where it is, at rest. Positions are clamped to each joint's limits and the gripper's travel. An arm without a gripper is driven too, its grip not read. configure and settings speak an EePoseControllerSettings, which carries each arm's IK parameters as well as its tuning.

Every method but close raises anvil.OperationError once it is closed, or once a Runtime has taken it.

Builds the controller for robot, each arm tuned as its description states for this controller.

Its arms are robot's in actuator order, as control_arms cuts them for this class, and a command's actuators are theirs. Hand it to a Runtime, which takes it over; ticked by hand instead, call activate before the first tick.

Parameters:

Name Type Description Default
robot RobotConfig

The robot to drive.

required
collision_stop CollisionStop | None

The stop that holds every target this controller sends short of a collision, built from the workcell whose robot is robot; None sends targets into one.

None

Raises:

Type Description
InvalidArgumentError

robot is refused or its chains cannot be cut from its URDF; collision_stop was built for another robot, its URDF, placement or actuators not robot's; for a LeaderFollowerController, robot has no leader.

Methods:

Name Description
close

Frees the part now rather than once it is collected.

activate

Warm-starts the controller from measured state; call it before the first tick.

tick

Runs one tick on measured state, tracking command, and returns what to send and why.

deactivate

Stops tracking, before another controller takes over; whatever comes next does its own blending.

configure

Retunes the arms; an arm the settings leave None keeps what it runs with.

settings

Returns what the controller is configured with now, every arm it drives filled in.

validate_settings

Checks settings against what the controller runs with now, without applying it.

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.

activate

activate(
    t_ns: int,
    seq: int,
    state: JointState,
    rate_hz: int | None = None,
    last_written: RobotCommand | None = None,
) -> None

Warm-starts the controller from measured state; call it before the first tick.

The gains ramp from those of last_written to what the tunings ask for, over each arm's JointGuards.gain_slew_s; out of idle that is from zero, so the arm comes up limp and takes its gains on. Each actuator's target starts where last_written held it where that had a position gain, and where the actuator measures otherwise, clamped to its limits; the first tick's command moves the chain on from there no faster than the arm's SpeedLimits, as every later one does.

Parameters:

Name Type Description Default
t_ns int

When state was measured, in nanoseconds, on the hw interface's clock.

required
seq int

The state's sequence number.

required
state JointState

Measured state of every actuator.

required
rate_hz int | None

The rate the caller ticks at, which the gain ramp is counted in: 250 or 500; None for the loop's default.

None
last_written RobotCommand | None

What was sent the tick before: another controller's output, at a switch; None for limp, as out of idle.

None

Raises:

Type Description
InvalidArgumentError

rate_hz is not a rate the loop runs at.

tick

tick(
    t_ns: int,
    seq: int,
    state: JointState,
    command: JointCommand
    | JointTrajectoryCommand
    | TimedWaypoints
    | EePoseCommand
    | None = None,
    command_t_ns: int | None = None,
    command_seq: int | None = None,
) -> tuple[
    RobotCommand,
    JointTrace
    | JointTrajectoryTrace
    | EePoseTrace
    | LeaderFollowerTrace,
]

Runs one tick on measured state, tracking command, and returns what to send and why.

A command is taken only on a tick where command_seq differs from the last one taken, and where it covers every actuator and arm the controller drives; the JointController and the EePoseController also skip one older than 100 milliseconds by command_t_ns against t_ns. A command not taken leaves the controller on its last target. The JointTrajectoryController starts a trajectory at t_ns of the tick it takes it, whatever its stamp.

Parameters:

Name Type Description Default
t_ns int

When state was measured, in nanoseconds, on the hw interface's clock.

required
seq int

The state's sequence number.

required
state JointState

Measured state of every actuator.

required
command JointCommand | JointTrajectoryCommand | TimedWaypoints | EePoseCommand | None

The latest command, of this controller's command class, or for the JointTrajectoryController the anvil.workcell.TimedWaypoints it is built from; None for none, as between commands.

None
command_t_ns int | None

When the command was sent, in nanoseconds, on the clock t_ns is on; required with a command.

None
command_seq int | None

The command's sequence number, counting from 1; required with a command.

None

Returns:

Type Description
tuple[RobotCommand, JointTrace | JointTrajectoryTrace | EePoseTrace | LeaderFollowerTrace]

The tick's RobotCommand, and this controller's trace: a JointTrace, JointTrajectoryTrace or EePoseTrace.

Raises:

Type Description
TypeError

command is another controller's command class, or is given without command_t_ns and command_seq.

InvalidArgumentError

command_seq is 0 with a command.

deactivate

deactivate() -> None

Stops tracking, before another controller takes over; whatever comes next does its own blending.

configure

configure(settings: ControllerSettings) -> None

Retunes the arms; an arm the settings leave None keeps what it runs with.

The gains slew to the new tuning over each arm's JointGuards.gain_slew_s. Nothing changes when settings is refused.

Parameters:

Name Type Description Default
settings ControllerSettings

This controller's own settings class, usually its settings(), edited.

required

Raises:

Type Description
TypeError

settings is another controller's settings class.

InvalidArgumentError

settings is one validate_settings refuses.

settings

settings() -> ControllerSettings

Returns what the controller is configured with now, every arm it drives filled in.

The objects in it are the settings' own, so an edit to one, then configure, is a retune.

validate_settings

validate_settings(settings: ControllerSettings) -> None

Checks settings against what the controller runs with now, without applying it.

Parameters:

Name Type Description Default
settings ControllerSettings

This controller's own settings class.

required

Raises:

Type Description
TypeError

settings is another controller's settings class.

InvalidArgumentError

A tuning or IK parameters for an arm the controller does not drive; a tuning whose chain joints or gripper differ from that arm's; a gain or gravity scale that is negative or not finite, an offset that is not finite, or a gain past its guard's ceiling; a speed that is not positive; a grip assist number that is negative or not finite; for the EePoseController, IK parameters that are refused; for the LeaderFollowerController, a stiffness_share that is not from 0 to 0.4.

LeaderFollowerController

LeaderFollowerController(
    robot: RobotConfig,
    collision_stop: CollisionStop | None = None,
)

Bases: Controller

The controller that mirrors each leader arm, moved by hand, on the follower on its side, every tick.

Every follower tracks the joints measured on its leader that tick, positions and rates, clamped to its own limits and held still at one, its gripper opened to the share of its travel the leader's handle stands at. Each leader's chain is sent where its follower stands, pulled there as hard as the ForceFeedback asks and floating while it is off, and its handle where it stands, limp; every arm gets gravity compensation, and only a follower's gripper the grip assist. It takes no command: the measured state is the command. Gains slew from what was on the wire over each arm's JointGuards.gain_slew_s after activate, and over the same slew on a retune, the feedback's included. Disengaged, each follower holds where it stood and each leader floats; engaging, activate included, moves each follower to its leader at a bounded joint speed, the leaders floating, before mirroring resumes. configure and settings speak a LeaderFollowerControllerSettings.

Every method but close raises anvil.OperationError once it is closed, or once a Runtime has taken it.

Builds the controller for robot, each arm tuned as its description states for this controller.

Its arms are robot's in actuator order, as control_arms cuts them for this class, and a command's actuators are theirs. Hand it to a Runtime, which takes it over; ticked by hand instead, call activate before the first tick.

Parameters:

Name Type Description Default
robot RobotConfig

The robot to drive.

required
collision_stop CollisionStop | None

The stop that holds every target this controller sends short of a collision, built from the workcell whose robot is robot; None sends targets into one.

None

Raises:

Type Description
InvalidArgumentError

robot is refused or its chains cannot be cut from its URDF; collision_stop was built for another robot, its URDF, placement or actuators not robot's; for a LeaderFollowerController, robot has no leader.

Methods:

Name Description
tick

Runs one tick on measured state and returns what to send and why; the leaders' joints are the command.

close

Frees the part now rather than once it is collected.

activate

Warm-starts the controller from measured state; call it before the first tick.

deactivate

Stops tracking, before another controller takes over; whatever comes next does its own blending.

configure

Retunes the arms; an arm the settings leave None keeps what it runs with.

settings

Returns what the controller is configured with now, every arm it drives filled in.

validate_settings

Checks settings against what the controller runs with now, without applying it.

tick

tick(
    t_ns: int,
    seq: int,
    state: JointState,
    command: None = None,
    command_t_ns: int | None = None,
    command_seq: int | None = None,
) -> tuple[RobotCommand, LeaderFollowerTrace]

Runs one tick on measured state and returns what to send and why; the leaders' joints are the command.

Parameters:

Name Type Description Default
t_ns int

When state was measured, in nanoseconds, on the hw interface's clock.

required
seq int

The state's sequence number.

required
state JointState

Measured state of every actuator, leaders and followers alike.

required
command None

Must be None: the controller takes no command.

None
command_t_ns int | None

Not read.

None
command_seq int | None

Not read.

None

Returns:

Type Description
tuple[RobotCommand, LeaderFollowerTrace]

The tick's RobotCommand and its LeaderFollowerTrace.

Raises:

Type Description
TypeError

command is not None.

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.

activate

activate(
    t_ns: int,
    seq: int,
    state: JointState,
    rate_hz: int | None = None,
    last_written: RobotCommand | None = None,
) -> None

Warm-starts the controller from measured state; call it before the first tick.

The gains ramp from those of last_written to what the tunings ask for, over each arm's JointGuards.gain_slew_s; out of idle that is from zero, so the arm comes up limp and takes its gains on. Each actuator's target starts where last_written held it where that had a position gain, and where the actuator measures otherwise, clamped to its limits; the first tick's command moves the chain on from there no faster than the arm's SpeedLimits, as every later one does.

Parameters:

Name Type Description Default
t_ns int

When state was measured, in nanoseconds, on the hw interface's clock.

required
seq int

The state's sequence number.

required
state JointState

Measured state of every actuator.

required
rate_hz int | None

The rate the caller ticks at, which the gain ramp is counted in: 250 or 500; None for the loop's default.

None
last_written RobotCommand | None

What was sent the tick before: another controller's output, at a switch; None for limp, as out of idle.

None

Raises:

Type Description
InvalidArgumentError

rate_hz is not a rate the loop runs at.

deactivate

deactivate() -> None

Stops tracking, before another controller takes over; whatever comes next does its own blending.

configure

configure(settings: ControllerSettings) -> None

Retunes the arms; an arm the settings leave None keeps what it runs with.

The gains slew to the new tuning over each arm's JointGuards.gain_slew_s. Nothing changes when settings is refused.

Parameters:

Name Type Description Default
settings ControllerSettings

This controller's own settings class, usually its settings(), edited.

required

Raises:

Type Description
TypeError

settings is another controller's settings class.

InvalidArgumentError

settings is one validate_settings refuses.

settings

settings() -> ControllerSettings

Returns what the controller is configured with now, every arm it drives filled in.

The objects in it are the settings' own, so an edit to one, then configure, is a retune.

validate_settings

validate_settings(settings: ControllerSettings) -> None

Checks settings against what the controller runs with now, without applying it.

Parameters:

Name Type Description Default
settings ControllerSettings

This controller's own settings class.

required

Raises:

Type Description
TypeError

settings is another controller's settings class.

InvalidArgumentError

A tuning or IK parameters for an arm the controller does not drive; a tuning whose chain joints or gripper differ from that arm's; a gain or gravity scale that is negative or not finite, an offset that is not finite, or a gain past its guard's ceiling; a speed that is not positive; a grip assist number that is negative or not finite; for the EePoseController, IK parameters that are refused; for the LeaderFollowerController, a stiffness_share that is not from 0 to 0.4.

ActuatorSnapshot dataclass

ActuatorSnapshot(
    name: str,
    actuator: int,
    q_rad: float,
    q_commanded_rad: float,
    qd_radps: float,
    tau_nm: float,
    status_name: str,
    faulted: bool,
    temp_mos_c: int,
    temp_rotor_c: int,
    guard_fired_since_activation: bool,
    limit_clamped_since_activation: bool,
)

One actuator of a Snapshot, by the joint it drives: what the snapshot's per-actuator lists hold for it.

Get one from an ArmSnapshot, which Snapshot.arm returns. It is a copy, as the snapshot is.

Attributes:

Name Type Description
name str

The joint the actuator drives, by its name in the arm's description, e.g. joint_4; gripper for a gripper.

actuator int

Where the actuator sits in the snapshot's per-actuator lists, as Snapshot.q[actuator] reads it.

q_rad float

Measured position, in radians: the motor's angle for a gripper.

q_commanded_rad float

Position the loop last wrote, in radians: where the actuator is being held.

qd_radps float

Measured velocity, in radians per second.

tau_nm float

Torque as the drive reports it, in newton meters.

status_name str

Drive status by name, as Snapshot.status_names holds it, e.g. Enabled.

faulted bool

Whether the drive reports a fault, as Snapshot.status_faults holds it.

temp_mos_c int

Drive electronics temperature, in degrees Celsius.

temp_rotor_c int

Motor winding temperature, in degrees Celsius.

guard_fired_since_activation bool

Whether a guard clamped this actuator on any tick since the last activation.

limit_clamped_since_activation bool

Whether this actuator was held at a joint limit on any tick since the last activation.

name instance-attribute

name: str

The joint the actuator drives, by its name in the arm's description, e.g. joint_4; gripper for a gripper.

actuator instance-attribute

actuator: int

Where the actuator sits in the snapshot's per-actuator lists, as Snapshot.q[actuator] reads it.

q_rad instance-attribute

q_rad: float

Measured position, in radians: the motor's angle for a gripper.

q_commanded_rad instance-attribute

q_commanded_rad: float

Position the loop last wrote, in radians: where the actuator is being held.

qd_radps instance-attribute

qd_radps: float

Measured velocity, in radians per second.

tau_nm instance-attribute

tau_nm: float

Torque as the drive reports it, in newton meters.

status_name instance-attribute

status_name: str

Drive status by name, as Snapshot.status_names holds it, e.g. Enabled.

faulted instance-attribute

faulted: bool

Whether the drive reports a fault, as Snapshot.status_faults holds it.

temp_mos_c instance-attribute

temp_mos_c: int

Drive electronics temperature, in degrees Celsius.

temp_rotor_c instance-attribute

temp_rotor_c: int

Motor winding temperature, in degrees Celsius.

guard_fired_since_activation instance-attribute

guard_fired_since_activation: bool

Whether a guard clamped this actuator on any tick since the last activation.

limit_clamped_since_activation instance-attribute

limit_clamped_since_activation: bool

Whether this actuator was held at a joint limit on any tick since the last activation.

ArmSnapshot dataclass

ArmSnapshot(
    name: str,
    joints: tuple[ActuatorSnapshot, ...],
    gripper: GripperSnapshot | None,
)

One arm of a Snapshot by name: its chain joints' actuators and its gripper's, each by the joint it drives.

Get one from Snapshot.arm or Snapshot.arms. Each ActuatorSnapshot carries actuator, its index into the snapshot's lists, for a caller that wants the lists themselves.

Methods:

Name Description
joint

Returns the chain joint named name; None when the arm has no chain joint of that name.

Attributes:

Name Type Description
name str

The arm's name, as its anvil.workcell.ArmConfig states it, e.g. left.

joints tuple[ActuatorSnapshot, ...]

Each chain joint's actuator, in the order the arm's description lists them.

gripper GripperSnapshot | None

The gripper's actuator; None for an arm whose gripper the stack does not drive, which has no actuator.

name instance-attribute

name: str

The arm's name, as its anvil.workcell.ArmConfig states it, e.g. left.

joints instance-attribute

joints: tuple[ActuatorSnapshot, ...]

Each chain joint's actuator, in the order the arm's description lists them.

gripper instance-attribute

gripper: GripperSnapshot | None

The gripper's actuator; None for an arm whose gripper the stack does not drive, which has no actuator.

joint

joint(name: str) -> ActuatorSnapshot | None

Returns the chain joint named name; None when the arm has no chain joint of that name.

Parameters:

Name Type Description Default
name str

The joint's name in the arm's description, e.g. joint_4.

required

ControllerHandle

ControllerHandle()

A controller a Runtime holds, reached through that runtime.

The runtime owns its controllers from the moment it is built, so a controller's settings are read and retuned through this handle, and it is activated by passing this handle to Runtime.activate. One comes from Runtime.controller.

Get one from Runtime.controller, never by calling the class.

Methods:

Name Description
configure

Retunes the controller, active or not, returning once a running loop has applied the new settings.

settings

Returns what the controller is configured with now, with every arm it drives filled in.

settings_json

Returns settings as JSON, for a caller that passes it on rather than reading it.

configure_json

Retunes the controller from JSON in the form settings_json writes, as configure does.

tuned_robot

Returns robot with each arm's description stating the tuning this controller runs now.

Attributes:

Name Type Description
name str

Name of the controller, as Runtime.active and Snapshot.active report it, such as "ee_pose".

name property

name: str

Name of the controller, as Runtime.active and Snapshot.active report it, such as "ee_pose".

configure

configure(settings: ControllerSettings) -> None

Retunes the controller, active or not, returning once a running loop has applied the new settings.

The loop applies them at the next tick boundary, and settings read after this returns shows them. The controller keeps them until the next retune, through any activation, and the runtime's other controllers are left as they were. The gains and gravity scale it sends slew to the new ones over the tuning's slew rather than stepping. On a runtime not yet started they are applied at its first tick, and settings shows them from then; one stopped refuses them, since no tick would apply them.

Parameters:

Name Type Description Default
settings ControllerSettings

The settings type the controller's class takes, such as a JointControllerSettings for a JointController; an arm left None keeps what it runs with.

required

Raises:

Type Description
TypeError

settings is the settings type of another controller class.

InvalidArgumentError

A gain is not finite or is negative, a tuning is not the shape of its arm, or the IK parameters are ones no solve can run on; nothing changed.

HwFaultError

The loop has faulted.

OperationError

The runtime has stopped; or the loop is running and did not apply the settings within a second, its thread stalled or dead.

settings

settings() -> ControllerSettings

Returns what the controller is configured with now, with every arm it drives filled in.

Returns:

Type Description
ControllerSettings

The settings type the controller's class takes, such as a JointControllerSettings for a JointController. A configure shows here once it returns.

settings_json

settings_json() -> str

Returns settings as JSON, for a caller that passes it on rather than reading it.

Returns:

Type Description
str

A JSON object holding tuning, a list with one tuning per arm and null past the arms the controller drives, and for an EePoseController also ik, one set of IK parameters per arm the same way. It is what configure_json takes back.

configure_json

configure_json(settings: str) -> None

Retunes the controller from JSON in the form settings_json writes, as configure does.

Parameters:

Name Type Description Default
settings str

The JSON text.

required

Raises:

Type Description
InvalidArgumentError

The text is not that JSON or names a field no setting has, or the settings are refused as configure refuses them; nothing changed.

HwFaultError

The loop has faulted.

OperationError

As for configure: the runtime has stopped, or a running loop did not apply the settings within a second.

tuned_robot

tuned_robot(robot: RobotConfig) -> RobotConfig

Returns robot with each arm's description stating the tuning this controller runs now.

This is what saving a workcell retuned live keeps for its next build. Each chain joint's gains go into this controller's section of the arm's tuning, its gravity correction onto the joint, and the gripper's gains and grip assist onto the gripper, which every controller reads. An EePoseController's IK parameters are not part of a description, so they are not kept.

Parameters:

Name Type Description Default
robot RobotConfig

The robot the runtime was built for.

required

Returns:

Type Description
RobotConfig

A new robot configuration; robot is left as it was.

Raises:

Type Description
TypeError

robot is not a anvil.workcell.RobotConfig.

InvalidArgumentError

robot has another number of arms than the controller drives, or an arm whose chain is not the length of the tuning the controller runs it with.

GripperSnapshot dataclass

GripperSnapshot(
    name: str,
    actuator: int,
    q_rad: float,
    q_commanded_rad: float,
    qd_radps: float,
    tau_nm: float,
    status_name: str,
    faulted: bool,
    temp_mos_c: int,
    temp_rotor_c: int,
    guard_fired_since_activation: bool,
    limit_clamped_since_activation: bool,
    opening_m: float,
)

Bases: ActuatorSnapshot

The gripper of an ArmSnapshot: its actuator, and the jaw opening the motor's measured angle comes to.

Attributes:

Name Type Description
name str

The joint the actuator drives, by its name in the arm's description, e.g. joint_4; gripper for a gripper.

actuator int

Where the actuator sits in the snapshot's per-actuator lists, as Snapshot.q[actuator] reads it.

q_rad float

Measured position, in radians: the motor's angle for a gripper.

q_commanded_rad float

Position the loop last wrote, in radians: where the actuator is being held.

qd_radps float

Measured velocity, in radians per second.

tau_nm float

Torque as the drive reports it, in newton meters.

status_name str

Drive status by name, as Snapshot.status_names holds it, e.g. Enabled.

faulted bool

Whether the drive reports a fault, as Snapshot.status_faults holds it.

temp_mos_c int

Drive electronics temperature, in degrees Celsius.

temp_rotor_c int

Motor winding temperature, in degrees Celsius.

guard_fired_since_activation bool

Whether a guard clamped this actuator on any tick since the last activation.

limit_clamped_since_activation bool

Whether this actuator was held at a joint limit on any tick since the last activation.

opening_m float

The jaw opening q_rad comes to, in meters, through the gripper's map as the arm's description states it.

name instance-attribute

name: str

The joint the actuator drives, by its name in the arm's description, e.g. joint_4; gripper for a gripper.

actuator instance-attribute

actuator: int

Where the actuator sits in the snapshot's per-actuator lists, as Snapshot.q[actuator] reads it.

q_rad instance-attribute

q_rad: float

Measured position, in radians: the motor's angle for a gripper.

q_commanded_rad instance-attribute

q_commanded_rad: float

Position the loop last wrote, in radians: where the actuator is being held.

qd_radps instance-attribute

qd_radps: float

Measured velocity, in radians per second.

tau_nm instance-attribute

tau_nm: float

Torque as the drive reports it, in newton meters.

status_name instance-attribute

status_name: str

Drive status by name, as Snapshot.status_names holds it, e.g. Enabled.

faulted instance-attribute

faulted: bool

Whether the drive reports a fault, as Snapshot.status_faults holds it.

temp_mos_c instance-attribute

temp_mos_c: int

Drive electronics temperature, in degrees Celsius.

temp_rotor_c instance-attribute

temp_rotor_c: int

Motor winding temperature, in degrees Celsius.

guard_fired_since_activation instance-attribute

guard_fired_since_activation: bool

Whether a guard clamped this actuator on any tick since the last activation.

limit_clamped_since_activation instance-attribute

limit_clamped_since_activation: bool

Whether this actuator was held at a joint limit on any tick since the last activation.

opening_m instance-attribute

opening_m: float

The jaw opening q_rad comes to, in meters, through the gripper's map as the arm's description states it.

That is anvil.workcell.ArmDescription.gripper_map's width_m, not a controller's tuning, which a retune may give another map.

HwFaultError

Bases: OperationError

Raised by an anvil.control.Runtime call that would drive the arms or read their state, once the loop faulted.

The loop faults when a call into its hw interface fails, or when a controller hits a bug of its own. It then takes itself to idle and writes nothing more, so the drives hold the last command they were sent, and it goes on running against the last state it read. The fault lasts the runtime's life: every such call raises this from then on, its message saying what failed first. Stop and close the runtime, fix what failed, and build a new one from a new hw interface.

JointTrajectoryHandle

JointTrajectoryHandle()

Bases: ControllerHandle

The handle to a runtime's JointTrajectoryController, which can also play a trajectory through to its end.

One comes from Runtime.controller(kind=JointTrajectoryController).

Get one from Runtime.controller, never by calling the class.

Methods:

Name Description
configure

Retunes the controller, active or not, returning once a running loop has applied the new settings.

settings

Returns what the controller is configured with now, with every arm it drives filled in.

settings_json

Returns settings as JSON, for a caller that passes it on rather than reading it.

configure_json

Retunes the controller from JSON in the form settings_json writes, as configure does.

tuned_robot

Returns robot with each arm's description stating the tuning this controller runs now.

play

Runs trajectory on this controller to its last waypoint, blocking until it is done.

Attributes:

Name Type Description
name str

Name of the controller, as Runtime.active and Snapshot.active report it, such as "ee_pose".

name property

name: str

Name of the controller, as Runtime.active and Snapshot.active report it, such as "ee_pose".

configure

configure(settings: ControllerSettings) -> None

Retunes the controller, active or not, returning once a running loop has applied the new settings.

The loop applies them at the next tick boundary, and settings read after this returns shows them. The controller keeps them until the next retune, through any activation, and the runtime's other controllers are left as they were. The gains and gravity scale it sends slew to the new ones over the tuning's slew rather than stepping. On a runtime not yet started they are applied at its first tick, and settings shows them from then; one stopped refuses them, since no tick would apply them.

Parameters:

Name Type Description Default
settings ControllerSettings

The settings type the controller's class takes, such as a JointControllerSettings for a JointController; an arm left None keeps what it runs with.

required

Raises:

Type Description
TypeError

settings is the settings type of another controller class.

InvalidArgumentError

A gain is not finite or is negative, a tuning is not the shape of its arm, or the IK parameters are ones no solve can run on; nothing changed.

HwFaultError

The loop has faulted.

OperationError

The runtime has stopped; or the loop is running and did not apply the settings within a second, its thread stalled or dead.

settings

settings() -> ControllerSettings

Returns what the controller is configured with now, with every arm it drives filled in.

Returns:

Type Description
ControllerSettings

The settings type the controller's class takes, such as a JointControllerSettings for a JointController. A configure shows here once it returns.

settings_json

settings_json() -> str

Returns settings as JSON, for a caller that passes it on rather than reading it.

Returns:

Type Description
str

A JSON object holding tuning, a list with one tuning per arm and null past the arms the controller drives, and for an EePoseController also ik, one set of IK parameters per arm the same way. It is what configure_json takes back.

configure_json

configure_json(settings: str) -> None

Retunes the controller from JSON in the form settings_json writes, as configure does.

Parameters:

Name Type Description Default
settings str

The JSON text.

required

Raises:

Type Description
InvalidArgumentError

The text is not that JSON or names a field no setting has, or the settings are refused as configure refuses them; nothing changed.

HwFaultError

The loop has faulted.

OperationError

As for configure: the runtime has stopped, or a running loop did not apply the settings within a second.

tuned_robot

tuned_robot(robot: RobotConfig) -> RobotConfig

Returns robot with each arm's description stating the tuning this controller runs now.

This is what saving a workcell retuned live keeps for its next build. Each chain joint's gains go into this controller's section of the arm's tuning, its gravity correction onto the joint, and the gripper's gains and grip assist onto the gripper, which every controller reads. An EePoseController's IK parameters are not part of a description, so they are not kept.

Parameters:

Name Type Description Default
robot RobotConfig

The robot the runtime was built for.

required

Returns:

Type Description
RobotConfig

A new robot configuration; robot is left as it was.

Raises:

Type Description
TypeError

robot is not a anvil.workcell.RobotConfig.

InvalidArgumentError

robot has another number of arms than the controller drives, or an arm whose chain is not the length of the tuning the controller runs it with.

play

play(
    trajectory: JointTrajectoryCommand | TimedWaypoints,
    timeout_s: float,
) -> None

Runs trajectory on this controller to its last waypoint, blocking until it is done.

The controller must be active. The gains first finish ramping from the previous controller's, then the trajectory is sent, and only a trace from a tick after the loop picked it up counts as done. This moves the arms. It waits on ticks and runs none: on a runtime built stepped, another thread must Runtime.advance it meanwhile, or this times out.

Parameters:

Name Type Description Default
trajectory JointTrajectoryCommand | TimedWaypoints

The waypoints to play, timed from when the loop picks it up: a trajectory, or a run a robot planned, such as anvil.workcell.RobotConfig.home_trajectory's, played as it is.

required
timeout_s float

How long to wait for the whole run, the ramp included, in seconds, to the millisecond.

required

Raises:

Type Description
InvalidArgumentError

timeout_s is negative, not finite, or past about 49 days; a run JointTrajectoryCommand.from_waypoints refuses.

HwFaultError

The loop faulted before or during the run.

TimedOutError

timeout_s passed before the trajectory finished.

StoppedShortError

The collision stop slowed a step of the trajectory, which ended it.

PreemptedError

Before it finished, another trajectory was sent to this controller, which replaced it, or another controller, idle or a freeze was activated.

OperationError

The controller is not active, trajectory is not for as many actuators as the controller drives, or the loop is not running.

LinkCounts

LinkCounts()

Bases: Counted

Frames the hw interface sent each drive, the replies that came back and the sends the kernel refused.

Each is a list with one count per actuator, counted since the interface opened, so the difference of two reads is what went through between them. An interface that does not count reports zeros. One comes from Snapshot.link.

Get one from Snapshot.link, never by calling the class.

Attributes:

Name Type Description
sent list[int]

Frames sent, or tried, per actuator: one the kernel refused counts, since it was asked for.

answered list[int]

Replies received to the frames sent, per actuator.

refused list[int]

Frames sent that the kernel refused because its transmit queue was full, per actuator.

sent property

sent: list[int]

Frames sent, or tried, per actuator: one the kernel refused counts, since it was asked for.

answered property

answered: list[int]

Replies received to the frames sent, per actuator.

refused property

refused: list[int]

Frames sent that the kernel refused because its transmit queue was full, per actuator.

These are commands that never left this host, which lost comms alone cannot tell from a loose cable. Always 0 from an interface with no queue to fill, such as the sim's.

LoopTiming

LoopTiming()

Bases: Struct

Where one tick of the loop's time went, as marks and the spans between them.

The marks are CLOCK_MONOTONIC in nanoseconds, the clock time.monotonic_ns() reads and the loop's own, not the hw interface's stamp on the state (Snapshot.t_ns). The spans are in microseconds. One comes from Snapshot.timing, Snapshot.slowest or Snapshot.most_late.

Get one from Snapshot.timing, Snapshot.slowest or Snapshot.most_late, never by calling the class.

Attributes:

Name Type Description
due_ns int

When the tick was due.

read_start_ns int

When the tick started, and the hw interface's read with it.

read_end_ns int

When the hw interface's read returned.

controller_start_ns int

When the active controller's tick started, or idle's null command was built.

controller_end_ns int

When the controller's tick, or idle's null command, was done.

write_start_ns int

When the hw interface's write started.

write_end_ns int

When the hw interface's write returned, where the tick's work ends.

late_us int

How late the tick started after it was due.

read_us int

How long the hw interface's read took.

controller_us int

How long the controller's tick, or idle's null command, took.

write_us int

How long the hw interface's write took.

total_us int

Time from the tick's start to the end of its write, the loop's own work between the steps included.

due_ns property

due_ns: int

When the tick was due.

read_start_ns property

read_start_ns: int

When the tick started, and the hw interface's read with it.

read_end_ns property

read_end_ns: int

When the hw interface's read returned.

controller_start_ns property

controller_start_ns: int

When the active controller's tick started, or idle's null command was built.

controller_end_ns property

controller_end_ns: int

When the controller's tick, or idle's null command, was done.

write_start_ns property

write_start_ns: int

When the hw interface's write started.

write_end_ns property

write_end_ns: int

When the hw interface's write returned, where the tick's work ends.

late_us property

late_us: int

How late the tick started after it was due.

read_us property

read_us: int

How long the hw interface's read took.

controller_us property

controller_us: int

How long the controller's tick, or idle's null command, took.

write_us property

write_us: int

How long the hw interface's write took.

total_us property

total_us: int

Time from the tick's start to the end of its write, the loop's own work between the steps included.

PreemptedError

Bases: OperationError

Raised by anvil.control.JointTrajectoryHandle.play when something took over before the trajectory played out.

Another trajectory was sent to the controller, which replaced it, or another controller, idle or a freeze was activated, as an emergency stop does. The arms do what took over says; the message names it. Send one trajectory at a time, or wait for play to return before sending the next.

Runtime

Runtime(
    hw: HwInterface,
    controllers: Sequence[Controller],
    rate_hz: int | None = None,
    stepped: bool = False,
)

The control loop, running on a thread of its own at a fixed rate and holding every controller it was given.

Each tick it reads the hw interface, applies any requested switch between controllers, hands the active controller the latest command, ticks it, and writes its output; with no controller active it writes the null command, which leaves the arms limp. Build one from an enabled hw interface and the controllers, start it, activate a controller through its handle from controller, and send it commands with send_command. with stops it on the way out, as stop does, however the block is left: on an exception too, the drives are disabled and an arm that is not at rest falls under gravity. Bring the arms to rest inside the block, in a try/finally where an exception could skip it, before leaving it.

Builds the loop over hw and controllers, taking them over.

From here they belong to the runtime, and any call on them raises anvil.OperationError. Scan and enable the drives before this; the runtime only runs the loop. A refused build takes nothing, and every part stays usable.

Parameters:

Name Type Description Default
hw HwInterface

The hw interface the loop drives, an anvil.hw.SimHw or an anvil.hw.DamiaoHw.

required
controllers Sequence[Controller]

One to four controllers, at most one of each class: a JointController, a JointTrajectoryController, an EePoseController or a LeaderFollowerController.

required
rate_hz int | None

The rate the loop runs at for its life: 250 or 500, since at 1000 a seven-drive arm's frames overflow its 1 Mbit/s CAN bus. None is the rate of an anvil.hw.SimHw in a lockstep world, and 500 otherwise.

None
stepped bool

Over an anvil.hw.SimHw in a lockstep world, ticks the loop only when advance asks and never against the wall clock, so the same commands give the same run twice. The first tick runs on start, and an activation or a retune runs the tick that applies it.

False

Raises:

Type Description
TypeError

hw is not a hw interface, controllers is a single controller rather than a list, or one of them is not a controller.

InvalidArgumentError

rate_hz is not 250 or 500, or differs from the rate of a lockstep world; two controllers are of one class; controllers is empty; or stepped is set over a hw interface that keeps its own clock.

OperationError

hw or a controller was closed, or already belongs to another runtime.

Methods:

Name Description
start

Starts the loop on its own thread, returning once it has a measured state or a second has passed.

advance

Runs ticks more ticks of a stepped loop, blocking until they have run.

stop

Stops the loop and waits for its thread to end, closing the hw interface with it.

controller

Returns the handle to this runtime's controller of class kind, to activate it and to read and retune it.

activate

Activates controller at the next tick boundary, blocking until the loop has done it.

deactivate

Deactivates the active controller at the next tick boundary, leaving the arms limp, blocking until done.

freeze

Holds the arms where they are measured at the next tick boundary, blocking until the loop has done it.

active

Returns the name of the active controller, such as "ee_pose"; None when the loop is idle or frozen.

send_command

Submits a command for the active controller, which the loop picks up at its next tick.

latest

Returns a copy of what the loop last measured, wrote and did.

fault_snapshot

Returns what the loop measured, wrote and did on the tick it faulted on; None while it has not faulted.

fault

Returns the message of the fault the loop idled on; None while it has not faulted.

transitions

Returns the switches between controllers since the last call, oldest first.

Attributes:

Name Type Description
rate_hz int

Rate the loop is paced to, in hertz, fixed when the runtime was built.

realtime_refusal str | None

Why the loop thread is not real-time, with what would grant it where anything can.

rate_hz property

rate_hz: int

Rate the loop is paced to, in hertz, fixed when the runtime was built.

realtime_refusal property

realtime_refusal: str | None

Why the loop thread is not real-time, with what would grant it where anything can.

It is not real-time when it runs at the default scheduling policy rather than SCHED_FIFO, or off the cores the kernel isolated for it. Only Linux has SCHED_FIFO, so on any other OS nothing is asked and this says so. None where the OS granted both, and before start.

start

start() -> None

Starts the loop on its own thread, returning once it has a measured state or a second has passed.

On Linux the thread asks the OS for real-time scheduling before its first tick; realtime_refusal says what came of it. Once this returns with a measured state, latest holds it to read and a controller can be activated. A drive that has not answered within the second leaves activate refused until it does.

Raises:

Type Description
OperationError

The runtime was started before; a runtime runs once, so build a new one.

advance

advance(ticks: int) -> None

Runs ticks more ticks of a stepped loop, blocking until they have run.

Each tick is a read, a tick of the active controller and a write that steps the world. The last of them is what latest shows on return. Ticks the loop ran on its own, to apply an activation or a retune, are not counted against these.

Parameters:

Name Type Description Default
ticks int

How many ticks to run past the one latest shows.

required

Raises:

Type Description
InvalidArgumentError

ticks is negative.

OperationError

The runtime was not built stepped, is not started, or was stopped, before or while the ticks ran.

stop

stop() -> None

Stops the loop and waits for its thread to end, closing the hw interface with it.

Closing the hw interface disables the drives, so an arm that is not at rest falls under gravity, and stops a sim server its anvil.sim.World started once nothing else holds that world. latest and active go on reporting the last tick. Stopping again, or stopping a runtime never started, does nothing; one never started closes its hw interface when it is collected.

controller

controller(
    kind: type[JointTrajectoryController],
) -> JointTrajectoryHandle
controller(kind: type[Controller]) -> ControllerHandle
controller(kind: type[Controller]) -> ControllerHandle

Returns the handle to this runtime's controller of class kind, to activate it and to read and retune it.

Parameters:

Name Type Description Default
kind type[Controller]

The controller's class, such as anvil.control.EePoseController.

required

Returns:

Type Description
ControllerHandle

A JointTrajectoryHandle, which can also play a trajectory, for a JointTrajectoryController; a ControllerHandle for any other.

Raises:

Type Description
TypeError

kind is not a controller class.

InvalidArgumentError

The runtime holds no controller of that class.

Example
ee_pose = runtime.controller(kind=anvil.control.EePoseController)
runtime.activate(controller=ee_pose)

activate

activate(controller: ControllerHandle) -> None

Activates controller at the next tick boundary, blocking until the loop has done it.

active and latest show it on return. When another controller is active this is the switch: it is deactivated and this one warm-starts from that tick's measured state and ticks on the same tick, its gains ramping from the outgoing controller's, with no idle tick between. A command sent before this is dropped, so send the first one after it. Activating the active controller does nothing.

Parameters:

Name Type Description Default
controller ControllerHandle

A handle from this runtime's controller.

required

Raises:

Type Description
TypeError

controller is not a controller handle.

InvalidArgumentError

controller is another runtime's.

HwFaultError

The loop has faulted.

OperationError

The runtime is not started, or was stopped; the hw interface has not yet measured a state, since warm-starting from one nothing measured would command a jump; the controller drives another number of actuators than the hw interface measures, built for another robot than the one it drives, which would leave the arms it does not know limp; or the loop did not answer within a second, its thread stalled or dead.

deactivate

deactivate() -> None

Deactivates the active controller at the next tick boundary, leaving the arms limp, blocking until done.

Idle writes the null command, which leaves the arms limp, unless the loop has faulted, when it writes nothing. Deactivating an idle loop does nothing, a faulted one's included: it idled as it faulted, and its drives hold the last command written rather than going limp.

Raises:

Type Description
OperationError

The runtime is not started, or was stopped, or the loop did not answer within a second, its thread stalled or dead.

freeze

freeze() -> None

Holds the arms where they are measured at the next tick boundary, blocking until the loop has done it.

The active controller is deactivated, and every tick after writes the same command until the next activation: each actuator at the position it measured then, at rest, under the gains last written, and each gripper without the grip assist's torque, which would go on closing it. No target the arms were moving to is held to, and commands are refused, as with no controller active. This is what an emergency stop wants. activate goes on from the frozen command; deactivate lets the arms go limp.

Raises:

Type Description
HwFaultError

The loop has faulted, and writes nothing more to hold the arms; the drives hold the last command written.

OperationError

The runtime is not started, or was stopped, or the loop did not answer within a second, its thread stalled or dead.

active

active() -> str | None

Returns the name of the active controller, such as "ee_pose"; None when the loop is idle or frozen.

send_command

send_command(command: Struct | TimedWaypoints) -> None

Submits a command for the active controller, which the loop picks up at its next tick.

The latest submission wins: one not yet picked up is replaced. The loop stamps the command with the measured state of the tick that picks it up, so the controller judges how stale it is from then.

Parameters:

Name Type Description Default
command Struct | TimedWaypoints

A JointCommand for the JointController, a JointTrajectoryCommand or the anvil.workcell.TimedWaypoints it is built from for the JointTrajectoryController, or an EePoseCommand for the EePoseController. The LeaderFollowerController takes none.

required

Raises:

Type Description
TypeError

command is not one of those, or is for a controller this runtime does not hold.

InvalidArgumentError

A TimedWaypoints that JointTrajectoryCommand.from_waypoints refuses.

HwFaultError

The loop has faulted.

OperationError

Nothing would follow it: the runtime is not started or was stopped, no controller is active, another controller is, or it is for another number of actuators or arms than the active controller drives.

latest

latest() -> Snapshot

Returns a copy of what the loop last measured, wrote and did.

Before start it holds nothing measured: ticks is 0 and every per-actuator list is empty. After stop it holds the last tick.

Raises:

Type Description
HwFaultError

The loop has faulted, whether or not the runtime has stopped since: what it last saw is no longer what the arms are doing.

fault_snapshot

fault_snapshot() -> Snapshot | None

Returns what the loop measured, wrote and did on the tick it faulted on; None while it has not faulted.

After a fault the loop writes nothing, and drives that answer only to a command fall silent, so what it reads from then on soon shows every drive lost; this tick shows which one failed. Asking raises nothing, and it stays after stop.

fault

fault() -> str | None

Returns the message of the fault the loop idled on; None while it has not faulted.

It is what the first call into the hw interface to fail said, or, where none failed, what a controller that hit a bug of its own said: the message every anvil.control.HwFaultError from this runtime carries. Asking raises nothing, so a caller can learn why the run ended without a call that is refused. It stays after stop.

transitions

transitions() -> list[tuple[str, list[tuple[str, str]]]]

Returns the switches between controllers since the last call, oldest first.

Each activation, deactivation or freeze that changed the active controller is one, and so is the idle the loop takes on a hw fault. A recorder reading this runtime keeps a cursor of its own, so both get every one.

Returns:

Type Description
list[tuple[str, list[tuple[str, str]]]]

One (kind, fields) tuple per switch: kind is "runtime.transition", and fields is a list of (key, value) pairs, ("from", name) then ("to", name), with "idle" for no controller. Empty when there has been none since the last call.

Snapshot

Snapshot()

Bases: Struct

A copy of what the loop last measured, wrote and did, taken when Runtime.latest was called.

The copy does not change: call Runtime.latest again for a newer one. Every per-actuator list holds one entry per actuator the hw interface measured, and is empty before the loop's first sample.

Get one from Runtime.latest, never by calling the class.

Methods:

Name Description
arm

Returns the arm named name as this snapshot saw it; None for no such arm, or before the first sample.

Attributes:

Name Type Description
t_ns int

The hw interface's stamp on the tick's measured state, in nanoseconds; 0 before the first sample.

num_actuators int

Actuators the hw interface measured, one entry each in every per-actuator list; 0 before the first sample.

q list[float]

Measured position per actuator, in radians.

q_commanded list[float]

Position per actuator the loop last wrote, in radians: where the arms are being held.

qd list[float]

Measured velocity per actuator, in radians per second.

tau list[float]

Torque per actuator as the drives report it, in newton meters.

status list[int]

Drive status code per actuator, verbatim from the drive; status_names decodes them.

status_names list[str]

Drive status name per actuator: Enabled, Disabled, CommunicationLoss, or a fault such as Overcurrent.

status_faults list[bool]

Whether each actuator's drive reports a fault: anything but Enabled or Disabled, unknown codes too.

temp_mos_c list[int]

Driver (MOSFET) temperature per actuator, in degrees Celsius.

temp_rotor_c list[int]

Motor winding temperature per actuator, in degrees Celsius.

temp_rotor_cutoff_c list[float | None]

Winding temperature each actuator's drive cuts out at, in degrees Celsius, per actuator.

robot_command RobotCommand

What the loop last wrote to the drives: the active controller's output, or idle's null command.

trace Struct | None

The tick trace of whichever controller ticked last; None until the first tick with a controller active.

active str | None

Name of the active controller, such as "ee_pose"; None when the loop is idle or frozen.

rate_hz int

Rate the loop is paced to, in hertz, which achieved_hz is measured against.

achieved_hz float

Ticks per second, averaged since Runtime.start.

ticks int

Ticks completed since Runtime.start.

timing LoopTiming

Where the last tick's time went.

slowest LoopTiming

Timing of the tick with the largest total_us since Runtime.start.

most_late LoopTiming

Timing of the tick with the largest late_us since Runtime.start.

guards_fired_since_activation int

Bitmask of the actuators whose guard clamped on any tick since the last activation, bit n for actuator n.

guards_fired_since_activation_actuators list[int]

Actuators in guards_fired_since_activation, lowest first.

limit_clamped_since_activation int

Bitmask of actuators held at a joint limit on any tick since the last activation, bit n for actuator n.

limit_clamped_since_activation_actuators list[int]

Actuators in limit_clamped_since_activation, lowest first.

q_min_since_activation list[float]

Lowest position each actuator has been measured at since the last activation, in radians.

q_max_since_activation list[float]

Highest position each actuator has been measured at since the last activation, in radians.

link LinkCounts

Frames the hw interface has sent each drive, had answered and had refused since it opened.

arms list[ArmSnapshot]

Each arm of the robot by name, in actuator order; empty before the loop's first sample.

t_ns property

t_ns: int

The hw interface's stamp on the tick's measured state, in nanoseconds; 0 before the first sample.

The stamp is on the hw interface's own clock: CLOCK_MONOTONIC on real drives, simulated time in sim. When this host had the state, on the loop's clock, is timing.

num_actuators property

num_actuators: int

Actuators the hw interface measured, one entry each in every per-actuator list; 0 before the first sample.

q property

q: list[float]

Measured position per actuator, in radians.

To hold the arms where they are, command q_commanded, not this: under gravity a hold at the measured position sags a little more every tick.

q_commanded property

q_commanded: list[float]

Position per actuator the loop last wrote, in radians: where the arms are being held.

qd property

qd: list[float]

Measured velocity per actuator, in radians per second.

tau property

tau: list[float]

Torque per actuator as the drives report it, in newton meters.

status property

status: list[int]

Drive status code per actuator, verbatim from the drive; status_names decodes them.

status_names property

status_names: list[str]

Drive status name per actuator: Enabled, Disabled, CommunicationLoss, or a fault such as Overcurrent.

A code this build does not know comes back as hex. Empty before the loop's first tick.

status_faults property

status_faults: list[bool]

Whether each actuator's drive reports a fault: anything but Enabled or Disabled, unknown codes too.

temp_mos_c property

temp_mos_c: list[int]

Driver (MOSFET) temperature per actuator, in degrees Celsius.

temp_rotor_c property

temp_rotor_c: list[int]

Motor winding temperature per actuator, in degrees Celsius.

temp_rotor_cutoff_c property

temp_rotor_cutoff_c: list[float | None]

Winding temperature each actuator's drive cuts out at, in degrees Celsius, per actuator.

The limit temp_rotor_c is heading for, read off each drive when the hw interface last enabled it; past it the drive faults and lets go. None for an actuator whose drive did not answer, and for every actuator of the sim, whose motors do not heat.

robot_command property

robot_command: RobotCommand

What the loop last wrote to the drives: the active controller's output, or idle's null command.

Once the hw interface has faulted, it is the last command written before the fault, which the drives hold.

trace property

trace: Struct | None

The tick trace of whichever controller ticked last; None until the first tick with a controller active.

It is the trace class of that controller: a JointTrace, a JointTrajectoryTrace, an EePoseTrace or a LeaderFollowerTrace. Idle ticks leave the last one in place.

active property

active: str | None

Name of the active controller, such as "ee_pose"; None when the loop is idle or frozen.

rate_hz property

rate_hz: int

Rate the loop is paced to, in hertz, which achieved_hz is measured against.

achieved_hz property

achieved_hz: float

Ticks per second, averaged since Runtime.start.

ticks property

ticks: int

Ticks completed since Runtime.start.

timing property

timing: LoopTiming

Where the last tick's time went.

slowest property

slowest: LoopTiming

Timing of the tick with the largest total_us since Runtime.start.

most_late property

most_late: LoopTiming

Timing of the tick with the largest late_us since Runtime.start.

guards_fired_since_activation property

guards_fired_since_activation: int

Bitmask of the actuators whose guard clamped on any tick since the last activation, bit n for actuator n.

It is the bitwise OR of every tick's guards_fired, so a guard that clamped on one tick out of thousands is still set when a reader polling at a human pace gets round to looking. guards_fired_since_activation_actuators lists the same actuators.

guards_fired_since_activation_actuators property

guards_fired_since_activation_actuators: list[int]

Actuators in guards_fired_since_activation, lowest first.

limit_clamped_since_activation property

limit_clamped_since_activation: int

Bitmask of actuators held at a joint limit on any tick since the last activation, bit n for actuator n.

It is the bitwise OR of every tick's limit_clamped, on the same terms as guards_fired_since_activation.

limit_clamped_since_activation_actuators property

limit_clamped_since_activation_actuators: list[int]

Actuators in limit_clamped_since_activation, lowest first.

q_min_since_activation property

q_min_since_activation: list[float]

Lowest position each actuator has been measured at since the last activation, in radians.

Before any activation, since the loop started.

q_max_since_activation property

q_max_since_activation: list[float]

Highest position each actuator has been measured at since the last activation, in radians.

Before any activation, since the loop started.

link: LinkCounts

Frames the hw interface has sent each drive, had answered and had refused since it opened.

arms property

arms: list[ArmSnapshot]

Each arm of the robot by name, in actuator order; empty before the loop's first sample.

Each holds, by the joint it drives, what this snapshot's per-actuator lists hold for its actuators, and its gripper's jaw opening. The names are those of the robot the runtime's controllers were built for, as it stood when they were built.

Raises:

Type Description
OperationError

The hw interface measured another number of actuators than that robot owns, so the controllers were built for another robot than the one it drives.

arm

arm(name: str) -> ArmSnapshot | None

Returns the arm named name as this snapshot saw it; None for no such arm, or before the first sample.

Parameters:

Name Type Description Default
name str

The arm's name, as its anvil.workcell.ArmConfig states it, e.g. left.

required

Raises:

Type Description
OperationError

As arms raises.

StoppedShortError

Bases: OperationError

Raised by anvil.control.JointTrajectoryHandle.play when the collision stop slowed the trajectory, ending it.

The arms hold where the stop sent them, and the trajectory controller stays active. anvil.control.CollisionStop.last_stop says which links were closing on what.

control_arms

control_arms(
    robot: RobotConfig, kind: type[Controller]
) -> list[ControlArm]

Returns the arms of robot as the controller class kind drives them, in actuator order.

Each carries its actuators, its chain and the tuning its description states for that controller.

Parameters:

Name Type Description Default
robot RobotConfig

The robot whose arms to cut.

required
kind type[Controller]

The controller class, such as EePoseController.

required

Returns:

Type Description
list[ControlArm]

One ControlArm per arm.

Raises:

Type Description
TypeError

kind is not one of the controller classes.

InvalidArgumentError

robot is refused or its chains cannot be cut from its URDF.