Skip to content

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 QuestHaptics field is.

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 QuestConfig.bind takes it.

QuestBoundAction

One of the six Quest actions that carry an argument, built with it, as QuestConfig.bind takes it.

QuestHaptic

What one Quest controller feels for a cue that happens once: a burst of count pulses.

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 QuestCue.CONTACT to be felt, and to be felt in full.

QuestHaptics

What the Quest controllers are made to feel, each QuestCue set apart for each controller.

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 QuestTeleop takes on construction and through QuestTeleop.config.

QuestFired

One action a Quest button asked for since the last poll, as QuestAppOutput.fired lists it.

QuestCheckOutcome

How one headset check came out.

QuestCheckStatus

What one headset check last reported, as QuestDevice.status lists it.

QuestBatteries

Each Quest battery's charge as last read, from QuestDevice.batteries.

QuestAbout

What the headset says it is, from QuestDevice.about.

QuestDeviceConfig

How QuestDevice reaches the headset over adb and sets it up.

QuestAppOutput

What one QuestTeleop.poll returns: the command to send, what each arm is doing, and the headset's newest frame.

KeyboardAction

What a key does under KeyboardTeleop.

KeyboardConfig

Everything the keyboard app is told, which KeyboardTeleop takes on construction and through its config.

KeyboardAppOutput

What one KeyboardTeleop.poll returns: the command to send, the arm the keys drive, and what each arm is doing.

QuestTeleop

The Quest teleop app: a Meta Quest headset's controller frames turned into end-effector targets for ee_pose.

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

ReplayMode

What of an episode EpisodeReplay plays back.

ReplayOverrides

What a ReplayMode.COMMANDS replay does with each command's tuning overrides and IK parameters.

ReplayPhase

Where along its timeline an EpisodeReplay is.

ReplayPoll

What one EpisodeReplay.poll returns: the controller to have active, and the command due now.

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

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 GloveTeleop.slots.

GloveHandPoll

One hand's part of a GloveTeleop.poll: what its glove sent since the last poll, and what that made of it.

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 QuestDeviceConfig.apk takes it.

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 QuestConfig.bind takes.

KEYBOARD_KEYS tuple[str, ...]

Every key a keyboard binding may name, by the names KeyboardConfig.bind and KeyboardTeleop.hold take.

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 QuestTeleop.reset, refusing to move.

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 hand's controller heading as the robot's forward.

toggle_motion_frame

Returns TOGGLE_MOTION_FRAME, which switches between the frames the controllers' motion is read in.

scale

Builds the action that multiplies the reach, QuestConfig.position_scale, by multiply_by.

set_scale

Builds the action that sets the reach, QuestConfig.position_scale, to to.

toggle_velocity

Returns TOGGLE_VELOCITY, which switches QuestConfig.velocity_ff on or off.

toggle_rigid

Returns TOGGLE_RIGID, which locks the two arms together at the transform between them, or lets them go.

toggle_axis_lock

Builds the action that puts lock on arms, or takes it off them when every one already holds it.

rehome

Returns REHOME, reported for the caller to bring the arms home.

record_start

Returns RECORD_START, reported for the caller to start recording.

record_stop

Returns RECORD_STOP, reported for the caller to stop recording.

mark_success

Returns MARK_SUCCESS, reported for the caller to say an episode succeeded.

mark_failure

Returns MARK_FAILURE, reported for the caller to say an episode failed.

annotate

Builds the action reported for the caller to pin the annotation it numbers annotation to now.

switch_profile

Builds the action reported for the caller to switch to the tuning profile it numbers profile.

Attributes:

Name Type Description
RECENTRE

A controller's heading taken as the robot's forward; built with the controller by recentre.

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

SET_SCALE

The reach set to a preset; built with the preset by set_scale.

TOGGLE_VELOCITY

QuestConfig.velocity_ff switched on or off.

TOGGLE_RIGID

QuestConfig.rigid switched on or off.

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

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

name str

The action's name as a binding table and a log spell it, such as recentre or toggle_motion_frame.

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

