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 |
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 |
Provenance |
Where each actuator's gains and feedforward came from on one tick, as every trace carries it. |
JointTrace |
The |
LeaderFollowerTrace |
The |
Waypoint |
One point of a |
JointTrajectoryCommand |
The |
JointTrajectoryTrace |
The |
ArmTarget |
One arm's target for the |
EePoseCommand |
The |
EePoseTrace |
The |
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 |
JointTrajectoryControllerSettings |
What the |
ForceFeedback |
How hard each leader is pulled toward its follower, so the operator's hand feels what holds the follower back. |
LeaderFollowerControllerSettings |
What the |
EePoseControllerSettings |
What the |
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 |
Why the collision stop held the arms, as |
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 |
JointTrajectoryController |
The controller that plays a |
EePoseController |
The controller that tracks streamed end-effector poses, one |
LeaderFollowerController |
The controller that mirrors each leader arm, moved by hand, on the follower on its side, every tick. |
ActuatorSnapshot |
One actuator of a |
ArmSnapshot |
One arm of a |
ControllerHandle |
A controller a |
GripperSnapshot |
The gripper of an |
HwFaultError |
Raised by an |
JointTrajectoryHandle |
The handle to a runtime's |
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 |
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 |
StoppedShortError |
Raised by |
Functions:
| Name | Description |
|---|---|
control_arms |
Returns the arms of |
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 |
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
ControllerSettings = (
JointControllerSettings
| JointTrajectoryControllerSettings
| EePoseControllerSettings
| LeaderFollowerControllerSettings
)
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 |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
A list holds something other than numbers. |
InvalidArgumentError
|
A list has more than |
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 |
list[str]
|
Drive status name per actuator: |
status_faults |
list[bool]
|
Whether each actuator's drive reports a fault: any status but |
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 |
required |
actuators
|
Sequence[MotorCommand]
|
One |
()
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
Attributes:
| Name | Type | Description |
|---|---|---|
num_actuators |
int
|
Actuators commanded, 0 to |
actuators |
list[MotorCommand]
|
One |
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 |
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 |
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; |
offset_nm |
float | None
|
What is added to the term after, in newton meters; |
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 |
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: |
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 |
side |
str | None
|
The arm's side, as its |
role |
str
|
The arm's role, as its |
joints |
tuple[JointOverride, ...]
|
One |
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 |
Attributes:
| Name | Type | Description |
|---|---|---|
arms |
One |
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
|
role
|
str | None
|
The role to pick; |
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 |
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 |
0.0
|
speed_mps
|
float
|
The rate the jaws are to be opening at as they pass |
0.0
|
motor
|
GripperTarget | None
|
The motor's own angle and rate, driven instead of 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 |
motor |
GripperTarget | None
|
The motor target driven instead of the opening; |
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, |
required |
qd
|
Sequence[float]
|
Velocity target per actuator, in radians per second, one per entry of |
required |
grips
|
Sequence[GripperCommand]
|
One |
required |
overrides
|
TuningOverride | None
|
What this command replaces of the arms' tunings while it is tracked; |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
Attributes:
| Name | Type | Description |
|---|---|---|
num_actuators |
int
|
Actuators commanded: |
num_arms |
int
|
Arms whose grippers the command speaks for: |
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 |
guards_fired_actuators |
list[int]
|
The actuators a guard clamped this tick, lowest first; the robot's |
limit_clamped |
int
|
Actuators held at a limit this tick because the command asked past it, a bit mask: bit |
limit_clamped_actuators |
list[int]
|
The actuators held at a limit this tick, lowest first; the robot's |
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 |
guards_fired_actuators |
list[int]
|
The actuators a guard clamped this tick, lowest first; the robot's |
limit_clamped |
int
|
Actuators held at a limit this tick because the leader stood past it, as a bit mask: bit |
limit_clamped_actuators |
list[int]
|
The actuators held at a limit this tick, lowest first; the robot's |
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 |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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: |
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 |
required |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
Methods:
| Name | Description |
|---|---|
from_waypoints |
Builds the trajectory that plays |
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.
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 |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
A waypoint's |
InvalidArgumentError
|
As the constructor and |
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 |
guards_fired_actuators |
list[int]
|
The actuators a guard clamped this tick, lowest first; the robot's |
limit_clamped |
int
|
Actuators held at a limit this tick because the trajectory ran past it, a bit mask: bit |
limit_clamped_actuators |
list[int]
|
The actuators held at a limit this tick, lowest first; the robot's |
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 |
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; |
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, |
0.0
|
grip_rad
|
float | None
|
The gripper motor's own angle to drive instead of |
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 |
grip_m |
float
|
The jaw opening asked for, in meters. |
grip_rad |
float | None
|
The motor angle driven instead of the opening, in radians; |
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. |
grip
property
grip: GripperCommand
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 |
required |
overrides
|
TuningOverride | None
|
What this command replaces of the arms' tunings while it is tracked; |
None
|
ik
|
Sequence[IkParams | None] | None
|
One |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
Attributes:
| Name | Type | Description |
|---|---|---|
num_arms |
int
|
Arms targeted: |
arms |
list[ArmTarget]
|
The targets, in the robot's arm order. |
ik |
list[IkParams | None]
|
The IK parameters per arm; |
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 |
guards_fired_actuators |
list[int]
|
The actuators a guard clamped this tick, lowest first; the robot's |
limit_clamped |
int
|
Actuators held at a limit this tick because the command asked past it, a bit mask: bit |
limit_clamped_actuators |
list[int]
|
The actuators held at a limit this tick, lowest first; the robot's |
ik_solved |
int
|
The arms IK ran for this tick, on a fresh target, as a bit mask: bit |
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 |
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 |
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 |
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
|
tcp_max_mps
|
float | None
|
The fastest the tool frame moves, in meters per second; |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
Attributes:
| Name | Type | Description |
|---|---|---|
max_dps |
list[float] | None
|
The fastest each chain joint's target moves, in degrees per second; |
tcp_max_mps |
float | None
|
The fastest the tool frame moves, meters per second; |
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 |
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
|
max_kd
|
Sequence[float] | None
|
Ceiling on the velocity gain per chain joint, in newton meters per radian per second, as |
None
|
gain_slew_s
|
float | None
|
How long a gain takes to reach what it is asked for, in seconds, zero or more; |
_DEFAULT_GAIN_SLEW_S
|
limits
|
LimitGuards | None
|
Which of the arm's described limits its targets are held to; |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
A ceiling holds something other than numbers. |
InvalidArgumentError
|
|
Attributes:
| Name | Type | Description |
|---|---|---|
max_kp |
list[float] | None
|
Ceiling on the position gain per chain joint, in newton meters per radian, chain order; |
max_kd |
list[float] | None
|
Ceiling on the velocity gain per chain joint, in newton meters per radian per second; |
gain_slew_s |
float | None
|
How long a gain takes to reach what it is asked for, in seconds; |
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
|
gripper
|
GripperTuning | None
|
The gripper motor's tuning, for an arm driven with one; |
None
|
speed
|
SpeedLimits | None
|
How fast the chain's targets move; |
None
|
guards
|
JointGuards | None
|
What the tick clamps the arm to; |
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 |
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; |
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 |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
Attributes:
| Name | Type | Description |
|---|---|---|
tuning |
list[ArmTuning | None]
|
One |
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 |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
Attributes:
| Name | Type | Description |
|---|---|---|
tuning |
list[ArmTuning | None]
|
One |
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 |
None
|
force_feedback
|
ForceFeedback | None
|
How hard each leader is pulled toward its follower; |
None
|
engaged
|
bool | None
|
Whether the followers mirror their leaders; |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
Attributes:
| Name | Type | Description |
|---|---|---|
tuning |
list[ArmTuning | None]
|
One |
force_feedback |
ForceFeedback | None
|
The force feedback, the settings' own; |
engaged |
bool | None
|
Whether the followers mirror their leaders; |
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 |
None
|
ik
|
Sequence[IkParams | None] | None
|
One |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
Attributes:
| Name | Type | Description |
|---|---|---|
tuning |
list[ArmTuning | None]
|
One |
ik |
list[IkParams | None]
|
One |
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 |
gripper_width_m |
Returns the jaws' opening at the actuator positions |
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 |
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 |
required |
Returns:
| Type | Description |
|---|---|
Pose
|
The tool frame's pose. |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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 |
required |
Returns:
| Type | Description |
|---|---|
float | None
|
The opening; |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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; |
keep_out |
str | None
|
The keep-out box it was closing on, by name; |
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
instance-attribute
link: str
The link that was closing, as the composed URDF names it.
on_link
instance-attribute
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; |
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; |
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; |
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
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
A margin is negative or not a number, |
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; |
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; |
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; |
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
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
A margin is negative or not a number, or |
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 |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
Methods:
| Name | Description |
|---|---|
activate |
Warm-starts the controller from measured state; call it before the first |
tick |
Runs one tick on measured |
deactivate |
Stops tracking, before another controller takes over; whatever comes next does its own blending. |
configure |
Retunes the arms; an arm the settings leave |
settings |
Returns what the controller is configured with now, every arm it drives filled in. |
validate_settings |
Checks |
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 |
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
|
last_written
|
RobotCommand | None
|
What was sent the tick before: another controller's output, at a switch; |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
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 |
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 |
None
|
command_t_ns
|
int | None
|
When the command was sent, in nanoseconds, on the clock |
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 |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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 |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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
|
|
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 |
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 |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
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 |
Runs one tick on measured |
deactivate |
Stops tracking, before another controller takes over; whatever comes next does its own blending. |
configure |
Retunes the arms; an arm the settings leave |
settings |
Returns what the controller is configured with now, every arm it drives filled in. |
validate_settings |
Checks |
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 |
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
|
last_written
|
RobotCommand | None
|
What was sent the tick before: another controller's output, at a switch; |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
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 |
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 |
None
|
command_t_ns
|
int | None
|
When the command was sent, in nanoseconds, on the clock |
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 |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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 |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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
|
|
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 |
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 |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
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 |
Runs one tick on measured |
deactivate |
Stops tracking, before another controller takes over; whatever comes next does its own blending. |
configure |
Retunes the arms; an arm the settings leave |
settings |
Returns what the controller is configured with now, every arm it drives filled in. |
validate_settings |
Checks |
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 |
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
|
last_written
|
RobotCommand | None
|
What was sent the tick before: another controller's output, at a switch; |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
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 |
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 |
None
|
command_t_ns
|
int | None
|
When the command was sent, in nanoseconds, on the clock |
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 |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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 |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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
|
|
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
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 |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
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 |
Runs one tick on measured |
deactivate |
Stops tracking, before another controller takes over; whatever comes next does its own blending. |
configure |
Retunes the arms; an arm the settings leave |
settings |
Returns what the controller is configured with now, every arm it drives filled in. |
validate_settings |
Checks |
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 |
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
|
last_written
|
RobotCommand | None
|
What was sent the tick before: another controller's output, at a switch; |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
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 |
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 |
None
|
command_t_ns
|
int | None
|
When the command was sent, in nanoseconds, on the clock |
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 |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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 |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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
|
|
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 |
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 |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
Methods:
| Name | Description |
|---|---|
tick |
Runs one tick on measured |
close |
Frees the part now rather than once it is collected. |
activate |
Warm-starts the controller from measured state; call it before the first |
deactivate |
Stops tracking, before another controller takes over; whatever comes next does its own blending. |
configure |
Retunes the arms; an arm the settings leave |
settings |
Returns what the controller is configured with now, every arm it drives filled in. |
validate_settings |
Checks |
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 |
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
|
command_t_ns
|
int | None
|
Not read. |
None
|
command_seq
|
int | None
|
Not read. |
None
|
Returns:
| Type | Description |
|---|---|
tuple[RobotCommand, LeaderFollowerTrace]
|
The tick's |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
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 |
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
|
last_written
|
RobotCommand | None
|
What was sent the tick before: another controller's output, at a switch; |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
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 |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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
|
|
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 |
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. |
actuator |
int
|
Where the actuator sits in the snapshot's per-actuator lists, as |
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 |
faulted |
bool
|
Whether the drive reports a fault, as |
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 |
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The arm's name, as its |
joints |
tuple[ActuatorSnapshot, ...]
|
Each chain joint's actuator, in the order the arm's description lists them. |
gripper |
GripperSnapshot | None
|
The gripper's 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. |
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 |
configure_json |
Retunes the controller from JSON in the form |
tuned_robot |
Returns |
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Name of the controller, as |
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 |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
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 |
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 |
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 |
HwFaultError
|
The loop has faulted. |
OperationError
|
As for |
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; |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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. |
actuator |
int
|
Where the actuator sits in the snapshot's per-actuator lists, as |
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 |
faulted |
bool
|
Whether the drive reports a fault, as |
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 |
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 |
configure_json |
Retunes the controller from JSON in the form |
tuned_robot |
Returns |
play |
Runs |
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Name of the controller, as |
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 |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
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 |
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 |
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 |
HwFaultError
|
The loop has faulted. |
OperationError
|
As for |
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; |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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 |
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
|
|
HwFaultError
|
The loop faulted before or during the run. |
TimedOutError
|
|
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, |
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 |
required |
controllers
|
Sequence[Controller]
|
One to four controllers, at most one of each class: a |
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
|
stepped
|
bool
|
Over an |
False
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
OperationError
|
|
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 |
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 |
activate |
Activates |
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 |
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; |
fault |
Returns the message of the fault the loop idled on; |
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 |
required |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
OperationError
|
The runtime was not built |
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 |
required |
Returns:
| Type | Description |
|---|---|
ControllerHandle
|
A |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
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 |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
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 |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
A |
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 |
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 |
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 |
list[str]
|
Drive status name per actuator: |
status_faults |
list[bool]
|
Whether each actuator's drive reports a fault: anything but |
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; |
active |
str | None
|
Name of the active controller, such as |
rate_hz |
int
|
Rate the loop is paced to, in hertz, which |
achieved_hz |
float
|
Ticks per second, averaged since |
ticks |
int
|
Ticks completed since |
timing |
LoopTiming
|
Where the last tick's time went. |
slowest |
LoopTiming
|
Timing of the tick with the largest |
most_late |
LoopTiming
|
Timing of the tick with the largest |
guards_fired_since_activation |
int
|
Bitmask of the actuators whose guard clamped on any tick since the last activation, bit |
guards_fired_since_activation_actuators |
list[int]
|
Actuators in |
limit_clamped_since_activation |
int
|
Bitmask of actuators held at a joint limit on any tick since the last activation, bit |
limit_clamped_since_activation_actuators |
list[int]
|
Actuators in |
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.
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
property
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 |
required |
Raises:
| Type | Description |
|---|---|
OperationError
|
As |
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 |
required |
Returns:
| Type | Description |
|---|---|
list[ControlArm]
|
One |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|