anvil.apps
anvil.apps
The apps: what turns an operator's intent, or a recorded episode, into commands for a controller.
QuestTeleop folds a Meta Quest headset's controller frames into end-effector targets, and QuestDevice provisions
the headset over USB and watches it. KeyboardTeleop does the same from the keys held and the spots clicked on a page.
EpisodeReplay turns a recorded episode into the commands that play it on the robot again. GloveTeleop turns UDCap
gloves into the six angles of the RealHand L6 on each side, which the caller sends with anvil.hw.RealHand.
None of them runs a loop or touches hardware: the caller polls the app at its own pace, sends what it hands back to the
active controller with anvil.control.Runtime.send_command, and polls again. Each app is released when its object is
collected.
Classes:
| Name | Description |
|---|---|
QuestHand |
One of the two physical Quest controllers, as it drives an arm or plays a cue; both arms may name the same. |
QuestCue |
Something the Quest controllers are made to feel, named as its |
QuestMotionFrame |
The frame the Quest controllers' motion is read in. |
QuestArmState |
What one arm is doing under the Quest app. |
QuestAction |
What a Quest button does when it fires, as |
QuestBoundAction |
One of the six Quest actions that carry an argument, built with it, as |
QuestHaptic |
What one Quest controller feels for a cue that happens once: a burst of |
QuestHeldHaptic |
What one Quest controller feels for a cue that lasts: a burst when it starts, repeated while it lasts. |
QuestHands |
One value for each Quest controller. |
QuestContactRange |
How far from its path an arm must stand for |
QuestHaptics |
What the Quest controllers are made to feel, each |
QuestVelocity |
A Quest controller's velocity in the headset's stage frame, as the headset estimates it from its own sensors. |
QuestConfig |
Everything the Quest app is told, which |
QuestFired |
One action a Quest button asked for since the last poll, as |
QuestCheckOutcome |
How one headset check came out. |
QuestCheckStatus |
What one headset check last reported, as |
QuestBatteries |
Each Quest battery's charge as last read, from |
QuestAbout |
What the headset says it is, from |
QuestDeviceConfig |
How |
QuestAppOutput |
What one |
KeyboardAction |
What a key does under |
KeyboardConfig |
Everything the keyboard app is told, which |
KeyboardAppOutput |
What one |
QuestTeleop |
The Quest teleop app: a Meta Quest headset's controller frames turned into end-effector targets for |
QuestDevice |
The Quest headset's lifecycle over USB, watched on a thread of its own. |
KeyboardTeleop |
The keyboard app: keys held and spots clicked, driving a robot's arms one at a time through |
ReplayMode |
What of an episode |
ReplayOverrides |
What a |
ReplayPhase |
Where along its timeline an |
ReplayPoll |
What one |
EpisodeReplay |
A recorded episode played on the robot again, polled for the controller to have active and the command due. |
GlovePose |
One pose the wearer holds while a glove is calibrated. |
GloveProcedure |
How the gloves are paired and calibrated, as the SDK runs them. Get one from |
GloveCalibration |
One glove's calibration: each of its twelve bend sensors' raw reading with the hand flat, and bent. |
GloveSlot |
One slot of the glove receiver, and the glove paired into it. Get them from |
GloveHandPoll |
One hand's part of a |
GloveTeleop |
UDCap gloves, read over their USB receiver, to the six angles of the RealHand L6 on each side. |
Functions:
| Name | Description |
|---|---|
quest_streamer |
Returns the headset streamer app this package ships, as |
glove_procedure |
Returns how the gloves are paired and calibrated: the poses, how long each is held, and the pairing wait. |
Attributes:
| Name | Type | Description |
|---|---|---|
QUEST_BUTTONS |
The seven Quest buttons a binding may name, by the names |
|
KEYBOARD_KEYS |
tuple[str, ...]
|
Every key a keyboard binding may name, by the names |
QUEST_BUTTONS
module-attribute
QUEST_BUTTONS = (
"left_x",
"left_y",
"left_menu",
"left_thumbstick",
"right_a",
"right_b",
"right_thumbstick",
)
The seven Quest buttons a binding may name, by the names QuestConfig.bind takes.
They are left_x, left_y, left_menu, left_thumbstick, right_a, right_b and right_thumbstick. The headset's
system menu takes left_menu on some headset builds; bind what you need to another.
KEYBOARD_KEYS
module-attribute
KEYBOARD_KEYS: tuple[str, ...] = (
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"up",
"down",
"left",
"right",
"shift",
"space",
"tab",
"escape",
"enter",
"backspace",
"[",
"]",
";",
"'",
",",
".",
"/",
"-",
"=",
"`",
"\\",
)
Every key a keyboard binding may name, by the names KeyboardConfig.bind and KeyboardTeleop.hold take.
They are a to z, 0 to 9, up, down, left, right, shift, space, tab, escape, enter,
backspace, and the punctuation keys by their character.
QuestHand
Bases: StrEnum
One of the two physical Quest controllers, as it drives an arm or plays a cue; both arms may name the same.
Attributes:
| Name | Type | Description |
|---|---|---|
LEFT |
The left controller. |
|
RIGHT |
The right controller. |
LEFT
class-attribute
instance-attribute
LEFT
The left controller.
RIGHT
class-attribute
instance-attribute
RIGHT
The right controller.
QuestCue
Bases: StrEnum
Something the Quest controllers are made to feel, named as its QuestHaptics field is.
TRACKING_LOST, LIMIT and CONTACT last, and the app plays them itself as they happen. The caller plays the
other four with QuestTeleop.play when it sees them happen.
Attributes:
| Name | Type | Description |
|---|---|---|
TRACKING_LOST |
The headset lost sight of the controller; lasts while it is lost. |
|
LIMIT |
An arm the controller drives stopped short of the hand, at a joint's limit or the edge of its reach. |
|
CONTACT |
Something stopped an arm the controller drives; lasts while it does. |
|
RECORDING_START |
An episode started recording. |
|
RECORDING_STOP |
An episode stopped recording. |
|
COLLISION_WARNING |
The arms came within a collision rule's warning margin. |
|
COLLISION_STOP |
The collision stop held the arms. |
TRACKING_LOST
class-attribute
instance-attribute
TRACKING_LOST
The headset lost sight of the controller; lasts while it is lost.
LIMIT
class-attribute
instance-attribute
LIMIT
An arm the controller drives stopped short of the hand, at a joint's limit or the edge of its reach.
CONTACT
class-attribute
instance-attribute
CONTACT
Something stopped an arm the controller drives; lasts while it does.
RECORDING_START
class-attribute
instance-attribute
RECORDING_START
An episode started recording.
RECORDING_STOP
class-attribute
instance-attribute
RECORDING_STOP
An episode stopped recording.
COLLISION_WARNING
class-attribute
instance-attribute
COLLISION_WARNING
The arms came within a collision rule's warning margin.
COLLISION_STOP
class-attribute
instance-attribute
COLLISION_STOP
The collision stop held the arms.
QuestMotionFrame
Bases: StrEnum
The frame the Quest controllers' motion is read in.
Attributes:
| Name | Type | Description |
|---|---|---|
GRIPPER |
A motion along the controller's own axes moves the gripper along its own axes. |
|
WORLD |
Stage up, forward and right move the robot along its world axes, whichever way the gripper faces. |
GRIPPER
class-attribute
instance-attribute
GRIPPER
A motion along the controller's own axes moves the gripper along its own axes.
Once the two have turned apart across a declutch, "up" lands wherever the gripper's matching axis points.
WORLD
class-attribute
instance-attribute
WORLD
Stage up, forward and right move the robot along its world axes, whichever way the gripper faces.
Which way is forward is set by QuestTeleop.recentre.
QuestArmState
Bases: StrEnum
What one arm is doing under the Quest app.
Attributes:
| Name | Type | Description |
|---|---|---|
IDLE |
The clutch is open, and the arm holds its last target. |
|
ANCHORING |
The poll the clutch closed on: the chain re-anchored to the arm's measured pose. |
|
DRIVING |
Following the controller. |
|
RECLUTCH |
Latched after a tracking loss or |
IDLE
class-attribute
instance-attribute
IDLE
The clutch is open, and the arm holds its last target.
ANCHORING
class-attribute
instance-attribute
ANCHORING
The poll the clutch closed on: the chain re-anchored to the arm's measured pose.
DRIVING
class-attribute
instance-attribute
DRIVING
Following the controller.
RECLUTCH
class-attribute
instance-attribute
RECLUTCH
Latched after a tracking loss or QuestTeleop.reset, refusing to move.
Nothing moves until the clutch is released and the controller has been tracked without a break for
QuestConfig.tracking_settle_s.
QuestAction
Bases: StrEnum
What a Quest button does when it fires, as QuestConfig.bind takes it.
The app does recentre, toggle_motion_frame, scale, set_scale, toggle_velocity, toggle_rigid and
toggle_axis_lock itself. rehome, record_start, record_stop, mark_success, mark_failure, annotate and
switch_profile it only reports, in QuestAppOutput.fired, for the caller to act on: no app re-homes a robot,
records, labels or annotates an episode or retunes a controller.
The six that carry an argument are built by the static methods of the same name. The other eight are the members themselves, and each has a static method too, so a binding table reads the same either way.
Example
config = QuestConfig()
config.bind(button="left_x", action=QuestAction.recentre(hand="left"))
config.bind(button="left_y", action=QuestAction.rehome(), held=True)
Methods:
| Name | Description |
|---|---|
recentre |
Builds the action that takes |
toggle_motion_frame |
Returns |
scale |
Builds the action that multiplies the reach, |
set_scale |
Builds the action that sets the reach, |
toggle_velocity |
Returns |
toggle_rigid |
Returns |
toggle_axis_lock |
Builds the action that puts |
rehome |
Returns |
record_start |
Returns |
record_stop |
Returns |
mark_success |
Returns |
mark_failure |
Returns |
annotate |
Builds the action reported for the caller to pin the annotation it numbers |
switch_profile |
Builds the action reported for the caller to switch to the tuning profile it numbers |
Attributes:
| Name | Type | Description |
|---|---|---|
RECENTRE |
A controller's heading taken as the robot's forward; built with the controller by |
|
TOGGLE_MOTION_FRAME |
A switch between the two frames the controllers' motion is read in. |
|
SCALE |
The reach multiplied by a factor; built with the factor by |
|
SET_SCALE |
The reach set to a preset; built with the preset by |
|
TOGGLE_VELOCITY |
|
|
TOGGLE_RIGID |
|
|
TOGGLE_AXIS_LOCK |
An axis lock put on arms or taken off them; built with the arms and the lock by |
|
REHOME |
Reported for the caller to act on: bring the arms home. |
|
RECORD_START |
Reported for the caller to act on: start recording. |
|
RECORD_STOP |
Reported for the caller to act on: stop recording. |
|
MARK_SUCCESS |
Reported for the caller to act on: say the episode went as its task asks. |
|
MARK_FAILURE |
Reported for the caller to act on: say the episode did not go as its task asks. |
|
ANNOTATE |
Reported for the caller to act on: pin an annotation to the episode being recorded; built with its number by |
|
SWITCH_PROFILE |
Reported for the caller to act on: switch to a tuning profile; built with its number by |
|
name |
str
|
The action's name as a binding table and a log spell it, such as |
RECENTRE
class-attribute
instance-attribute
RECENTRE
A controller's heading taken as the robot's forward; built with the controller by recentre.
TOGGLE_MOTION_FRAME
class-attribute
instance-attribute
TOGGLE_MOTION_FRAME
A switch between the two frames the controllers' motion is read in.
SCALE
class-attribute
instance-attribute
SCALE
The reach multiplied by a factor; built with the factor by scale.
SET_SCALE
class-attribute
instance-attribute
SET_SCALE
The reach set to a preset; built with the preset by set_scale.
TOGGLE_VELOCITY
class-attribute
instance-attribute
TOGGLE_VELOCITY
QuestConfig.velocity_ff switched on or off.
TOGGLE_RIGID
class-attribute
instance-attribute
TOGGLE_RIGID
QuestConfig.rigid switched on or off.
TOGGLE_AXIS_LOCK
class-attribute
instance-attribute
TOGGLE_AXIS_LOCK
An axis lock put on arms or taken off them; built with the arms and the lock by toggle_axis_lock.
REHOME
class-attribute
instance-attribute
REHOME
Reported for the caller to act on: bring the arms home.
RECORD_START
class-attribute
instance-attribute
RECORD_START
Reported for the caller to act on: start recording.
RECORD_STOP
class-attribute
instance-attribute
RECORD_STOP
Reported for the caller to act on: stop recording.
MARK_SUCCESS
class-attribute
instance-attribute
MARK_SUCCESS
Reported for the caller to act on: say the episode went as its task asks.
MARK_FAILURE
class-attribute
instance-attribute
MARK_FAILURE
Reported for the caller to act on: say the episode did not go as its task asks.
ANNOTATE
class-attribute
instance-attribute
ANNOTATE
Reported for the caller to act on: pin an annotation to the episode being recorded; built with its number by
annotate.
SWITCH_PROFILE
class-attribute
instance-attribute
SWITCH_PROFILE
Reported for the caller to act on: switch to a tuning profile; built with its number by switch_profile.
name
property
name: str
The action's name as a binding table and a log spell it, such as recentre or toggle_motion_frame.
This is the member's value, not its Python name: QuestAction.REHOME.name is rehome.
recentre
staticmethod
recentre(hand: QuestHand | str) -> QuestBoundAction
Builds the action that takes hand's controller heading as the robot's forward.
The heading is the way that controller points, flattened to the floor; it need not be the controller whose
button fired. Only QuestMotionFrame.WORLD reads it. QuestTeleop.recentre does the same on a call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hand
|
QuestHand | str
|
The controller whose heading is taken. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
toggle_motion_frame
staticmethod
toggle_motion_frame() -> QuestAction
Returns TOGGLE_MOTION_FRAME, which switches between the frames the controllers' motion is read in.
scale
staticmethod
scale(multiply_by: float) -> QuestBoundAction
Builds the action that multiplies the reach, QuestConfig.position_scale, by multiply_by.
The product is held inside the range position_scale allows, 0.1 to 5. 1.25 and 0.8 are one step either way
and undo each other exactly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
multiply_by
|
float
|
The factor. One that is not positive and finite is refused when the configuration is
validated or handed to a |
required |
set_scale
staticmethod
set_scale(to: float) -> QuestBoundAction
Builds the action that sets the reach, QuestConfig.position_scale, to to.
A preset one press returns to, wherever scale steps have taken the reach: bind 1, 2 and 3 to three buttons to
switch between them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
to
|
float
|
The reach. One outside the range |
required |
toggle_velocity
staticmethod
toggle_velocity() -> QuestAction
Returns TOGGLE_VELOCITY, which switches QuestConfig.velocity_ff on or off.
toggle_rigid
staticmethod
toggle_rigid() -> QuestAction
Returns TOGGLE_RIGID, which locks the two arms together at the transform between them, or lets them go.
toggle_axis_lock
staticmethod
toggle_axis_lock(arms: str, lock: str) -> QuestBoundAction
Builds the action that puts lock on arms, or takes it off them when every one already holds it.
Neither is checked here: an arm or a lock nothing is named is refused when the configuration is validated or
handed to a QuestTeleop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arms
|
str
|
|
required |
lock
|
str
|
The axes the arms may move along, spelled as |
required |
rehome
staticmethod
rehome() -> QuestAction
Returns REHOME, reported for the caller to bring the arms home.
record_start
staticmethod
record_start() -> QuestAction
Returns RECORD_START, reported for the caller to start recording.
record_stop
staticmethod
record_stop() -> QuestAction
Returns RECORD_STOP, reported for the caller to stop recording.
mark_success
staticmethod
mark_success() -> QuestAction
Returns MARK_SUCCESS, reported for the caller to say an episode succeeded.
mark_failure
staticmethod
mark_failure() -> QuestAction
Returns MARK_FAILURE, reported for the caller to say an episode failed.
annotate
staticmethod
annotate(annotation: int) -> QuestBoundAction
Builds the action reported for the caller to pin the annotation it numbers annotation to now.
The caller decides what each number says, a key and a value, and pins it with
anvil.recorder.Recorder.annotate; the app only reports it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
annotation
|
int
|
The annotation's number. A negative one is refused when the configuration is validated or
handed to a |
required |
switch_profile
staticmethod
switch_profile(profile: int) -> QuestBoundAction
Builds the action reported for the caller to switch to the tuning profile it numbers profile.
The caller decides what each number means; the app only reports it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
profile
|
int
|
The profile's number. A negative one is refused when the configuration is validated or handed to
a |
required |
QuestBoundAction
dataclass
QuestBoundAction(
action: QuestAction,
argument: QuestHand | float | int | str,
)
One of the six Quest actions that carry an argument, built with it, as QuestConfig.bind takes it.
QuestAction.recentre, QuestAction.scale, QuestAction.set_scale, QuestAction.toggle_axis_lock,
QuestAction.annotate and QuestAction.switch_profile return one, and QuestConfig.bindings, QuestConfig.holds
and QuestFired.action hold one where such an action is bound. Its name and str are the action's own, the way
the eight that carry nothing read, and the argument goes out beside it in the binding table: recentre:left,
scale:1.25, set_scale:2.0, annotate:2, switch_profile:3, toggle_axis_lock:left:gripper:z.
Attributes:
| Name | Type | Description |
|---|---|---|
action |
QuestAction
|
The action, one of |
argument |
QuestHand | float | int | str
|
|
name |
str
|
The action's name as a binding table and a log spell it, such as |
action
instance-attribute
action: QuestAction
The action, one of RECENTRE, SCALE, SET_SCALE, TOGGLE_AXIS_LOCK, ANNOTATE and SWITCH_PROFILE.
argument
instance-attribute
argument: QuestHand | float | int | str
recentre's hand, scale's factor, set_scale's reach, annotate's or switch_profile's number, or
toggle_axis_lock's arms and lock.
name
property
name: str
The action's name as a binding table and a log spell it, such as recentre or scale.
QuestHaptic
dataclass
QuestHaptic(
enabled: bool,
amplitude: float,
duration_s: float,
count: int,
gap_s: float,
)
What one Quest controller feels for a cue that happens once: a burst of count pulses.
Its numbers are checked when the configuration is validated or handed to a QuestTeleop, enabled or not.
Attributes:
| Name | Type | Description |
|---|---|---|
enabled |
bool
|
Whether it is felt; off keeps the rest as set, for switching back on. |
amplitude |
float
|
How hard each pulse is, 0 to 1. |
duration_s |
float
|
How long each pulse lasts, in seconds; more than zero. |
count |
int
|
How many pulses in the burst, at least one. |
gap_s |
float
|
From one pulse ending to the next starting, in seconds. |
enabled
instance-attribute
enabled: bool
Whether it is felt; off keeps the rest as set, for switching back on.
amplitude
instance-attribute
amplitude: float
How hard each pulse is, 0 to 1.
duration_s
instance-attribute
duration_s: float
How long each pulse lasts, in seconds; more than zero.
count
instance-attribute
count: int
How many pulses in the burst, at least one.
gap_s
instance-attribute
gap_s: float
From one pulse ending to the next starting, in seconds.
QuestHeldHaptic
dataclass
QuestHeldHaptic(
enabled: bool,
amplitude: float,
duration_s: float,
count: int,
gap_s: float,
repeat_s: float | None,
)
What one Quest controller feels for a cue that lasts: a burst when it starts, repeated while it lasts.
A single pulse longer than its repeat is one unbroken rumble. Its numbers are checked when the configuration is
validated or handed to a QuestTeleop, enabled or not.
Attributes:
| Name | Type | Description |
|---|---|---|
enabled |
bool
|
Whether it is felt; off keeps the rest as set, for switching back on. |
amplitude |
float
|
How hard each pulse is, 0 to 1. |
duration_s |
float
|
How long each pulse lasts, in seconds; more than zero. |
count |
int
|
How many pulses in a burst, at least one. |
gap_s |
float
|
From one pulse ending to the next starting, in seconds. |
repeat_s |
float | None
|
From one burst's start to the next while the cue lasts, in seconds and more than zero; |
enabled
instance-attribute
enabled: bool
Whether it is felt; off keeps the rest as set, for switching back on.
amplitude
instance-attribute
amplitude: float
How hard each pulse is, 0 to 1.
duration_s
instance-attribute
duration_s: float
How long each pulse lasts, in seconds; more than zero.
count
instance-attribute
count: int
How many pulses in a burst, at least one.
gap_s
instance-attribute
gap_s: float
From one pulse ending to the next starting, in seconds.
repeat_s
instance-attribute
repeat_s: float | None
From one burst's start to the next while the cue lasts, in seconds and more than zero; None plays it once.
QuestHands
dataclass
QuestHands(left: T, right: T)
QuestContactRange
dataclass
QuestContactRange(deadband_m: float, full_m: float)
How far from its path an arm must stand for QuestCue.CONTACT to be felt, and to be felt in full.
The distance is between a driven arm that something stops and the path it was told along over the last moments. An arm anywhere on that path is trailing its target, which is lag, not contact.
Attributes:
| Name | Type | Description |
|---|---|---|
deadband_m |
float
|
Nothing is felt this close, in meters; zero or more. |
full_m |
float
|
Full strength from this far and beyond, in meters; more than |
deadband_m
instance-attribute
deadband_m: float
Nothing is felt this close, in meters; zero or more.
full_m
instance-attribute
full_m: float
Full strength from this far and beyond, in meters; more than deadband_m.
QuestHaptics
dataclass
QuestHaptics(
tracking_lost: QuestHands[QuestHeldHaptic],
limit: QuestHands[QuestHeldHaptic],
contact: QuestHands[QuestHeldHaptic],
contact_range: QuestContactRange,
recording_start: QuestHands[QuestHaptic],
recording_stop: QuestHands[QuestHaptic],
collision_warning: QuestHands[QuestHaptic],
collision_stop: QuestHands[QuestHaptic],
)
What the Quest controllers are made to feel, each QuestCue set apart for each controller.
Felt only through a headset streamer of 1.3.0 or newer.
Attributes:
| Name | Type | Description |
|---|---|---|
tracking_lost |
QuestHands[QuestHeldHaptic]
|
While the headset has lost sight of the controller. |
limit |
QuestHands[QuestHeldHaptic]
|
While an arm the controller drives stops short of the hand, at a joint's limit or the edge of its reach. |
contact |
QuestHands[QuestHeldHaptic]
|
While something stops an arm the controller drives; the amplitude is the strength at full contact. |
contact_range |
QuestContactRange
|
How far from its path a stopped arm must stand for |
recording_start |
QuestHands[QuestHaptic]
|
When an episode starts recording; the caller plays it with |
recording_stop |
QuestHands[QuestHaptic]
|
When an episode stops recording; the caller plays it with |
collision_warning |
QuestHands[QuestHaptic]
|
When the arms come within a collision rule's warning margin; the caller plays it with |
collision_stop |
QuestHands[QuestHaptic]
|
When the collision stop holds the arms; the caller plays it with |
tracking_lost
instance-attribute
tracking_lost: QuestHands[QuestHeldHaptic]
While the headset has lost sight of the controller.
limit
instance-attribute
limit: QuestHands[QuestHeldHaptic]
While an arm the controller drives stops short of the hand, at a joint's limit or the edge of its reach.
contact
instance-attribute
contact: QuestHands[QuestHeldHaptic]
While something stops an arm the controller drives; the amplitude is the strength at full contact.
contact_range
instance-attribute
contact_range: QuestContactRange
How far from its path a stopped arm must stand for contact to be felt, and to be felt in full.
recording_start
instance-attribute
recording_start: QuestHands[QuestHaptic]
When an episode starts recording; the caller plays it with QuestTeleop.play.
recording_stop
instance-attribute
recording_stop: QuestHands[QuestHaptic]
When an episode stops recording; the caller plays it with QuestTeleop.play.
collision_warning
instance-attribute
collision_warning: QuestHands[QuestHaptic]
When the arms come within a collision rule's warning margin; the caller plays it with QuestTeleop.play.
collision_stop
instance-attribute
collision_stop: QuestHands[QuestHaptic]
When the collision stop holds the arms; the caller plays it with QuestTeleop.play.
QuestVelocity
dataclass
QuestVelocity(
linear_mps: list[float], angular_radps: list[float]
)
A Quest controller's velocity in the headset's stage frame, as the headset estimates it from its own sensors.
Attributes:
| Name | Type | Description |
|---|---|---|
linear_mps |
list[float]
|
Linear velocity along the stage's x, y and z axes, in meters per second. |
angular_radps |
list[float]
|
Angular velocity about the stage's x, y and z axes, in radians per second. |
linear_mps
instance-attribute
linear_mps: list[float]
Linear velocity along the stage's x, y and z axes, in meters per second.
angular_radps
instance-attribute
angular_radps: list[float]
Angular velocity about the stage's x, y and z axes, in radians per second.
QuestConfig
dataclass
QuestConfig(address: str = '127.0.0.1:8081', left_arm: QuestHand = QuestHand.LEFT, right_arm: QuestHand = QuestHand.RIGHT, engage_above: float = 0.75, disengage_below: float = 0.25, tracking_settle_s: float = 0.5, link_timeout_s: float = 0.5, pivot_m: float = -0.05, gripper_from_controller: list[list[float]] = [[0.0, -1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, -1.0]], world_from_stage: list[list[float]] = [[0.0, 0.0, -1.0], [-1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], grip_open_m: float = 0.1, grip_closed_m: float = 0.0, grip_trigger_exponent: float = 0.75, motion_frame: QuestMotionFrame = QuestMotionFrame.GRIPPER, position_scale: float = 1.0, velocity_ff: bool = False, rigid: bool = False, axis_locks: list[str | None] = [None, None], bindings: dict[str, _Bound] = {'left_menu': <QuestAction.TOGGLE_MOTION_FRAME: 'toggle_motion_frame'>, 'left_thumbstick': QuestBoundAction(action=<QuestAction.SCALE: 'scale'>, argument=0.8), 'left_x': QuestBoundAction(action=<QuestAction.RECENTRE: 'recentre'>, argument=<QuestHand.LEFT: 'left'>), 'left_y': <QuestAction.REHOME: 'rehome'>, 'right_a': <QuestAction.RECORD_START: 'record_start'>, 'right_b': <QuestAction.RECORD_STOP: 'record_stop'>, 'right_thumbstick': QuestBoundAction(action=<QuestAction.SCALE: 'scale'>, argument=1.25)}, holds: dict[str, _Bound] = {}, hold_s: float = 0.5, haptics: QuestHaptics = QuestHaptics(tracking_lost=QuestHands(left=QuestHeldHaptic(enabled=True, amplitude=0.5, duration_s=0.15, count=1, gap_s=0.0, repeat_s=None), right=QuestHeldHaptic(enabled=True, amplitude=0.5, duration_s=0.15, count=1, gap_s=0.0, repeat_s=None)), limit=QuestHands(left=QuestHeldHaptic(enabled=False, amplitude=1.0, duration_s=0.04, count=1, gap_s=0.0, repeat_s=None), right=QuestHeldHaptic(enabled=False, amplitude=1.0, duration_s=0.04, count=1, gap_s=0.0, repeat_s=None)), contact=QuestHands(left=QuestHeldHaptic(enabled=False, amplitude=1.0, duration_s=0.1, count=1, gap_s=0.0, repeat_s=0.05), right=QuestHeldHaptic(enabled=False, amplitude=1.0, duration_s=0.1, count=1, gap_s=0.0, repeat_s=0.05)), contact_range=QuestContactRange(deadband_m=0.015, full_m=0.06), recording_start=QuestHands(left=QuestHaptic(enabled=False, amplitude=0.8, duration_s=0.06, count=2, gap_s=0.09), right=QuestHaptic(enabled=False, amplitude=0.8, duration_s=0.06, count=2, gap_s=0.09)), recording_stop=QuestHands(left=QuestHaptic(enabled=False, amplitude=0.8, duration_s=0.06, count=2, gap_s=0.09), right=QuestHaptic(enabled=False, amplitude=0.8, duration_s=0.06, count=2, gap_s=0.09)), collision_warning=QuestHands(left=QuestHaptic(enabled=False, amplitude=1.0, duration_s=0.3, count=1, gap_s=0.0), right=QuestHaptic(enabled=False, amplitude=1.0, duration_s=0.3, count=1, gap_s=0.0)), collision_stop=QuestHands(left=QuestHaptic(enabled=False, amplitude=1.0, duration_s=0.3, count=1, gap_s=0.0), right=QuestHaptic(enabled=False, amplitude=1.0, duration_s=0.3, count=1, gap_s=0.0))))
Everything the Quest app is told, which QuestTeleop takes on construction and through QuestTeleop.config.
It says where the headset connects, how the clutch and the gripper read, the frame motion is read in, how far a
motion carries, what every button does, and what the controllers feel. QuestConfig() is the OpenArm setup and
shipped picks one by arm family; assign the fields that differ.
validate checks a configuration alone. Constructing a QuestTeleop or assigning its config checks it again,
with what only an app can judge, and the buttons write the same fields while the app runs.
Methods:
| Name | Description |
|---|---|
shipped |
Returns the Quest setup shipped for an arm family. |
validate |
Checks this configuration on its own, raising on what |
bind |
Binds |
to_json |
Returns this configuration as JSON, the form |
from_json |
Builds a configuration from the JSON |
Attributes:
| Name | Type | Description |
|---|---|---|
address |
str
|
Where the headset connects, |
left_arm |
QuestHand
|
The controller that drives the left arm of a two-arm cell; a one-arm cell is driven by whichever clutches in. |
right_arm |
QuestHand
|
The controller that drives the right arm of a two-arm cell. |
engage_above |
float
|
The side trigger's reading, 0 to 1, above which the clutch closes. |
disengage_below |
float
|
The side trigger's reading, 0 to 1, below which the clutch opens; between the two it keeps its state. |
tracking_settle_s |
float
|
How long a controller must be tracked without a break before a latched arm may clutch in again, in seconds. |
link_timeout_s |
float
|
How long without a frame from the headset before every arm latches, in seconds. |
pivot_m |
float
|
Distance along the controller's z axis to the point its motion pivots about, in meters. |
gripper_from_controller |
list[list[float]]
|
Controller axes to gripper axes, a 3 by 3 rotation as a list of its rows. |
world_from_stage |
list[list[float]]
|
Headset stage axes to the robot's world axes, a 3 by 3 rotation as a list of its rows. |
grip_open_m |
float
|
Gripper opening commanded with the index trigger released, in meters. |
grip_closed_m |
float
|
Gripper opening commanded with the index trigger fully pulled, in meters. |
grip_trigger_exponent |
float
|
The power the index trigger's reading, 0 to 1, is raised to before it maps to an opening. |
motion_frame |
QuestMotionFrame
|
The frame the controllers' motion is read in. |
position_scale |
float
|
Multiplier on the controller's translation, 0.1 to 5; rotation is never scaled. |
velocity_ff |
bool
|
Whether each target carries the rate it is moving at as a velocity, so damping does not brake the move. |
rigid |
bool
|
Whether the two arms move as one body; refused on a one-arm cell. |
axis_locks |
list[str | None]
|
The axes each arm may move along, the left arm's first, |
bindings |
dict[str, _Bound]
|
What each button does when pressed, keyed by its name in |
holds |
dict[str, _Bound]
|
What each button does once held for |
hold_s |
float
|
How long a button is held before its hold fires, in seconds; more than zero. |
haptics |
QuestHaptics
|
What the controllers are made to feel. |
address
class-attribute
instance-attribute
address: str = '127.0.0.1:8081'
Where the headset connects, host:port.
The headset's own port is tunneled to this machine's loopback over USB, so the host is loopback and the port is
QuestDeviceConfig.port. Port 0 binds one the machine picks, which QuestTeleop.config reads back.
left_arm
class-attribute
instance-attribute
left_arm: QuestHand = QuestHand.LEFT
The controller that drives the left arm of a two-arm cell; a one-arm cell is driven by whichever clutches in.
right_arm
class-attribute
instance-attribute
right_arm: QuestHand = QuestHand.RIGHT
The controller that drives the right arm of a two-arm cell.
engage_above
class-attribute
instance-attribute
engage_above: float = 0.75
The side trigger's reading, 0 to 1, above which the clutch closes.
disengage_below
class-attribute
instance-attribute
disengage_below: float = 0.25
The side trigger's reading, 0 to 1, below which the clutch opens; between the two it keeps its state.
tracking_settle_s
class-attribute
instance-attribute
tracking_settle_s: float = 0.5
How long a controller must be tracked without a break before a latched arm may clutch in again, in seconds.
link_timeout_s
class-attribute
instance-attribute
link_timeout_s: float = 0.5
How long without a frame from the headset before every arm latches, in seconds.
pivot_m
class-attribute
instance-attribute
pivot_m: float = -0.05
Distance along the controller's z axis to the point its motion pivots about, in meters.
gripper_from_controller
class-attribute
instance-attribute
gripper_from_controller: list[list[float]] = [
[0.0, -1.0, 0.0],
[-1.0, 0.0, 0.0],
[0.0, 0.0, -1.0],
]
Controller axes to gripper axes, a 3 by 3 rotation as a list of its rows.
world_from_stage
class-attribute
instance-attribute
world_from_stage: list[list[float]] = [
[0.0, 0.0, -1.0],
[-1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
]
Headset stage axes to the robot's world axes, a 3 by 3 rotation as a list of its rows.
grip_open_m
class-attribute
instance-attribute
grip_open_m: float = 0.1
Gripper opening commanded with the index trigger released, in meters.
grip_closed_m
class-attribute
instance-attribute
grip_closed_m: float = 0.0
Gripper opening commanded with the index trigger fully pulled, in meters.
grip_trigger_exponent
class-attribute
instance-attribute
grip_trigger_exponent: float = 0.75
The power the index trigger's reading, 0 to 1, is raised to before it maps to an opening.
Below 1, the first part of the pull closes the gripper faster than the last.
motion_frame
class-attribute
instance-attribute
motion_frame: QuestMotionFrame = QuestMotionFrame.GRIPPER
The frame the controllers' motion is read in.
position_scale
class-attribute
instance-attribute
position_scale: float = 1.0
Multiplier on the controller's translation, 0.1 to 5; rotation is never scaled.
velocity_ff
class-attribute
instance-attribute
velocity_ff: bool = False
Whether each target carries the rate it is moving at as a velocity, so damping does not brake the move.
rigid
class-attribute
instance-attribute
rigid: bool = False
Whether the two arms move as one body; refused on a one-arm cell.
The hand on the clutch drives the arm it always drives, and the other follows at the transform the two had when the lock was taken.
axis_locks
class-attribute
instance-attribute
axis_locks: list[str | None] = [None, None]
The axes each arm may move along, the left arm's first, None for every axis; a one-arm cell's is the first.
A lock is its frame and the axes it lets through, gripper:z or world:x,y,rz: x, y and z translate along
the frame's axes, rx, ry and rz turn about them, and the gripper's approach is its z. A lock that lets no
axis through is refused.
bindings
class-attribute
instance-attribute
bindings: dict[str, _Bound] = {'left_menu': <QuestAction.TOGGLE_MOTION_FRAME: 'toggle_motion_frame'>, 'left_thumbstick': QuestBoundAction(action=<QuestAction.SCALE: 'scale'>, argument=0.8), 'left_x': QuestBoundAction(action=<QuestAction.RECENTRE: 'recentre'>, argument=<QuestHand.LEFT: 'left'>), 'left_y': <QuestAction.REHOME: 'rehome'>, 'right_a': <QuestAction.RECORD_START: 'record_start'>, 'right_b': <QuestAction.RECORD_STOP: 'record_stop'>, 'right_thumbstick': QuestBoundAction(action=<QuestAction.SCALE: 'scale'>, argument=1.25)}
What each button does when pressed, keyed by its name in QUEST_BUTTONS; a button left out does nothing.
holds
class-attribute
instance-attribute
holds: dict[str, _Bound] = {}
What each button does once held for hold_s, on the same terms as bindings.
A button with a hold fires its press on release rather than as it goes down.
hold_s
class-attribute
instance-attribute
hold_s: float = 0.5
How long a button is held before its hold fires, in seconds; more than zero.
haptics
class-attribute
instance-attribute
haptics: QuestHaptics = QuestHaptics(
tracking_lost=QuestHands(
left=QuestHeldHaptic(
enabled=True,
amplitude=0.5,
duration_s=0.15,
count=1,
gap_s=0.0,
repeat_s=None,
),
right=QuestHeldHaptic(
enabled=True,
amplitude=0.5,
duration_s=0.15,
count=1,
gap_s=0.0,
repeat_s=None,
),
),
limit=QuestHands(
left=QuestHeldHaptic(
enabled=False,
amplitude=1.0,
duration_s=0.04,
count=1,
gap_s=0.0,
repeat_s=None,
),
right=QuestHeldHaptic(
enabled=False,
amplitude=1.0,
duration_s=0.04,
count=1,
gap_s=0.0,
repeat_s=None,
),
),
contact=QuestHands(
left=QuestHeldHaptic(
enabled=False,
amplitude=1.0,
duration_s=0.1,
count=1,
gap_s=0.0,
repeat_s=0.05,
),
right=QuestHeldHaptic(
enabled=False,
amplitude=1.0,
duration_s=0.1,
count=1,
gap_s=0.0,
repeat_s=0.05,
),
),
contact_range=QuestContactRange(
deadband_m=0.015, full_m=0.06
),
recording_start=QuestHands(
left=QuestHaptic(
enabled=False,
amplitude=0.8,
duration_s=0.06,
count=2,
gap_s=0.09,
),
right=QuestHaptic(
enabled=False,
amplitude=0.8,
duration_s=0.06,
count=2,
gap_s=0.09,
),
),
recording_stop=QuestHands(
left=QuestHaptic(
enabled=False,
amplitude=0.8,
duration_s=0.06,
count=2,
gap_s=0.09,
),
right=QuestHaptic(
enabled=False,
amplitude=0.8,
duration_s=0.06,
count=2,
gap_s=0.09,
),
),
collision_warning=QuestHands(
left=QuestHaptic(
enabled=False,
amplitude=1.0,
duration_s=0.3,
count=1,
gap_s=0.0,
),
right=QuestHaptic(
enabled=False,
amplitude=1.0,
duration_s=0.3,
count=1,
gap_s=0.0,
),
),
collision_stop=QuestHands(
left=QuestHaptic(
enabled=False,
amplitude=1.0,
duration_s=0.3,
count=1,
gap_s=0.0,
),
right=QuestHaptic(
enabled=False,
amplitude=1.0,
duration_s=0.3,
count=1,
gap_s=0.0,
),
),
)
What the controllers are made to feel.
shipped
classmethod
shipped(family: str) -> Self
Returns the Quest setup shipped for an arm family.
openarm is QuestConfig(); openyam differs from it only in pivot_m.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
family
|
str
|
The arm family, |
required |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
Nothing ships for |
validate
validate() -> None
Checks this configuration on its own, raising on what QuestTeleop would refuse in it.
What only an app can judge is left to it: whether address is the one it bound, a pair lock or a second
arm's axis lock on a one-arm cell, and whether the two frames are rotations.
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
bind
bind(
button: str,
action: _Bound | str | None = None,
held: bool = False,
) -> None
Binds button to action, or to nothing when action is None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
button
|
str
|
The button's name, one of |
required |
action
|
_Bound | str | None
|
A |
None
|
held
|
bool
|
Whether to bind what the button does once held for |
False
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
ValueError
|
|
to_json
to_json() -> str
Returns this configuration as JSON, the form from_json reads back.
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
A binding names a button that is not one of |
from_json
classmethod
from_json(wire: str) -> Self
Builds a configuration from the JSON to_json wrote.
The values are not checked here but when the configuration is validated or handed to a QuestTeleop.
Text saved before the haptics or the axis locks were settings reads with the shipped ones.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wire
|
str
|
JSON text, as |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
InvalidArgumentError
|
|
QuestFired
dataclass
QuestFired(action: _Bound, held: bool, refused: str | None)
One action a Quest button asked for since the last poll, as QuestAppOutput.fired lists it.
An action the caller owns, rehome, record_start, record_stop, mark_success, mark_failure, annotate or
switch_profile, is never refused: the app reports it rather than attempting it.
Attributes:
| Name | Type | Description |
|---|---|---|
action |
_Bound
|
What the button was bound to: a |
held |
bool
|
Whether it was the button's hold that fired, rather than its press. |
refused |
str | None
|
Why the app did not do it, or |
action
instance-attribute
action: _Bound
What the button was bound to: a QuestAction, or a QuestBoundAction for one that carries an argument.
held
instance-attribute
held: bool
Whether it was the button's hold that fired, rather than its press.
refused
instance-attribute
refused: str | None
Why the app did not do it, or None when it did, or when the action is the caller's.
QuestCheckOutcome
Bases: StrEnum
How one headset check came out.
Attributes:
| Name | Type | Description |
|---|---|---|
UNTESTED |
Not run yet. |
|
IN_PROGRESS |
Still running a slow step its message names, such as installing the app. |
|
PASSED |
Passed. |
|
WARNED |
Passed with something to see to, such as a battery under 20 percent. |
|
FAILED |
Failed; its message says what was found. |
UNTESTED
class-attribute
instance-attribute
UNTESTED
Not run yet.
IN_PROGRESS
class-attribute
instance-attribute
IN_PROGRESS
Still running a slow step its message names, such as installing the app.
PASSED
class-attribute
instance-attribute
PASSED
Passed.
WARNED
class-attribute
instance-attribute
WARNED
Passed with something to see to, such as a battery under 20 percent.
FAILED
class-attribute
instance-attribute
FAILED
Failed; its message says what was found.
QuestCheckStatus
dataclass
QuestCheckStatus(
name: str,
stage: str,
outcome: QuestCheckOutcome,
message: str,
)
What one headset check last reported, as QuestDevice.status lists it.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
How the check reads on an operator's screen, such as |
stage |
str
|
The stage of provisioning it belongs to: |
outcome |
QuestCheckOutcome
|
How it came out. |
message |
str
|
What it found, or what to do about it. |
name
instance-attribute
name: str
How the check reads on an operator's screen, such as Quest USB connected.
stage
instance-attribute
stage: str
The stage of provisioning it belongs to: Connected, Set up, Running or Batteries.
message
instance-attribute
message: str
What it found, or what to do about it.
QuestBatteries
dataclass
QuestBatteries(
headset_pct: int | None,
left_pct: int | None,
right_pct: int | None,
)
Each Quest battery's charge as last read, from QuestDevice.batteries.
Attributes:
| Name | Type | Description |
|---|---|---|
headset_pct |
int | None
|
The headset's charge, in percent; |
left_pct |
int | None
|
The left controller's charge, in percent; |
right_pct |
int | None
|
The right controller's charge, in percent; |
headset_pct
instance-attribute
headset_pct: int | None
The headset's charge, in percent; None before a read, or when the last one failed.
left_pct
instance-attribute
left_pct: int | None
The left controller's charge, in percent; None before a read, or when the last one failed.
right_pct
instance-attribute
right_pct: int | None
The right controller's charge, in percent; None before a read, or when the last one failed.
QuestAbout
dataclass
QuestAbout(
model: str | None,
serial: str | None,
os_build: str | None,
app_version: str | None,
)
What the headset says it is, from QuestDevice.about.
Attributes:
| Name | Type | Description |
|---|---|---|
model |
str | None
|
The headset's model; |
serial |
str | None
|
The headset's serial number; |
os_build |
str | None
|
The headset's OS build; |
app_version |
str | None
|
The teleop app's installed version; |
model
instance-attribute
model: str | None
The headset's model; None before it is read, or when the read failed.
serial
instance-attribute
serial: str | None
The headset's serial number; None before it is read, or when the read failed.
os_build
instance-attribute
os_build: str | None
The headset's OS build; None before it is read, or when the read failed.
app_version
instance-attribute
app_version: str | None
The teleop app's installed version; None before it is read, or when the read failed.
QuestDeviceConfig
dataclass
QuestDeviceConfig(
adb: str = "adb",
package: str = "bot.anvil.AnvilQuestTeleop",
activity: str = "android.app.NativeActivity",
port: int = 8081,
apk: tuple[str, str] | None = None,
screen_off_ms: int = 14400000,
brightness: int = 5,
retry_s: float = 5.0,
)
How QuestDevice reaches the headset over adb and sets it up.
QuestDeviceConfig() is the shipped setup: adb on the path, the teleop app's package and the default tunneled
port, with no app to install. Assign the fields that differ, apk from anvil.apps.quest_streamer among them.
Attributes:
| Name | Type | Description |
|---|---|---|
adb |
str
|
The |
package |
str
|
The teleop app's Android package. |
activity |
str
|
The activity the teleop app is started with. |
port |
int
|
The port |
apk |
tuple[str, str] | None
|
The app to keep installed, as its |
screen_off_ms |
int
|
How long the headset's screen stays on, in milliseconds. |
brightness |
int
|
The headset's screen brightness, 0 to 255. |
retry_s |
float
|
How long to wait before retrying a startup check that failed, in seconds. |
adb
class-attribute
instance-attribute
adb: str = 'adb'
The adb binary to run: a path, or a name on the PATH.
package
class-attribute
instance-attribute
package: str = 'bot.anvil.AnvilQuestTeleop'
The teleop app's Android package.
activity
class-attribute
instance-attribute
activity: str = 'android.app.NativeActivity'
The activity the teleop app is started with.
port
class-attribute
instance-attribute
port: int = 8081
The port adb reverse tunnels from the headset; it must match the app's own and QuestConfig.address's.
apk
class-attribute
instance-attribute
apk: tuple[str, str] | None = None
The app to keep installed, as its .apk path and the versionName it carries; None leaves it to the user.
An installed version other than this one is replaced.
screen_off_ms
class-attribute
instance-attribute
screen_off_ms: int = 14400000
How long the headset's screen stays on, in milliseconds.
brightness
class-attribute
instance-attribute
brightness: int = 5
The headset's screen brightness, 0 to 255.
retry_s
class-attribute
instance-attribute
retry_s: float = 5.0
How long to wait before retrying a startup check that failed, in seconds.
QuestAppOutput
QuestAppOutput()
Bases: Struct
What one QuestTeleop.poll returns: the command to send, what each arm is doing, and the headset's newest frame.
Read-only. The fields a frame carries, the controllers' poses, triggers, grips, thumbsticks, buttons and tracking,
are None until the first frame arrives, which is how a poll before the headset has connected reads. The head pose
and the velocities are None besides from a streamer older than 1.3.0, which sends neither. A per-arm list holds
one entry per arm, in the cell's order.
Get one from QuestTeleop.poll, never by calling the class.
Methods:
| Name | Description |
|---|---|
frame |
Returns the newest frame as plain data, what a recording logs as the headset's raw input. |
Attributes:
| Name | Type | Description |
|---|---|---|
command |
EePoseCommand
|
The command to send this poll: every arm's held target and gripper opening, driving or not. |
states |
list[QuestArmState]
|
What each arm is doing, in the cell's order. |
drivers |
list[QuestHand | None]
|
The controller driving each arm, in the cell's order; |
measured |
list[Pose]
|
Each arm's tip pose at the |
connected |
bool
|
Whether the headset is connected to the app's socket. |
frame_hz |
float
|
Frames received per second over the last second, in hertz. |
left_controller |
Pose | None
|
The left controller's raw aim pose in the headset's stage frame; |
right_controller |
Pose | None
|
The right controller's raw aim pose in the headset's stage frame; |
left_trigger |
float | None
|
The left index trigger, 0 released to 1 pulled; |
right_trigger |
float | None
|
The right index trigger, 0 released to 1 pulled; |
left_grip |
float | None
|
The left side trigger, the clutch, 0 released to 1 squeezed; |
right_grip |
float | None
|
The right side trigger, the clutch, 0 released to 1 squeezed; |
left_thumbstick |
list[float] | None
|
The left thumbstick's x and y, each -1 to 1; |
right_thumbstick |
list[float] | None
|
The right thumbstick's x and y, each -1 to 1; |
left_buttons |
list[str] | None
|
The buttons held on the left controller, by their names in |
right_buttons |
list[str] | None
|
The buttons held on the right controller, by their names in |
left_tracked |
bool | None
|
Whether the headset tracked the left controller in the newest frame; |
right_tracked |
bool | None
|
Whether the headset tracked the right controller in the newest frame; |
headset_us |
int | None
|
The headset's own clock at the newest frame, in microseconds since its boot; |
protocol |
int | None
|
The protocol the headset's streamer speaks; |
head |
Pose | None
|
The headset's own pose in its stage frame; |
head_tracked |
bool | None
|
Whether the headset tracked its own pose; |
left_velocity |
QuestVelocity | None
|
The left controller's velocity in the newest frame; |
right_velocity |
QuestVelocity | None
|
The right controller's velocity in the newest frame; |
fired |
list[QuestFired]
|
What the buttons asked for since the last poll, at most one per button, in the order of |
command
property
command: EePoseCommand
The command to send this poll: every arm's held target and gripper opening, driving or not.
Send every poll's with anvil.control.Runtime.send_command, which stamps it as the loop picks it up. The poll
a clutch closes on asks IK to reseed, and a skipped one loses that.
drivers
property
drivers: list[QuestHand | None]
The controller driving each arm, in the cell's order; None while its clutch is open.
measured
property
measured: list[Pose]
Each arm's tip pose at the q the poll was given, in the cell's order.
It is what a clutch-in anchors to, and what a panel compares the target against.
connected
property
connected: bool
Whether the headset is connected to the app's socket.
frame_hz
property
frame_hz: float
Frames received per second over the last second, in hertz.
The headset sends one per rendered frame and never asks for a refresh rate, so this reads whatever display mode the headset is in: 90 Hz on a Quest 3S.
left_controller
property
left_controller: Pose | None
The left controller's raw aim pose in the headset's stage frame; None before the first frame.
The stage frame has +y up and -z forward, in meters.
right_controller
property
right_controller: Pose | None
The right controller's raw aim pose in the headset's stage frame; None before the first frame.
left_trigger
property
left_trigger: float | None
The left index trigger, 0 released to 1 pulled; None before the first frame.
right_trigger
property
right_trigger: float | None
The right index trigger, 0 released to 1 pulled; None before the first frame.
left_grip
property
left_grip: float | None
The left side trigger, the clutch, 0 released to 1 squeezed; None before the first frame.
right_grip
property
right_grip: float | None
The right side trigger, the clutch, 0 released to 1 squeezed; None before the first frame.
left_thumbstick
property
left_thumbstick: list[float] | None
The left thumbstick's x and y, each -1 to 1; None before the first frame.
right_thumbstick
property
right_thumbstick: list[float] | None
The right thumbstick's x and y, each -1 to 1; None before the first frame.
left_buttons
property
left_buttons: list[str] | None
The buttons held on the left controller, by their names in QUEST_BUTTONS; None before a frame.
Held, not pressed: what a press asked for is in fired.
right_buttons
property
right_buttons: list[str] | None
The buttons held on the right controller, by their names in QUEST_BUTTONS; None before a frame.
left_tracked
property
left_tracked: bool | None
Whether the headset tracked the left controller in the newest frame; None before the first frame.
While it is false, left_controller means nothing.
right_tracked
property
right_tracked: bool | None
Whether the headset tracked the right controller in the newest frame; None before the first frame.
headset_us
property
headset_us: int | None
The headset's own clock at the newest frame, in microseconds since its boot; None before the first frame.
An ordering key, never comparable with a clock on this machine.
protocol
property
protocol: int | None
The protocol the headset's streamer speaks; None before the first frame.
It is 1 for a streamer older than 1.3.0, which takes no haptics and sends no head pose or velocity, else 2.
head
property
head: Pose | None
The headset's own pose in its stage frame; None until a frame carries one.
It is the eyes when worn, and the chest when hung around the operator's neck.
head_tracked
property
head_tracked: bool | None
Whether the headset tracked its own pose; None while head is.
left_velocity
property
left_velocity: QuestVelocity | None
The left controller's velocity in the newest frame; None until a frame carries one.
right_velocity
property
right_velocity: QuestVelocity | None
The right controller's velocity in the newest frame; None until a frame carries one.
fired
property
fired: list[QuestFired]
What the buttons asked for since the last poll, at most one per button, in the order of QUEST_BUTTONS.
The app has already done the ones it owns, or said why not in QuestFired.refused; rehome, record_start,
record_stop, mark_success, mark_failure, annotate and switch_profile are for the caller to act on.
frame
frame() -> dict[str, Any] | None
Returns the newest frame as plain data, what a recording logs as the headset's raw input.
Returns:
| Type | Description |
|---|---|
dict[str, Any] | None
|
|
KeyboardAction
Bases: StrEnum
What a key does under KeyboardTeleop.
The move, tip, roll, twist, open, close and fast actions act while the key is held, in the frame of the operator's view; the rest act once per press.
Attributes:
| Name | Type | Description |
|---|---|---|
MOVE_FORWARD |
Motion of the gripper the way the view faces, while held. |
|
MOVE_BACK |
Motion of the gripper back toward the viewer, while held. |
|
MOVE_LEFT |
Motion of the gripper to the view's left, while held. |
|
MOVE_RIGHT |
Motion of the gripper to the view's right, while held. |
|
MOVE_UP |
Motion of the gripper up, while held. |
|
MOVE_DOWN |
Motion of the gripper down, while held. |
|
TIP_AWAY |
A downward gripper's tip swung away from the viewer, while held. |
|
TIP_TOWARD |
A downward gripper's tip swung toward the viewer, while held. |
|
ROLL_LEFT |
A downward gripper's tip swung to the view's left, while held. |
|
ROLL_RIGHT |
A downward gripper's tip swung to the view's right, while held. |
|
TWIST_LEFT |
The gripper twisted counterclockwise seen from above, while held. |
|
TWIST_RIGHT |
The gripper twisted clockwise seen from above, while held. |
|
OPEN |
The jaws opened a little at a time, at |
|
CLOSE |
The jaws closed a little at a time, at |
|
FAST |
Every speed multiplied by |
|
TOGGLE_GRIP |
A closed gripper opened, or an open one closed, once per press. |
|
NEXT_ARM |
The next arm driven by the keys, once per press. |
|
ARM_1 |
The first arm driven by the keys, once per press; passed over in a cell without one. |
|
ARM_2 |
The second arm driven by the keys, once per press; passed over in a cell without one. |
|
ARM_3 |
The third arm driven by the keys, once per press; passed over in a cell without one. |
|
ARM_4 |
The fourth arm driven by the keys, once per press; passed over in a cell without one. |
|
STOP |
Every arm stopped where it is aimed and every goal dropped, once per press. |
MOVE_FORWARD
class-attribute
instance-attribute
MOVE_FORWARD
Motion of the gripper the way the view faces, while held.
MOVE_BACK
class-attribute
instance-attribute
MOVE_BACK
Motion of the gripper back toward the viewer, while held.
MOVE_LEFT
class-attribute
instance-attribute
MOVE_LEFT
Motion of the gripper to the view's left, while held.
MOVE_RIGHT
class-attribute
instance-attribute
MOVE_RIGHT
Motion of the gripper to the view's right, while held.
MOVE_UP
class-attribute
instance-attribute
MOVE_UP
Motion of the gripper up, while held.
MOVE_DOWN
class-attribute
instance-attribute
MOVE_DOWN
Motion of the gripper down, while held.
TIP_AWAY
class-attribute
instance-attribute
TIP_AWAY
A downward gripper's tip swung away from the viewer, while held.
TIP_TOWARD
class-attribute
instance-attribute
TIP_TOWARD
A downward gripper's tip swung toward the viewer, while held.
ROLL_LEFT
class-attribute
instance-attribute
ROLL_LEFT
A downward gripper's tip swung to the view's left, while held.
ROLL_RIGHT
class-attribute
instance-attribute
ROLL_RIGHT
A downward gripper's tip swung to the view's right, while held.
TWIST_LEFT
class-attribute
instance-attribute
TWIST_LEFT
The gripper twisted counterclockwise seen from above, while held.
TWIST_RIGHT
class-attribute
instance-attribute
TWIST_RIGHT
The gripper twisted clockwise seen from above, while held.
OPEN
class-attribute
instance-attribute
OPEN
The jaws opened a little at a time, at KeyboardConfig.grip_creep_mps, while held.
CLOSE
class-attribute
instance-attribute
CLOSE
The jaws closed a little at a time, at KeyboardConfig.grip_creep_mps, while held.
FAST
class-attribute
instance-attribute
FAST
Every speed multiplied by KeyboardConfig.fast_scale, while held.
TOGGLE_GRIP
class-attribute
instance-attribute
TOGGLE_GRIP
A closed gripper opened, or an open one closed, once per press.
NEXT_ARM
class-attribute
instance-attribute
NEXT_ARM
The next arm driven by the keys, once per press.
ARM_1
class-attribute
instance-attribute
ARM_1
The first arm driven by the keys, once per press; passed over in a cell without one.
ARM_2
class-attribute
instance-attribute
ARM_2
The second arm driven by the keys, once per press; passed over in a cell without one.
ARM_3
class-attribute
instance-attribute
ARM_3
The third arm driven by the keys, once per press; passed over in a cell without one.
ARM_4
class-attribute
instance-attribute
ARM_4
The fourth arm driven by the keys, once per press; passed over in a cell without one.
STOP
class-attribute
instance-attribute
STOP
Every arm stopped where it is aimed and every goal dropped, once per press.
KeyboardConfig
dataclass
KeyboardConfig(speed_mps: float = 0.08, turn_radps: float = 0.9, fast_scale: float = 2.5, accel_mps2: float = 0.8, turn_accel_radps2: float = 8.0, goal_speed_mps: float = 0.15, grip_mps: float = 0.2, grip_creep_mps: float = 0.05, lead_m: float = 0.05, lead_rad: float = 0.35, bindings: dict[str, KeyboardAction] = {'1': <KeyboardAction.ARM_1: 'arm_1'>, '2': <KeyboardAction.ARM_2: 'arm_2'>, '3': <KeyboardAction.ARM_3: 'arm_3'>, '4': <KeyboardAction.ARM_4: 'arm_4'>, 'a': <KeyboardAction.MOVE_LEFT: 'move_left'>, 'c': <KeyboardAction.ROLL_RIGHT: 'roll_right'>, 'd': <KeyboardAction.MOVE_RIGHT: 'move_right'>, 'down': <KeyboardAction.TIP_TOWARD: 'tip_toward'>, 'e': <KeyboardAction.MOVE_UP: 'move_up'>, 'escape': <KeyboardAction.STOP: 'stop'>, 'f': <KeyboardAction.CLOSE: 'close'>, 'left': <KeyboardAction.TWIST_LEFT: 'twist_left'>, 'q': <KeyboardAction.MOVE_DOWN: 'move_down'>, 'r': <KeyboardAction.OPEN: 'open'>, 'right': <KeyboardAction.TWIST_RIGHT: 'twist_right'>, 's': <KeyboardAction.MOVE_BACK: 'move_back'>, 'shift': <KeyboardAction.FAST: 'fast'>, 'space': <KeyboardAction.TOGGLE_GRIP: 'toggle_grip'>, 'tab': <KeyboardAction.NEXT_ARM: 'next_arm'>, 'up': <KeyboardAction.TIP_AWAY: 'tip_away'>, 'w': <KeyboardAction.MOVE_FORWARD: 'move_forward'>, 'z': <KeyboardAction.ROLL_LEFT: 'roll_left'>})
Everything the keyboard app is told, which KeyboardTeleop takes on construction and through its config.
It says how fast the keys fly the gripper and how quickly they get there, how fast a clicked goal is glided to, the
jaws' speeds, how far the target may lead the arm, and what every key does. KeyboardConfig() is the shipped
setup; assign the fields that differ. Each speed, rate and lead must lie in the range its field states, above its
floor and up to its ceiling, which validate, the constructor and assigning KeyboardTeleop.config check.
Methods:
| Name | Description |
|---|---|
validate |
Checks this configuration, raising on what |
bind |
Binds |
to_json |
Returns this configuration as JSON, the form |
from_json |
Builds a configuration from the JSON |
Attributes:
| Name | Type | Description |
|---|---|---|
speed_mps |
float
|
How fast a move key flies the gripper, in meters per second; above 0, up to 0.5. |
turn_radps |
float
|
How fast a tip, roll or twist key turns it, in radians per second; above 0, up to 3. |
fast_scale |
float
|
What |
accel_mps2 |
float
|
How quickly the gripper gets up to speed and stops, in meters per second squared; above 0, up to 5. |
turn_accel_radps2 |
float
|
How quickly a turn gets up to speed and stops, in radians per second squared; above 0, up to 50. |
goal_speed_mps |
float
|
How fast the gripper glides to a clicked goal, in meters per second; above 0, up to 0.5. |
grip_mps |
float
|
How fast the jaws travel on |
grip_creep_mps |
float
|
How fast the open and close keys creep the jaws, in meters per second; above 0, up to 0.5. |
lead_m |
float
|
How far the target may lead the arm's measured tip, in meters; above 0, up to 0.2. |
lead_rad |
float
|
How far the target's orientation may turn ahead of the arm's, in radians; above 0, up to 1. |
bindings |
dict[str, KeyboardAction]
|
What each key does, keyed by its name in |
speed_mps
class-attribute
instance-attribute
speed_mps: float = 0.08
How fast a move key flies the gripper, in meters per second; above 0, up to 0.5.
turn_radps
class-attribute
instance-attribute
turn_radps: float = 0.9
How fast a tip, roll or twist key turns it, in radians per second; above 0, up to 3.
fast_scale
class-attribute
instance-attribute
fast_scale: float = 2.5
What KeyboardAction.FAST multiplies both speeds by; 1 to 5.
accel_mps2
class-attribute
instance-attribute
accel_mps2: float = 0.8
How quickly the gripper gets up to speed and stops, in meters per second squared; above 0, up to 5.
turn_accel_radps2
class-attribute
instance-attribute
turn_accel_radps2: float = 8.0
How quickly a turn gets up to speed and stops, in radians per second squared; above 0, up to 50.
goal_speed_mps
class-attribute
instance-attribute
goal_speed_mps: float = 0.15
How fast the gripper glides to a clicked goal, in meters per second; above 0, up to 0.5.
grip_mps
class-attribute
instance-attribute
grip_mps: float = 0.2
How fast the jaws travel on KeyboardAction.TOGGLE_GRIP, in meters per second; above 0, up to 1.
grip_creep_mps
class-attribute
instance-attribute
grip_creep_mps: float = 0.05
How fast the open and close keys creep the jaws, in meters per second; above 0, up to 0.5.
lead_m
class-attribute
instance-attribute
lead_m: float = 0.05
How far the target may lead the arm's measured tip, in meters; above 0, up to 0.2.
lead_rad
class-attribute
instance-attribute
lead_rad: float = 0.35
How far the target's orientation may turn ahead of the arm's, in radians; above 0, up to 1.
bindings
class-attribute
instance-attribute
bindings: dict[str, KeyboardAction] = {'1': <KeyboardAction.ARM_1: 'arm_1'>, '2': <KeyboardAction.ARM_2: 'arm_2'>, '3': <KeyboardAction.ARM_3: 'arm_3'>, '4': <KeyboardAction.ARM_4: 'arm_4'>, 'a': <KeyboardAction.MOVE_LEFT: 'move_left'>, 'c': <KeyboardAction.ROLL_RIGHT: 'roll_right'>, 'd': <KeyboardAction.MOVE_RIGHT: 'move_right'>, 'down': <KeyboardAction.TIP_TOWARD: 'tip_toward'>, 'e': <KeyboardAction.MOVE_UP: 'move_up'>, 'escape': <KeyboardAction.STOP: 'stop'>, 'f': <KeyboardAction.CLOSE: 'close'>, 'left': <KeyboardAction.TWIST_LEFT: 'twist_left'>, 'q': <KeyboardAction.MOVE_DOWN: 'move_down'>, 'r': <KeyboardAction.OPEN: 'open'>, 'right': <KeyboardAction.TWIST_RIGHT: 'twist_right'>, 's': <KeyboardAction.MOVE_BACK: 'move_back'>, 'shift': <KeyboardAction.FAST: 'fast'>, 'space': <KeyboardAction.TOGGLE_GRIP: 'toggle_grip'>, 'tab': <KeyboardAction.NEXT_ARM: 'next_arm'>, 'up': <KeyboardAction.TIP_AWAY: 'tip_away'>, 'w': <KeyboardAction.MOVE_FORWARD: 'move_forward'>, 'z': <KeyboardAction.ROLL_LEFT: 'roll_left'>}
What each key does, keyed by its name in KEYBOARD_KEYS; a key left out does nothing.
validate
validate() -> None
Checks this configuration, raising on what KeyboardTeleop would refuse in it.
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
A speed, rate or lead is outside its field's range; a binding names a key that
is not one of |
bind
bind(
key: str, action: KeyboardAction | str | None = None
) -> None
Binds key to action, or to nothing when action is None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The key's name, one of |
required |
action
|
KeyboardAction | str | None
|
A |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
ValueError
|
|
to_json
to_json() -> str
Returns this configuration as JSON, the form from_json reads back.
from_json
classmethod
from_json(wire: str) -> Self
Builds a configuration from the JSON to_json wrote.
The values are not checked here but when the configuration is validated or handed to a KeyboardTeleop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wire
|
str
|
JSON text, as |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
InvalidArgumentError
|
|
KeyboardAppOutput
KeyboardAppOutput()
Bases: Struct
What one KeyboardTeleop.poll returns: the command to send, the arm the keys drive, and what each arm is doing.
Read-only. A per-arm list holds one entry per arm, in the cell's order.
Get one from KeyboardTeleop.poll, never by calling the class.
Attributes:
| Name | Type | Description |
|---|---|---|
command |
EePoseCommand
|
The command to send this poll: every arm's target and gripper opening. |
driven |
int
|
The arm the keys drive, as an index into the cell's arms. |
measured |
list[Pose]
|
Each arm's tip pose at the |
goals |
list[list[float] | None]
|
Where a click sent each arm's gripper, x y z in the world frame in meters; |
blocked |
list[bool]
|
Whether each arm's target is held back by the lead, waiting for the arm. |
speeds_mps |
list[float]
|
How fast each arm's target is moving, in meters per second. |
command
property
command: EePoseCommand
The command to send this poll: every arm's target and gripper opening.
Send every poll's with anvil.control.Runtime.send_command, which stamps it as the loop picks it up: the
controller holds an arm whose command has gone stale.
driven
property
driven: int
The arm the keys drive, as an index into the cell's arms.
measured
property
measured: list[Pose]
Each arm's tip pose at the q the poll was given, in the cell's order: what its target's lead is from.
goals
property
goals: list[list[float] | None]
Where a click sent each arm's gripper, x y z in the world frame in meters; None for an arm with no goal.
A goal stays until the gripper gets there or gives up.
blocked
property
blocked: list[bool]
Whether each arm's target is held back by the lead, waiting for the arm.
An arm holds its target back at the edge of its reach, or against a joint's limit.
speeds_mps
property
speeds_mps: list[float]
How fast each arm's target is moving, in meters per second.
QuestTeleop
QuestTeleop(
config: QuestConfig,
robot: RobotConfig,
q: Sequence[float],
)
The Quest teleop app: a Meta Quest headset's controller frames turned into end-effector targets for ee_pose.
Constructing it binds the socket the headset connects back to, which is closed when this object is collected. Poll
it at the caller's loop rate and send each poll's command to the ee_pose controller. It drives follower arms, one
or two; its methods may be called from any thread.
Binds the headset's socket and starts capture, holding robot's arms where they are at q.
Each arm's target starts at its tip pose and gripper opening at q, so the first commands change nothing
whether or not the headset ever connects. Two arms are driven by the controllers config names for them; one
arm by whichever controller clutches in first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
QuestConfig
|
What the app is told. Its |
required |
robot
|
RobotConfig
|
The robot to drive: one or two follower arms. |
required |
q
|
Sequence[float]
|
The measured joint positions, one per slot, as |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
BusyError
|
Another program holds |
OperationError
|
|
Methods:
| Name | Description |
|---|---|
poll |
Folds every headset frame received since the last poll into the targets and returns the result. |
reset |
Latches every arm so nothing moves until the operator releases the clutch and closes it again. |
play |
Plays |
pulse |
Vibrates |
recentre |
Takes the heading of |
set_enabled |
Lets |
enabled |
Returns whether |
targets |
Returns the targets every arm holds now, in the cell's order. |
Attributes:
| Name | Type | Description |
|---|---|---|
config |
QuestConfig
|
Everything the app is told, as it stands; the buttons write the same fields while it runs. |
config
property
writable
config: QuestConfig
Everything the app is told, as it stands; the buttons write the same fields while it runs.
Read it, edit it and assign it back: everything but address is in force from the next poll, and address
reads back as the socket that was bound. Nothing is taken when the assignment is refused.
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
On assignment, what |
poll
poll(
q: Sequence[float], q_commanded: Sequence[float]
) -> QuestAppOutput
Folds every headset frame received since the last poll into the targets and returns the result.
It never blocks. Send every poll's QuestAppOutput.command, and act on what its fired reports for the caller.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
q
|
Sequence[float]
|
The measured joint positions, one per slot, |
required |
q_commanded
|
Sequence[float]
|
The positions the loop last wrote, one per slot, |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
reset
reset() -> None
Latches every arm so nothing moves until the operator releases the clutch and closes it again.
Call it when something outside the app moved the arms, such as a re-home or a controller swap, and when
QuestDevice.take_reset returns true. Each arm re-anchors to its measured pose at the next clutch-in.
play
Plays cue on hand's controller, or on both for None, as config.haptics says, from the next poll.
Call it on both for the cues the caller sees happen: QuestCue.RECORDING_START and QuestCue.RECORDING_STOP,
whatever started or stopped the episode, and QuestCue.COLLISION_WARNING and QuestCue.COLLISION_STOP. A cue
that lasts is played as if it lasted a second, which is how a settings page lets one be felt. A controller
whose haptic for the cue is off feels nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cue
|
QuestCue | str
|
What to play. |
required |
hand
|
QuestHand | str | None
|
The controller to play it on; |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
|
InvalidArgumentError
|
No headset that takes haptics is connected: none, or one whose streamer is older than 1.3.0. |
pulse
pulse(
hand: QuestHand | str,
amplitude: float,
duration_s: float,
) -> None
Vibrates hand's controller now, at amplitude for duration_s, replacing any pulse still running.
One pulse every poll at a changing amplitude is continuous feedback.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hand
|
QuestHand | str
|
The controller to vibrate. |
required |
amplitude
|
float
|
How hard, 0 to 1; a value outside is clamped to it. |
required |
duration_s
|
float
|
How long, in seconds; the headset clamps it to what the controller can do. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
InvalidArgumentError
|
|
recentre
recentre(hand: QuestHand | str) -> None
Takes the heading of hand's controller as the robot's forward, for QuestMotionFrame.WORLD.
The heading is the way the controller points, flattened to the floor. Point it the way the arms face and call
this once, and again after the headset redraws its boundary. QuestMotionFrame.GRIPPER reads nothing from it.
A button bound to QuestAction.recentre does the same.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hand
|
QuestHand | str
|
The controller whose heading is taken. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
InvalidArgumentError
|
No frame has arrived yet, the controller is untracked, or it points within ten degrees of straight up or down. |
set_enabled
set_enabled(hand: QuestHand | str, enabled: bool) -> None
Lets hand's controller drive, or stops it, from the next poll.
A disabled controller's side trigger reads as released however far it is squeezed: an arm it drives is let go
of where it stands, and enabling it with the trigger squeezed is a clutch-in, anchored to the arm's measured
pose like any other. Its buttons still fire. This is for a controller whose side trigger is held down for it,
one strapped to a glove say. Every controller starts enabled, and reset leaves this alone.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hand
|
QuestHand | str
|
The controller to enable or disable. |
required |
enabled
|
bool
|
Whether it may drive. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
QuestDevice
QuestDevice(config: QuestDeviceConfig | None = None)
The Quest headset's lifecycle over USB, watched on a thread of its own.
Constructing it starts provisioning the headset over adb: setting it up, installing the teleop app when
QuestDeviceConfig.apk names one, starting it and tunneling its port. Each check then re-runs as it comes due.
A critical check failing, the USB link, adb or the headset being awake, means the connection itself is gone: the
headset is provisioned again from the top, and take_reset says so. The thread stops when this object is
collected. Its methods return what the watcher last found, and may be called from any thread.
Starts provisioning the headset and watching it, returning at once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
QuestDeviceConfig | None
|
How to reach and set up the headset; |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
OperationError
|
The watcher thread could not be started. |
Methods:
| Name | Description |
|---|---|
status |
Returns what every check last reported, in the order provisioning runs them. |
batteries |
Returns each battery's charge as last read; the batteries are read every two minutes. |
about |
Returns what the headset says it is, as last read. |
ready |
Returns whether provisioning has finished: every startup check passed, the teleop app running among them. |
take_reset |
Returns whether a critical check has failed since the last call, clearing the flag. |
status
status() -> list[QuestCheckStatus]
Returns what every check last reported, in the order provisioning runs them.
batteries
batteries() -> QuestBatteries
Returns each battery's charge as last read; the batteries are read every two minutes.
ready
ready() -> bool
Returns whether provisioning has finished: every startup check passed, the teleop app running among them.
take_reset
take_reset() -> bool
Returns whether a critical check has failed since the last call, clearing the flag.
A critical check failing means the USB link, adb or the headset being awake was lost, and whatever the
operator was doing is over. Answer true with QuestTeleop.reset, so nothing moves until they clutch in again.
KeyboardTeleop
KeyboardTeleop(
config: KeyboardConfig,
robot: RobotConfig,
q: Sequence[float],
)
The keyboard app: keys held and spots clicked, driving a robot's arms one at a time through ee_pose.
Keys held fly the driven arm's gripper the way the operator's view faces; a spot clicked is a goal its gripper glides to. Neither may lead the measured arm far, so holding a key into the edge of the reach winds nothing up.
It reads no keyboard and no clock: the caller says which keys are held and when, on the clock it polls on. Keys
held are a lease that runs out 0.3 seconds after the last hold, so a page that falls silent stops the arm. It is
released when this object is collected; its methods may be called from any thread.
Builds the app over robot, holding its arms where they are at q and driving the first.
Each arm's target starts at its tip pose and gripper opening at q, so the first commands change nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
KeyboardConfig
|
What the app is told. |
required |
robot
|
RobotConfig
|
The robot to drive. |
required |
q
|
Sequence[float]
|
The measured joint positions, one per slot, as |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
What |
Methods:
| Name | Description |
|---|---|
poll |
Moves every target on by the time since the last poll and returns the result, never blocking. |
hold |
Takes |
press |
Does what one press of |
drive |
Drives |
go_to |
Sends |
reset |
Snaps every target to where the arms are at |
Attributes:
| Name | Type | Description |
|---|---|---|
config |
KeyboardConfig
|
Everything the app is told, as it stands. |
config
property
writable
config: KeyboardConfig
Everything the app is told, as it stands.
Read it, edit it and assign it back: it is in force from the next poll, and keys held now keep doing what they did until they are said again. Nothing is taken when the assignment is refused.
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
On assignment, what |
poll
poll(q: Sequence[float], t_ns: int) -> KeyboardAppOutput
Moves every target on by the time since the last poll and returns the result, never blocking.
The first poll moves nothing, and a gap of more than 0.1 seconds since the last moves the targets no further
than 0.1 seconds would. Send every poll's KeyboardAppOutput.command.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
q
|
Sequence[float]
|
The measured joint positions, one per slot, |
required |
t_ns
|
int
|
Now, in nanoseconds, on the clock |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
hold
hold(
keys: Sequence[str],
heading_rad: float,
t_ns: int,
move_view: Sequence[float] = (0.0, 0.0, 0.0),
turn_view: Sequence[float] = (0.0, 0.0, 0.0),
) -> None
Takes keys and the sticks as held at t_ns, until the lease runs out 0.3 seconds later or the next call.
Each key does what the configuration binds it to while held. A name that is no key, or a key bound to nothing or to an action done once per press, is passed over. The sticks, a touch screen's or a gamepad's, move and turn as the keys do, each as far as it is pushed: 1 is a key's whole speed, and a stick held with a key adds to it. Moving the driven arm drops the goal a click gave it. After a stop, nothing held does anything until every key and stick is let go, so one still held when the stop came does not start the arm again.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
keys
|
Sequence[str]
|
The names of the keys held, from |
required |
heading_rad
|
float
|
The way the operator's view faces, in radians about world +z from +x: what
|
required |
t_ns
|
int
|
When the keys were held, in nanoseconds, on the clock |
required |
move_view
|
Sequence[float]
|
How far the sticks move the gripper along the view's forward, left and up, each from -1 to 1. |
(0.0, 0.0, 0.0)
|
turn_view
|
Sequence[float]
|
How far they turn it about the view's forward, left and up, right-handed, each from -1 to 1. |
(0.0, 0.0, 0.0)
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
press
press(key: str) -> None
Does what one press of key asks, where the configuration binds it to an action done once per press.
That is driving the next arm or a numbered one, opening or closing the driven gripper, or stopping every arm and dropping every goal, the keys held then doing nothing until let go. A name that is no key, a key bound to nothing or to an action done while held, or an arm the cell does not have, is passed over.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The key's name, from |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
drive
drive(arm: int) -> None
Drives arm with the keys from here on.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arm
|
int
|
An index into the cell's arms, from 0. |
required |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
The cell has no arm |
go_to
go_to(arm: int, position_m: Sequence[float]) -> None
Sends arm's gripper gliding to position_m, its orientation kept, and drives that arm with the keys.
It glides at KeyboardConfig.goal_speed_mps. A held move key drops the goal, and one out of reach is given up
a second after the target stops getting closer. The way there is checked first against the robot's collision
pieces, solved a centimeter at a time with the other arms where the last poll measured them, and a goal whose
way takes two links of the robot nearer than anvil.control.CollisionStop.DEFAULT_MARGIN_M that stood further
apart where it starts is refused: a spot behind the robot would otherwise send the gripper through it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arm
|
int
|
An index into the cell's arms, from 0. |
required |
position_m
|
Sequence[float]
|
Where to, x y z in the world frame, in meters. |
required |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
reset
reset(q: Sequence[float]) -> None
Snaps every target to where the arms are at q, holding, with every goal, speed and held key dropped.
Call it after something other than this app moved the arms.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
q
|
Sequence[float]
|
The measured joint positions, one per slot, |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
ReplayMode
Bases: StrEnum
What of an episode EpisodeReplay plays back.
Attributes:
| Name | Type | Description |
|---|---|---|
COMMANDS |
Every command a controller was sent, resent to it at the time the loop picked it up. |
|
JOINT_ANGLES |
Every slot's measured position, tracked through the |
|
DRIVE_COMMANDS |
What the drives were told, each slot's position, velocity, gains and feedforward, through |
COMMANDS
class-attribute
instance-attribute
COMMANDS
Every command a controller was sent, resent to it at the time the loop picked it up.
They run under the settings in effect now, so other gains, IK or tuning change what the arms do.
JOINT_ANGLES
class-attribute
instance-attribute
JOINT_ANGLES
Every slot's measured position, tracked through the joint controller: what the arms did.
The grippers are played by motor angle, and positions are linear between samples.
DRIVE_COMMANDS
class-attribute
instance-attribute
DRIVE_COMMANDS
What the drives were told, each slot's position, velocity, gains and feedforward, through joint.
The loop's guards and limits still apply.
ReplayOverrides
Bases: StrEnum
What a ReplayMode.COMMANDS replay does with each command's tuning overrides and IK parameters.
Attributes:
| Name | Type | Description |
|---|---|---|
APPLY |
Resends them as recorded. |
|
IGNORE |
Strips them, so the settings in effect now apply to every command. |
APPLY
class-attribute
instance-attribute
APPLY
Resends them as recorded.
IGNORE
class-attribute
instance-attribute
IGNORE
Strips them, so the settings in effect now apply to every command.
ReplayPhase
Bases: StrEnum
Where along its timeline an EpisodeReplay is.
Attributes:
| Name | Type | Description |
|---|---|---|
APPROACHING |
Easing the arms from where they stood to the episode's first pose, under |
|
PLAYING |
Playing the episode at its recorded timing, divided by the replay's speed. |
|
HOLDING |
Holding where the episode ended, under |
APPROACHING
class-attribute
instance-attribute
APPROACHING
Easing the arms from where they stood to the episode's first pose, under joint_trajectory.
PLAYING
class-attribute
instance-attribute
PLAYING
Playing the episode at its recorded timing, divided by the replay's speed.
HOLDING
class-attribute
instance-attribute
HOLDING
Holding where the episode ended, under joint.
ReplayPoll
ReplayPoll()
Bases: Struct
What one EpisodeReplay.poll returns: the controller to have active, and the command due now.
Read-only. Make controller active, then send command with anvil.control.Runtime.send_command where there is
one.
Get one from EpisodeReplay.poll, never by calling the class.
Attributes:
| Name | Type | Description |
|---|---|---|
phase |
ReplayPhase
|
Where along its timeline the replay is. |
episode_ns |
int
|
How far into the episode, in nanoseconds of its own time: 0 while approaching, its duration once holding. |
controller |
type
|
The class of the controller that should be active now, as |
command |
JointCommand | JointTrajectoryCommand | EePoseCommand | None
|
The command due now, for |
episode_ns
property
episode_ns: int
How far into the episode, in nanoseconds of its own time: 0 while approaching, its duration once holding.
controller
property
controller: type
The class of the controller that should be active now, as anvil.control.Runtime.controller takes it.
It is anvil.control.JointTrajectoryController while approaching, the controller each recorded command was
for while playing a ReplayMode.COMMANDS replay, and anvil.control.JointController otherwise.
command
property
command: (
JointCommand
| JointTrajectoryCommand
| EePoseCommand
| None
)
The command due now, for controller; None when nothing new is due.
EpisodeReplay
EpisodeReplay(
path: Path,
robot: RobotConfig,
mode: ReplayMode | str,
speed: float = 1.0,
overrides: ReplayOverrides | str = APPLY,
)
A recorded episode played on the robot again, polled for the controller to have active and the command due.
Its timeline eases the arms from where they stand to the episode's first pose under joint_trajectory, plays the
episode at its recorded timing divided by its speed, and holds where it ended under joint. Poll it with the time
since the first poll and the measured joints, make the controller each poll names active, and send its command.
The episode is closed when this object is collected.
Opens the episode at path to replay on robot, checking it was recorded on the same robot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
The episode, a file the recorder finished. |
required |
robot
|
RobotConfig
|
The running robot, which must have the arms, variants and grippers the episode was recorded on. |
required |
mode
|
ReplayMode | str
|
What of the episode to play back. |
required |
speed
|
float
|
The fraction of the recorded pace to play at, above 0 and up to 1, the episode's own. |
1.0
|
overrides
|
ReplayOverrides | str
|
What a |
APPLY
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
ValueError
|
|
NotFoundError
|
Nothing is at |
InvalidArgumentError
|
The file is not a finished episode, or its workcell does not read in this
version; it was recorded on other arms, variants or grippers than |
OperationError
|
The file could not be read. |
Methods:
| Name | Description |
|---|---|
poll |
Returns what is due |
Attributes:
| Name | Type | Description |
|---|---|---|
duration_s |
float
|
The episode's length, from its first state to its last, in seconds of its own time. |
hardware |
list[dict[str, Any]] | None
|
The physical robot the episode was recorded on, as |
duration_s
property
duration_s: float
The episode's length, from its first state to its last, in seconds of its own time.
It plays over this divided by the replay's speed.
hardware
property
hardware: list[dict[str, Any]] | None
The physical robot the episode was recorded on, as anvil.hw.Report.hardware has one; None without one.
It holds per arm its bus, the interface it was placed on and every motor that answered there, with its identity
record's uuid. An episode recorded without a readiness check, as one in the sim is, has none.
poll
poll(elapsed_ns: int, q: Sequence[float]) -> ReplayPoll
Returns what is due elapsed_ns after the first poll, the arms standing at q.
The first poll plans the approach from q; later polls read q only to check it covers the robot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elapsed_ns
|
int
|
Time since the first poll, in nanoseconds. |
required |
q
|
Sequence[float]
|
The measured joint positions, one per slot, |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
OperationError
|
The episode's file stopped reading partway, as one changed or damaged since it was opened does. |
GlovePose
Bases: StrEnum
One pose the wearer holds while a glove is calibrated.
Attributes:
| Name | Type | Description |
|---|---|---|
FIST |
Fists. |
|
FLAT |
Hands flat, fingers straight and together. |
|
SPREAD |
Fingers spread wide. |
FIST
class-attribute
instance-attribute
FIST
Fists.
FLAT
class-attribute
instance-attribute
FLAT
Hands flat, fingers straight and together.
SPREAD
class-attribute
instance-attribute
SPREAD
Fingers spread wide.
GloveProcedure
dataclass
GloveProcedure(
poses: list[GlovePose], hold_s: float, pairing_s: float
)
How the gloves are paired and calibrated, as the SDK runs them. Get one from glove_procedure.
Attributes:
| Name | Type | Description |
|---|---|---|
poses |
list[GlovePose]
|
The poses a calibration holds, in the order they are held. |
hold_s |
float
|
How long each pose is held, in seconds; its reading is the last one before the time is up. |
pairing_s |
float
|
How long |
poses
instance-attribute
poses: list[GlovePose]
The poses a calibration holds, in the order they are held.
hold_s
instance-attribute
hold_s: float
How long each pose is held, in seconds; its reading is the last one before the time is up.
pairing_s
instance-attribute
pairing_s: float
How long GloveTeleop.pair waits for a glove to pair, in seconds.
GloveCalibration
dataclass
GloveCalibration(flat: list[float], bent: list[float])
One glove's calibration: each of its twelve bend sensors' raw reading with the hand flat, and bent.
Build one with from_poses from the readings at the end of each pose of glove_procedure, and keep it by the
glove's name: it holds for that glove until it is calibrated again.
Methods:
| Name | Description |
|---|---|
from_poses |
Builds a calibration from a glove's twelve sensors at the end of each pose. |
Attributes:
| Name | Type | Description |
|---|---|---|
flat |
list[float]
|
Each sensor with the hand flat and the fingers straight and together. |
bent |
list[float]
|
Each sensor bent as far as it goes: in a fist, or spread for the three sensors between the fingers. |
flat
instance-attribute
flat: list[float]
Each sensor with the hand flat and the fingers straight and together.
bent
instance-attribute
bent: list[float]
Each sensor bent as far as it goes: in a fist, or spread for the three sensors between the fingers.
from_poses
classmethod
from_poses(
fist: Sequence[float],
flat: Sequence[float],
spread: Sequence[float],
) -> Self
Builds a calibration from a glove's twelve sensors at the end of each pose.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fist
|
Sequence[float]
|
The sensors after holding fists, |
required |
flat
|
Sequence[float]
|
The sensors after holding the hand flat, fingers together. |
required |
spread
|
Sequence[float]
|
The sensors after holding the fingers spread. |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
A reading is not a flat sequence of numbers. |
InvalidArgumentError
|
A reading is not the glove's twelve sensors; or no sensor moved more than a few counts between the poses: the glove was not worn through them. |
GloveSlot
dataclass
GloveSlot(side: Side, glove: str | None)
One slot of the glove receiver, and the glove paired into it. Get them from GloveTeleop.slots.
Attributes:
| Name | Type | Description |
|---|---|---|
side |
Side
|
Which hand the slot is for. |
glove |
str | None
|
The paired glove's name, e.g. |
glove
instance-attribute
glove: str | None
The paired glove's name, e.g. UDXST4810L; None while the slot is empty.
GloveHandPoll
dataclass
GloveHandPoll(
side: Side,
readings: int,
sensors: list[float],
calibrated: bool,
angles: list[float] | None,
locked: bool,
a_pressed: bool,
)
One hand's part of a GloveTeleop.poll: what its glove sent since the last poll, and what that made of it.
Attributes:
| Name | Type | Description |
|---|---|---|
side |
Side
|
Which hand. |
readings |
int
|
How many readings its glove sent since the last poll; none leaves the rest as the last poll had them. |
sensors |
list[float]
|
The glove's twelve bend sensors, raw, as last read: what |
calibrated |
bool
|
Whether the hand has a calibration to be driven through, from |
angles |
list[float] | None
|
The six angles to send the hand, from 0 closed to 100 open: thumb flexion, thumb abduction, index, middle, |
locked |
bool
|
Whether the B button's grasp lock is on: the thumb parks and pinches with the index, and middle, ring and pinky |
a_pressed |
bool
|
Whether A was pressed since the last poll. |
readings
instance-attribute
readings: int
How many readings its glove sent since the last poll; none leaves the rest as the last poll had them.
sensors
instance-attribute
sensors: list[float]
The glove's twelve bend sensors, raw, as last read: what GloveCalibration.from_poses takes.
calibrated
instance-attribute
calibrated: bool
Whether the hand has a calibration to be driven through, from GloveTeleop.set_calibration.
angles
instance-attribute
angles: list[float] | None
The six angles to send the hand, from 0 closed to 100 open: thumb flexion, thumb abduction, index, middle,
ring, pinky; None until a calibrated glove has sent a reading that could be used.
locked
instance-attribute
locked: bool
Whether the B button's grasp lock is on: the thumb parks and pinches with the index, and middle, ring and pinky close together.
a_pressed
instance-attribute
a_pressed: bool
Whether A was pressed since the last poll.
GloveTeleop
GloveTeleop()
UDCap gloves, read over their USB receiver, to the six angles of the RealHand L6 on each side.
Constructing it opens the receiver, which has a slot for each hand, and starts the glove paired into each slot
streaming. A hand is driven once it has a calibration; then poll turns every reading its glove sent into the
angles to send it with anvil.hw.RealHand.send, at whatever pace the caller polls. B toggles a hand's grasp
lock; A is reported for the caller to act on. Nothing moves a hand here. The receiver is closed when this object
is collected, and its methods may be called from any thread, one at a time.
The receiver's ports belong to the dialout group, which the user has to be in.
Opens the receiver and names the glove in each of its slots, returning once both have answered.
Raises:
| Type | Description |
|---|---|
OperationError
|
No receiver is plugged in; a port cannot be opened, as one the user may not open or HandDriver holds cannot; the receiver does not answer; or a glove is a hardware revision the SDK has no sensor model for. |
Methods:
| Name | Description |
|---|---|
slots |
Returns each slot of the receiver, left first, and the glove paired into it. |
pair |
Pairs a glove into |
unpair |
Clears the receiver's pairings, both slots at once, and both hands' calibrations with them. |
set_calibration |
Drives |
poll |
Returns each hand, left then right, after every reading that arrived since the last poll; never blocks. |
slots
slots() -> list[GloveSlot]
Returns each slot of the receiver, left first, and the glove paired into it.
pair
pair(side: Side) -> None
Pairs a glove into side's slot, blocking until one has or GloveProcedure.pairing_s has passed.
The slot is put in pairing mode, and the wearer turns the glove on and holds its power button about three
seconds, until its lights flash green. A slot that already has a glove must be emptied with unpair first.
The side's hand loses its calibration, since the glove may be another. No poll runs while this blocks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
side
|
Side
|
Which slot to pair into. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
OperationError
|
No glove paired in time; the receiver said the pairing failed; or the glove that paired is the other hand's, or a revision the SDK has no sensor model for. |
unpair
unpair() -> None
Clears the receiver's pairings, both slots at once, and both hands' calibrations with them.
Raises:
| Type | Description |
|---|---|
OperationError
|
The receiver did not confirm, or a slot still reported its glove after. |
set_calibration
set_calibration(
side: Side, calibration: GloveCalibration | None
) -> None
Drives side's hand through calibration of its glove from the next reading on.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
side
|
Side
|
Which hand. |
required |
calibration
|
GloveCalibration | None
|
The calibration of the glove in that side's slot; |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
InvalidArgumentError
|
|
poll
poll() -> list[GloveHandPoll]
Returns each hand, left then right, after every reading that arrived since the last poll; never blocks.
quest_streamer
quest_streamer() -> tuple[str, str] | None
Returns the headset streamer app this package ships, as QuestDeviceConfig.apk takes it.
Assigned to QuestDeviceConfig.apk, it has QuestDevice install this version over any other on the headset.
Returns:
| Type | Description |
|---|---|
tuple[str, str] | None
|
The |
glove_procedure
glove_procedure() -> GloveProcedure
Returns how the gloves are paired and calibrated: the poses, how long each is held, and the pairing wait.