hand is not a QuestHand or its value.

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

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 position_scale allows, 0.1 to 5, is refused when the configuration is validated or handed to a QuestTeleop.

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

left, right or both.

required
lock str

The axes the arms may move along, spelled as QuestConfig.axis_locks spells a lock, such as gripper:z.

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

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

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 RECENTRE, SCALE, SET_SCALE, TOGGLE_AXIS_LOCK, ANNOTATE and SWITCH_PROFILE.

argument QuestHand | float | int | str

recentre's hand, scale's factor, set_scale's reach, annotate's or switch_profile's number, or

name str

The action's name as a binding table and a log spell it, such as recentre or scale.

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; None plays it once.

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)

One value for each Quest controller.

Attributes:

Name Type Description
left T

The left controller's.

right T

The right controller's.

left instance-attribute

left: T

The left controller's.

right instance-attribute

right: T

The right controller's.

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.

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 contact to be felt, and to be felt in full.

recording_start QuestHands[QuestHaptic]

When an episode starts recording; the caller plays it with QuestTeleop.play.

recording_stop QuestHands[QuestHaptic]

When an episode stops recording; the caller plays it with QuestTeleop.play.

collision_warning QuestHands[QuestHaptic]

When the arms come within a collision rule's warning margin; the caller plays it with QuestTeleop.play.

collision_stop QuestHands[QuestHaptic]

When the collision stop holds the arms; the caller plays it with QuestTeleop.play.

tracking_lost instance-attribute

tracking_lost: QuestHands[QuestHeldHaptic]

While the headset has lost sight of the controller.

limit instance-attribute

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

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 QuestTeleop would refuse in it.

bind

Binds button to action, or to nothing when action is None.

to_json

Returns this configuration as JSON, the form from_json reads back.

from_json

Builds a configuration from the JSON to_json wrote.

Attributes:

Name Type Description
address str

Where the headset connects, host:port.

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, None for every axis; a one-arm cell's is the first.

bindings dict[str, _Bound]

What each button does when pressed, keyed by its name in QUEST_BUTTONS; a button left out does nothing.

holds dict[str, _Bound]

What each button does once held for hold_s, on the same terms as bindings.

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: 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, openarm or openyam.

required

Raises:

Type Description
InvalidArgumentError

Nothing ships for family.

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

engage_above is not under 1, or disengage_below not over 0 and no higher than it; grip_trigger_exponent is not above 0; link_timeout_s is zero; position_scale is outside 0.1 to 5; a bound scale factor is not positive and finite, or a bound profile is negative; an axis lock lets no axis through, or names a frame or an axis that is not one; hold_s is zero; a haptic is one no controller can make; a duration is negative or not finite; a binding names a button that is not one of QUEST_BUTTONS; or address is not host:port, or names a host that resolves to nothing.

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

required
action _Bound | str | None

A QuestAction or its value, or a QuestBoundAction; None leaves the button doing nothing.

None
held bool

Whether to bind what the button does once held for hold_s rather than its press. A button with a hold fires its press on release rather than as it goes down.

False

Raises:

Type Description
InvalidArgumentError

button is not one of QUEST_BUTTONS.

ValueError

action is a string no QuestAction has.

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 QUEST_BUTTONS, or a duration is negative or not finite.

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 to_json writes it.

required

Raises:

Type Description
ValueError

wire is not JSON, or a hand, a motion frame or an action is a string that names none.

InvalidArgumentError

wire is missing a field to_json writes.

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 QuestAction, or a QuestBoundAction for one that carries an argument.

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 None when it did, or when the action is the caller's.

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 Quest USB connected.

stage str

The stage of provisioning it belongs to: Connected, Set up, Running or Batteries.

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.

outcome instance-attribute

How it came out.

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; None before a read, or when the last one failed.

left_pct int | None

The left controller's charge, in percent; None before a read, or when the last one failed.

right_pct int | None

The right controller's charge, in percent; None before a read, or when the last one failed.

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; None before it is read, or when the read failed.

serial str | None

The headset's serial number; None before it is read, or when the read failed.

os_build str | None

The headset's OS build; None before it is read, or when the read failed.

app_version str | None

The teleop app's installed version; None before it is read, or when the read failed.

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 adb binary to run: a path, or a name on the PATH.

package str

The teleop app's Android package.

activity str

The activity the teleop app is started with.

port int

The port adb reverse tunnels from the headset; it must match the app's own and QuestConfig.address's.

apk tuple[str, str] | None

The app to keep installed, as its .apk path and the versionName it carries; None leaves it to the user.

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; None while its clutch is open.

measured list[Pose]

Each arm's tip pose at the q the poll was given, in the cell's order.

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; None before the first frame.

right_controller Pose | None

The right controller's raw aim pose in the headset's stage frame; None before the first frame.

left_trigger float | None

The left index trigger, 0 released to 1 pulled; None before the first frame.

right_trigger float | None

The right index trigger, 0 released to 1 pulled; None before the first frame.

left_grip float | None

The left side trigger, the clutch, 0 released to 1 squeezed; None before the first frame.

right_grip float | None

The right side trigger, the clutch, 0 released to 1 squeezed; None before the first frame.

left_thumbstick list[float] | None

The left thumbstick's x and y, each -1 to 1; None before the first frame.

right_thumbstick list[float] | None

The right thumbstick's x and y, each -1 to 1; None before the first frame.

left_buttons list[str] | None

The buttons held on the left controller, by their names in QUEST_BUTTONS; None before a frame.

right_buttons list[str] | None

The buttons held on the right controller, by their names in QUEST_BUTTONS; None before a frame.

left_tracked bool | None

Whether the headset tracked the left controller in the newest frame; None before the first frame.

right_tracked bool | None

Whether the headset tracked the right controller in the newest frame; None before the first frame.

headset_us int | None

The headset's own clock at the newest frame, in microseconds since its boot; None before the first frame.

protocol int | None

The protocol the headset's streamer speaks; None before the first frame.

head Pose | None

The headset's own pose in its stage frame; None until a frame carries one.

head_tracked bool | None

Whether the headset tracked its own pose; None while head is.

left_velocity QuestVelocity | None

The left controller's velocity in the newest frame; None until a frame carries one.

right_velocity QuestVelocity | None

The right controller's velocity in the newest frame; None until a frame carries one.

fired list[QuestFired]

What the buttons asked for since the last poll, at most one per button, in the order of QUEST_BUTTONS.

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.

states property

states: list[QuestArmState]

What each arm is doing, in the cell's order.

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

None before the first frame, else a dict of the properties of the same names: headset_us, protocol, and head, None or a dict of pose and tracked; then left and right, each a dict of the controller's pose, velocity, trigger, grip, thumbstick, buttons and tracked; and arms, a list of one dict per arm in the cell's order, its state and the hand that is its driver, as strings, driver None while the clutch is open. A pose is a dict of position and orientation, and a velocity None or a dict of linear_mps and angular_radps.

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 KeyboardConfig.grip_creep_mps, while held.

CLOSE

The jaws closed a little at a time, at KeyboardConfig.grip_creep_mps, while held.

FAST

Every speed multiplied by KeyboardConfig.fast_scale, while held.

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 KeyboardTeleop would refuse in it.

bind

Binds key to action, or to nothing when action is None.

to_json

Returns this configuration as JSON, the form from_json reads back.

from_json

Builds a configuration from the JSON to_json wrote.

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 KeyboardAction.FAST multiplies both speeds by; 1 to 5.

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 KeyboardAction.TOGGLE_GRIP, in meters per second; above 0, up to 1.

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 KEYBOARD_KEYS; a key left out does nothing.

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

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

required
action KeyboardAction | str | None

A KeyboardAction or its value; None leaves the key doing nothing.

None

Raises:

Type Description
InvalidArgumentError

key is not one of KEYBOARD_KEYS.

ValueError

action is a string no KeyboardAction has.

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 to_json writes it.

required

Raises:

Type Description
ValueError

wire is not JSON, or a binding names an action no KeyboardAction has.

InvalidArgumentError

wire is missing a field to_json writes.

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 q the poll was given, in the cell's order: what its target's lead is from.

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.

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 address is bound here and nowhere else, so port 0 reads back through config as the port the machine picked.

required
robot RobotConfig

The robot to drive: one or two follower arms.

required
q Sequence[float]

The measured joint positions, one per slot, as anvil.control.Snapshot.q holds them.

required

Raises:

Type Description
TypeError

robot is not an anvil.workcell.RobotConfig, or q is not a flat sequence of numbers.

InvalidArgumentError

q does not cover the arms' slots; robot has a leader arm or more than two arms; config is one QuestConfig.validate refuses; config.address is not host:port or resolves to nothing; or a frame in config is not a rotation.

BusyError

Another program holds config.address, such as another QuestTeleop.

OperationError

config.address cannot be bound for another reason, such as an address that is not this machine's.

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 cue on hand's controller, or on both for None, as config.haptics says, from the next poll.

pulse

Vibrates hand's controller now, at amplitude for duration_s, replacing any pulse still running.

recentre

Takes the heading of hand's controller as the robot's forward, for QuestMotionFrame.WORLD.

set_enabled

Lets hand's controller drive, or stops it, from the next poll.

enabled

Returns whether hand's controller may drive, as set_enabled left it.

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 QuestConfig.validate refuses; a frame that is not a rotation; rigid or a second arm's axis lock on a one-arm cell; or an address other than the bound one.

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, anvil.control.Snapshot.q: each arm's tip pose at it anchors the chain at every clutch-in.

required
q_commanded Sequence[float]

The positions the loop last wrote, one per slot, anvil.control.Snapshot.q_commanded: where the controller holds each arm, and where letting go, or losing the controller mid-drive, freezes the target, so the arm stops where it stands instead of finishing the move.

required

Raises:

Type Description
TypeError

q or q_commanded is not a flat sequence of numbers.

InvalidArgumentError

q or q_commanded does not cover the arms' slots.

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

play(
    cue: QuestCue | str, hand: QuestHand | str | None = None
) -> None

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 for both.

None

Raises:

Type Description
ValueError

cue or hand is a string that names none.

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

hand is a string no QuestHand has.

InvalidArgumentError

duration_s is negative or not finite, or no headset that takes haptics is connected: none, or one whose streamer is older than 1.3.0.

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

hand is a string no QuestHand has.

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

hand is a string no QuestHand has.

enabled

enabled(hand: QuestHand | str) -> bool

Returns whether hand's controller may drive, as set_enabled left it.

Parameters:

Name Type Description Default
hand QuestHand | str

The controller asked about.

required

Raises:

Type Description
ValueError

hand is a string no QuestHand has.

targets

targets() -> EePoseCommand

Returns the targets every arm holds now, in the cell's order.

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 for QuestDeviceConfig().

None

Raises:

Type Description
InvalidArgumentError

config.retry_s is negative or not finite, or a field is outside what it holds: port 0 to 65535, brightness 0 to 255, screen_off_ms zero or more.

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.

about

about() -> QuestAbout

Returns what the headset says it is, as last read.

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 anvil.control.Snapshot.q holds them.

required

Raises:

Type Description
TypeError

robot is not an anvil.workcell.RobotConfig, or q is not a flat sequence of numbers.

InvalidArgumentError

What KeyboardConfig.validate refuses, or q does not cover the arms' slots.

Methods:

Name Description
poll

Moves every target on by the time since the last poll and returns the result, never blocking.

hold

Takes keys and the sticks as held at t_ns, until the lease runs out 0.3 seconds later or the next call.

press

Does what one press of key asks, where the configuration binds it to an action done once per press.

drive

Drives arm with the keys from here on.

go_to

Sends arm's gripper gliding to position_m, its orientation kept, and drives that arm with the keys.

reset

Snaps every target to where the arms are at q, holding, with every goal, speed and held key dropped.

Attributes:

Name Type Description
config KeyboardConfig

Everything the app is told, as it stands.

config property writable

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 KeyboardConfig.validate refuses.

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, anvil.control.Snapshot.q: what each arm's lead is measured from.

required
t_ns int

Now, in nanoseconds, on the clock hold is given.

required

Raises:

Type Description
TypeError

q is not a flat sequence of numbers.

InvalidArgumentError

t_ns is negative, or q does not cover the arms' slots.

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 KEYBOARD_KEYS; empty for none.

required
heading_rad float

The way the operator's view faces, in radians about world +z from +x: what KeyboardAction.MOVE_FORWARD drives toward.

required
t_ns int

When the keys were held, in nanoseconds, on the clock poll is given.

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

keys is one string rather than a sequence of them.

InvalidArgumentError

t_ns is negative, heading_rad is not finite, keys holds something other than strings, or move_view or turn_view is not three values from -1 to 1.

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

required

Raises:

Type Description
TypeError

key is not a string.

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

position_m does not hold three values, or one is not finite; the cell has no arm arm; or the way there takes the robot into itself, naming the two links and where.

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, anvil.control.Snapshot.q.

required

Raises:

Type Description
TypeError

q is not a flat sequence of numbers.

InvalidArgumentError

q does not cover the arms' slots.

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 joint controller: what the arms did.

DRIVE_COMMANDS

What the drives were told, each slot's position, velocity, gains and feedforward, through joint.

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

PLAYING

Playing the episode at its recorded timing, divided by the replay's speed.

HOLDING

Holding where the episode ended, under joint.

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 anvil.control.Runtime.controller takes it.

command JointCommand | JointTrajectoryCommand | EePoseCommand | None

The command due now, for controller; None when nothing new is due.

phase property

phase: ReplayPhase

Where along its timeline the replay is.

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

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 ReplayMode.COMMANDS replay does with each command's tuning overrides and IK parameters; read for that mode alone.

APPLY

Raises:

Type Description
TypeError

robot is not an anvil.workcell.RobotConfig.

ValueError

mode or overrides is a string that names none.

NotFoundError

Nothing is at path.

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 robot's; it holds no motion; a ReplayMode.COMMANDS replay of an episode with no controller commands, which one the leader_follower controller drove has none of; a ReplayMode.DRIVE_COMMANDS replay of one that recorded no drive commands; or speed is outside (0, 1].

OperationError

The file could not be read.

Methods:

Name Description
poll

Returns what is due elapsed_ns after the first poll, the arms standing at q.

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 anvil.hw.Report.hardware has one; None without one.

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, anvil.control.Snapshot.q.

required

Raises:

Type Description
TypeError

q is not a flat sequence of numbers.

InvalidArgumentError

elapsed_ns is negative, or q does not cover the robot's slots.

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 GloveTeleop.pair waits for a glove to pair, in seconds.

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, GloveHandPoll.sensors.

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. UDXST4810L; None while the slot is empty.

side instance-attribute

side: Side

Which hand the slot is for.

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 GloveCalibration.from_poses takes.

calibrated bool

Whether the hand has a calibration to be driven through, from GloveTeleop.set_calibration.

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.

side instance-attribute

side: Side

Which hand.

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 side's slot, blocking until one has or GloveProcedure.pairing_s has passed.

unpair

Clears the receiver's pairings, both slots at once, and both hands' calibrations with them.

set_calibration

Drives side's hand through calibration of its glove from the next reading on.

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

side is not a side.

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; None drives the hand no more.

required

Raises:

Type Description
ValueError

side is not a side.

InvalidArgumentError

calibration does not hold twelve values in each half.

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 .apk's path and its versionName, or None when this package carries none, in which case the app on the headset is left as it was installed by hand.

glove_procedure

glove_procedure() -> GloveProcedure

Returns how the gloves are paired and calibrated: the poses, how long each is held, and the pairing wait.