Teleoperation¶
Operator-side teleoperation stacks: the process an operator runs to stream commands to a robot process. A stack is a
TeleopOperator built from a TeleopInputSource (what the operator wears or holds) and a TeleopStreamer (what it
sends), configured by TeleopOptions. The stack that ships is SONIC whole-body teleoperation: PICO body tracking on
the operator station, sent over ZMQ to the robot process that runs the SONIC policy. The input devices themselves
(keyboard, gamepad, SpaceMouse, GELLO, Meta Quest, PICO, Wuji Glove) are on the
devices page. The teleoperation guide covers every pipeline
and its eden command.
from eden.extensions.teleop.base import TeleopInputSource, TeleopOperator, TeleopStreamer
from eden.options.extensions.teleop import TeleopOptions
Base classes and options¶
Base classes for teleoperation operator stacks.
The interfaces a teleop stack implements, so future stacks (a different robot, a different tracker, a different wire protocol) share one shape:
- :class:
TeleopOperator— the operator-station lifecycle (setup -> loop -> teardown). - :class:
TeleopInputSource— a tracked-input device reader. - :class:
TeleopStreamer— a per-tick pipeline that packs and publishes one message.
:class:TeleopOperator is the configurable extension object, so it follows the
Eden convention shared by every other extension base — ConfigurableMixin
bound to a declarative :class:~eden.options.extensions.teleop.TeleopOptions
(the same shape as DeploymentBase): its config is mirrored onto the instance
from the options, and a concrete stack adds its device-specific parameters as
class attributes.
:class:TeleopInputSource and :class:TeleopStreamer are plain :class:abc.ABC
contracts instead, because they are dependency-injected internal components —
constructed with live callables and sockets (send, a socket reader), not from
a serializable options schema — so ConfigurableMixin (which exists to bind an
Options instance) would not fit them. This mirrors sysid's split between
SystemIdentificationBase (ConfigurableMixin) and its plain-ABC
Excitation interface. The SONIC stack under teleop/sonic is the reference
implementation of all three.
Classes¶
TeleopInputSource
¶
Bases: ABC
A tracked-input device reader (headset / controllers / trackers).
A concrete source owns a background thread or socket and hands the operator
loop the latest sample without blocking. get_latest returns None
until the first frame arrives; disconnected reports whether the stream
has since gone stale.
TeleopStreamer
¶
Bases: ABC
A per-tick operator pipeline that packs and publishes one wire message.
A streamer owns one control mode. run_once consumes the latest input and
calls the injected send at most once per tick, self-pacing to the mode's
rate; its exact arguments are mode-specific, hence the loose signature.
reset_yaw re-zeros the accumulated heading on (re)entry and
on_mode_exit runs when the mode is left.
TeleopOperator
¶
TeleopOperator(options: T)
Bases: ConfigurableMixin[TeleopOptions], ABC
The operator-station lifecycle: build the stack, run the loop, tear down.
Configured by a :class:~eden.options.extensions.teleop.TeleopOptions (the
fields below are mirrored onto the instance by ConfigurableMixin); a
concrete stack adds its device-specific parameters as further class
attributes. Construct it from an options instance built with configure().
run is the template method — setup once, then step until
should_stop is true, then teardown in a finally (so it runs even
on KeyboardInterrupt, which routes through on_interrupt). setup
runs inside the same try — it can block for a long time (headset wait) —
so teardown must cope with a partially built stack. The loop cadence is
set by the streamers' own pacing inside step, not by this loop.
Source code in eden/utils/common.py
Attributes¶
Methods:¶
run
¶
Set up the stack, drive the control loop, and tear down on exit.
Source code in eden/extensions/teleop/base.py
setup
abstractmethod
¶
step
abstractmethod
¶
teardown
abstractmethod
¶
Release every resource (publish socket first, then slow scenes / windows).
Also runs after a setup that raised or was interrupted part-way, so it
releases only what was actually built.
Source code in eden/extensions/teleop/base.py
Configuration options for teleoperation operator stacks.
Classes¶
TeleopOptions
¶
Bases: DynamicConfigurableOptions
Options for teleoperation operator stacks.
Carries the fields common to any operator process; a concrete stack declares
its device-/protocol-specific parameters as class attributes on its
:class:~eden.extensions.teleop.base.TeleopOperator subclass, which are
mirrored through as extras (this options class is extra="allow").
| PARAMETER | DESCRIPTION |
|---|---|
robot_host
|
Host running the robot process this operator drives (its feedback channel).
TYPE:
|
port
|
Port the operator binds to publish its commands / pose / planner stream.
TYPE:
|
feedback_port
|
Port the robot process publishes its debug / feedback state on.
TYPE:
|
Source code in eden/options/options.py
SONIC operator station¶
SONIC operator station: PICO body tracking -> ZMQ commands for the robot process.
The Eden counterpart of the reference stack's T3. One manager loop polls the
controllers, advances the operator state machine, runs the active mode's
streamer (POSE at ~50 Hz, planner modes at ~20 Hz), and publishes the
command / manager_state messages. Run it with eden teleop sonic operator.
PICO input arrives through eden pico bridge, which this process spawns
(pose-only, port --bridge-port) unless --bridge-host points at an
external one or --no-bridge-start is given; --sdk reads the
XRoboToolkit SDK in-process instead (the reference-parity path).
Controls (PICO): A+B+X+Y engage + calibrate (stand in the neutral pose) / stop + exit A+X toggle POSE <-> PLANNER B+Y toggle POSE <-> PLANNER_FROZEN_UPPER_BODY L-stick click: toggle VR_3PT from either planner mode A+B/X+Y next / previous locomotion mode (planner modes) triggers close/open that hand menu hold pause the pose stream (re-align before releasing)
Attributes¶
Classes¶
SonicTeleopOperator
¶
SonicTeleopOperator(options: TeleopOptions)
Bases: TeleopOperator
The SONIC operator station as a :class:TeleopOperator.
setup builds the reader, publish socket, calibration FK, streamers and
state machine (blocking until body data arrives); step runs one control
tick (poll -> state machine -> effects -> stream -> publish); teardown
releases everything, publish socket first. The loop cadence is set by the
active streamer's own pacing sleep inside its run_once. Config is mirrored
from a :class:~eden.options.extensions.teleop.TeleopOptions (build one with
SonicTeleopOperator.configure(**vars(args))); the PICO/SONIC-specific
parameters below ride along as options extras.
Source code in eden/extensions/teleop/sonic/manager.py
Attributes¶
Methods:¶
setup
¶
Source code in eden/extensions/teleop/sonic/manager.py
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | |
step
¶
Source code in eden/extensions/teleop/sonic/manager.py
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 | |
on_interrupt
¶
teardown
¶
Source code in eden/extensions/teleop/sonic/manager.py
Functions:¶
input_lost
¶
input_lost(mode: StreamMode, *, bridge_died: bool, body_stale: bool) -> bool
Whether an engaged operator has lost the input its current mode streams from.
A dead bridge freezes everything. Body staleness only matters to the modes that stream body poses: PLANNER / FROZEN_UPPER run on the controllers (whose stalls the robot's planner timeout already handles), so a tracker occlusion must not end joystick walking.
Source code in eden/extensions/teleop/sonic/manager.py
build_arg_parser
¶
build_arg_parser() -> ArgumentParser
Source code in eden/extensions/teleop/sonic/manager.py
main
¶
Source code in eden/extensions/teleop/sonic/manager.py
Operator state machine for SONIC teleoperation (pure logic, no I/O).
Port of the reference manager's mode table. Two chains share the VR_3PT sub-mode, which remembers its parent:
chain 1: POSE <--(B+Y)--> PLANNER_FROZEN_UPPER_BODY <--(L-stick click)--> PLANNER_VR_3PT
chain 2: POSE <--(A+X)--> PLANNER <--(L-stick click)--> PLANNER_VR_3PT
VR_3PT --(A+X or B+Y)--> POSE; A+B+X+Y engages from OFF / disengages from anywhere
POSE --(menu held)--> POSE_PAUSE --(menu released)--> POSE (level-triggered)
Button combos are rising-edge except the menu pause. Each transition carries
the side effects the manager must run before streaming the new mode's first
tick, plus the command message to send after it.
Disengaging is an Eden deviation: the reference's A+B+X+Y from a live mode
sends stop, which ends the robot process — on hardware a damping-only
command, and an unsupported robot falls. Here it hands the robot to the
planner's IDLE instead, so it keeps balancing in place; the robot session
ends only from the robot side.
Attributes¶
PLANNER_MODES
module-attribute
¶
PLANNER_MODES = (StreamMode.PLANNER, StreamMode.PLANNER_FROZEN_UPPER_BODY, StreamMode.PLANNER_VR_3PT)
Classes¶
Effect
¶
ButtonSnapshot
dataclass
¶
ButtonSnapshot(a: bool = False, b: bool = False, x: bool = False, y: bool = False, left_axis_click: bool = False, left_menu: bool = False)
Transition
dataclass
¶
Transition(previous: StreamMode, mode: StreamMode, changed: bool, effects: list[Effect] = list(), command: tuple[str, bool] | None = None)
OperatorStateMachine
¶
Rising-edge mode table with VR_3PT parent tracking.
Source code in eden/extensions/teleop/sonic/state_machine.py
Attributes¶
Methods:¶
step
¶
step(buttons: ButtonSnapshot) -> Transition
Advance one tick; returns the (possibly unchanged) mode and its effects.
Source code in eden/extensions/teleop/sonic/state_machine.py
force_off
¶
force_off() -> Transition
External force-off (robot fall auto-reset, input loss): back to OFF, no effects but the pose exit.
The caller decides what the robot needs: nothing after a fall auto-reset (it already reset itself), a planner hand-off after an input loss. The next A+B+X+Y re-engages through the normal OFF -> PLANNER edge with full calibration.
Source code in eden/extensions/teleop/sonic/state_machine.py
PICO body-tracking readers for the SONIC operator station.
Both readers hand the pipelines the raw XRoboToolkit service data — the
(24, 7) full-body joint poses in the service's own frame (positions +
scalar-last quaternions) plus controller buttons/sticks/triggers. The SONIC
pipelines apply their own frame conversions (see smpl.py /
three_point.py), so neither reader goes through
:class:eden.utils.devices.pico.xr_client.PicoXrClient's robot-frame poses,
whose conversion differs from the reference teleop math.
:class:BridgeReader (the default) subscribes to eden pico bridge's pose
channel and consumes its lossless body_raw passthrough, so the SDK and its
service-library linkage live in the bridge process. :class:PicoReader is the
in-process port of the reference input_readers.PicoReader: a daemon thread
spins on is_body_data_available, dedupes frames by device timestamp, keeps
exactly one latest sample, and flags a disconnect after 5 s without fresh data.
The bridge reader keeps the same sample semantics (dedupe on the same
timestamp, identical dt/fps bookkeeping).
Attributes¶
Classes¶
BodySample
dataclass
¶
BodySample(body_poses: ndarray, timestamp_realtime: float, timestamp_monotonic: float, timestamp_ns: int, dt: float, fps: float)
ControllerSample
dataclass
¶
ControllerSample(a: bool, b: bool, x: bool, y: bool, left_axis_click: bool, right_axis_click: bool, left_menu: bool, right_menu: bool, left_trigger: float, right_trigger: float, left_grip: float, right_grip: float, left_axis: tuple[float, float], right_axis: tuple[float, float])
PicoReader
¶
Bases: _BodyStreamReader
In-process reader for the raw PICO body stream + on-demand controller reads.
Source code in eden/extensions/teleop/sonic/inputs.py
Methods:¶
start
¶
stop
¶
controllers
¶
controllers() -> ControllerSample
Read the current controller state (level values; edges are the caller's job).
Source code in eden/extensions/teleop/sonic/inputs.py
BridgeReader
¶
BridgeReader(host: str = '127.0.0.1', port: int = SONIC_BRIDGE_POSE_PORT)
Bases: _BodyStreamReader
PicoReader-compatible reader fed by eden pico bridge over ZMQ.
Same interface and sample semantics; the SDK (and its service-library
linkage) lives in the bridge process instead. Consumes the bridge state's
body_raw / body_raw_timestamp_ns passthrough, so the pipelines see
exactly the bytes the SDK reported; buttons map from the bridge's
metaquest-compatible keys (left primary = X, right primary = A).
Source code in eden/extensions/teleop/sonic/inputs.py
Methods:¶
start
¶
stop
¶
get_timestamp_ns
¶
get_timestamp_ns() -> int
The bridge's latest service timestamp (advances at the publish rate).
controllers
¶
controllers() -> ControllerSample
The latest published controller state (neutral before the first message).
Source code in eden/extensions/teleop/sonic/inputs.py
Functions:¶
POSE-mode streamer: PICO body frames -> 50 Hz windowed SMPL pose messages.
Port of the reference PoseStreamer. Device frames (~90 Hz) are resampled
onto a fixed 50 Hz virtual clock by interpolating between the two frames that
bracket each virtual tick (nlerp for rotations), then the last five resampled
frames are re-sent as an overlapping window on every tick — the robot's
motion merger aligns them by frame index and rides out packet loss. Publishing
is gated until the window is full after a mode entry.
Each message also carries: the wrist joint targets derived from the elbow twist/swing mapping, the calibrated VR 3-point pose of the current frame, Dex3 hand targets from the triggers, data-collection toggle edges, and the right-stick heading increment.
Attributes¶
Classes¶
YawAccumulator
¶
YawAccumulator(yaw_gain: float = 1.5, deadzone: float = JOYSTICK_DEADZONE)
Accumulates a heading angle from the right stick (yaw gain 1.5 rad/s).
Source code in eden/extensions/teleop/sonic/pose_streamer.py
Attributes¶
Methods:¶
reset
¶
update
¶
Integrate the stick's x-axis; returns the facing unit vector [x, y, 0].
Source code in eden/extensions/teleop/sonic/pose_streamer.py
PoseStreamer
¶
PoseStreamer(send: Callable[[bytes], None], three_point: ThreePointPose, fk: SmplFk | None = None, num_frames_to_send: int = 5, target_fps: int = 50, record_dir: str = '', on_frame: Callable[[ndarray, ndarray], None] | None = None)
Bases: TeleopStreamer
One POSE-mode streaming iteration per manager tick.
send publishes one packed message; on_frame(vr_3pt_pose,
smpl_joints_local) is the visualization hook, called once per
processed device frame.
Source code in eden/extensions/teleop/sonic/pose_streamer.py
Attributes¶
frame_buffer
instance-attribute
¶
frame_buffer: dict[str, deque] = defaultdict(lambda: deque(maxlen=num_frames_to_send))
Methods:¶
reset_yaw
¶
on_mode_exit
¶
Clear the stream window; re-entry publishes only after it refills.
Source code in eden/extensions/teleop/sonic/pose_streamer.py
run_once
¶
run_once(sample: BodySample | None, controllers: ControllerSample) -> None
Process the latest device frame; publish when a virtual tick completes.
Source code in eden/extensions/teleop/sonic/pose_streamer.py
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | |
Functions:¶
Planner-mode streamer: joystick -> locomotion commands (~20 Hz).
Port of the reference PlannerStreamer. The left stick sets the movement
direction/speed (deadzone-rescaled, rotated into the world by the accumulated
facing), the right stick turns, and A+B / X+Y cycle the locomotion mode.
PLANNER_FROZEN_UPPER_BODY attaches the frozen upper-body targets grabbed from
the robot's feedback; PLANNER_VR_3PT attaches the calibrated 3-point pose and
trigger-driven hand targets.
Attributes¶
Classes¶
PlannerStreamer
¶
PlannerStreamer(send: Callable[[bytes], None], three_point: ThreePointPose, feedback: FeedbackReader, poll_hz: int = 20, on_frame: Callable[[ndarray, ndarray | None], None] | None = None)
Bases: TeleopStreamer
One planner-mode streaming iteration per manager tick.
Source code in eden/extensions/teleop/sonic/planner_streamer.py
Attributes¶
Methods:¶
reset_yaw
¶
send_idle
¶
Send one IDLE planner message, holding the current facing, with no optional fields.
No upper-body, hand or VR 3-point fields: the robot reads their absence as "not operator-driven", so this drops VR 3-point control (back to the g1 encoder) and the frozen upper body. Used when the operator hands the robot back to the planner.
Source code in eden/extensions/teleop/sonic/planner_streamer.py
save_upper_body_position_target
¶
Grab the robot's measured upper body as the frozen targets (retried until it arrives).
Source code in eden/extensions/teleop/sonic/planner_streamer.py
recalibrate_for_vr3pt
¶
Schedule wrist recalibration against the robot's measured joints (retried until they arrive).
Until then no 3-point targets are sent, so the robot keeps the g1 encoder: calibrating against an all-zero configuration instead (the arms are not at zero) would produce silently wrong wrist offsets and a wrist jump.
Source code in eden/extensions/teleop/sonic/planner_streamer.py
run_once
¶
run_once(stream_mode: StreamMode, sample: BodySample | None, controllers: ControllerSample, timestamp_ns: int) -> None
Send one planner command (skipped while the headset stream is stalled).
Source code in eden/extensions/teleop/sonic/planner_streamer.py
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | |
Functions:¶
VR 3-point pose extraction and calibration (VR_3PT mode).
Port of the reference streamer's 3-point path. From the raw (24, 7)
service body frame it takes root (0), left hand (22), right hand (23) and
neck (12 — deliberately not the head, which moves too freely), converts them
to the robot frame, applies per-joint alignment offsets, and expresses the
three keypoints relative to the root. :class:ThreePointPose then calibrates
them against G1 forward kinematics so the streamed targets live in the same
pelvis-local frame the policy's teleop encoder was trained on:
- full calibration (every engage): operator stands in the neutral pose ==
the G1's all-zero joint configuration; the neck orientation and both wrist
offsets are captured against FK of
q = 0. - wrist recalibration (every VR_3PT entry): only the wrist offsets are re-captured, against FK of the robot's measured joints; the neck calibration is preserved to avoid jumps from tracking noise.
The neck position is never measured: it is reconstructed from the calibrated neck orientation through a fixed two-link chain.
Attributes¶
Q_ROBOT_SERVICE
module-attribute
¶
Classes¶
ThreePointPose
¶
Calibrated 3-point targets from raw PICO body frames (in-memory state only).
fk provides key_frame_poses(q_mj29) -> {left_wrist, right_wrist, torso}
(see :class:~eden.extensions.teleop.sonic.g1_fk.G1KeyFrameFk).
Source code in eden/extensions/teleop/sonic/three_point.py
Attributes¶
Methods:¶
process
¶
Extract + calibrate the 3-point pose for one raw body frame.
Source code in eden/extensions/teleop/sonic/three_point.py
calibrate_now
¶
Full calibration against FK of the all-zero joint configuration.
The operator's neutral pose (upright, feet together, forearms bent 90° forward) is the G1's zero configuration.
Source code in eden/extensions/teleop/sonic/three_point.py
reset_with_measured_q
¶
Queue a wrist-only recalibration against FK of the measured joints.
Source code in eden/extensions/teleop/sonic/three_point.py
Functions:¶
process_3pt_pose
¶
Raw (24, 7) service frame -> (3, 7) root-relative keypoints.
Rows are [left wrist, right wrist, neck], each [x, y, z, qw, qx, qy, qz].
Source code in eden/extensions/teleop/sonic/three_point.py
PICO full-body tracking -> SMPL parameters and root-local joints (POSE mode).
Port of the reference streamer's SMPL path:
- The service's 24 global joint rotations (scalar-last, service frame) become
parent-local axis-angle after a per-joint
Ry(180°)frame fix. - The root orientation is re-based: pre-multiply
Rx(+90°)(service Y-up -> robot Z-up), then post-multiply away SMPL's rest orientation (the 120° rotation about (1,1,1)). - Joint positions come from an SMPL-X forward kinematics over the rest
skeleton in
sonic/smplx_joints_info.npz(fetched from theKashu7100/eden_assetsHF dataset; 55 joints; output = the 22 body joints - the two thumb tips, SMPL-X indices 39 and 54 — NOT the SMPL left_hand/right_hand joints), expressed in the root-local frame.
No body-shape (betas) or operator-height scaling exists anywhere in this pipeline — the reference relies on calibration offsets and the policy itself.
Attributes¶
SMPL_PARENTS
module-attribute
¶
SMPL_PARENTS: tuple[int, ...] = (-1, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9, 12, 13, 14, 16, 17, 18, 19, 20, 21)
Classes¶
SmplFk
¶
SmplFk(info_path: str | None = None)
SMPL-X forward kinematics over the HF-hosted rest skeleton (positions only).
Source code in eden/extensions/teleop/sonic/smpl.py
Methods:¶
joints
¶
Posed joint positions (24, 3) for a root axis-angle + 63-dim body pose.
The full 55-joint pose is [global_orient, body_pose, zeros(99)];
the output gathers the 22 body joints plus the two thumb tips.
Source code in eden/extensions/teleop/sonic/smpl.py
SmplFrame
dataclass
¶
Functions:¶
smpl_root_ytoz_up
¶
Rebase the SMPL root quaternion from Y-up to Z-up (pre-multiply Rx(+90°)).
remove_smpl_base_rot
¶
Remove SMPL's rest orientation (post-multiply by conj([.5, .5, .5, .5])).
global_quats_to_local_aa
¶
Service-frame global joint rotations -> parent-local axis-angle (24, 3).
body_poses is the raw (24, 7) service array (scalar-last quats).
Every joint is post-multiplied by Ry(180°) first (the service's joint
frames are flipped relative to SMPL's).
Source code in eden/extensions/teleop/sonic/smpl.py
process_body_poses
¶
Full POSE-mode conversion of one raw (24, 7) service frame.
Source code in eden/extensions/teleop/sonic/smpl.py
SMPL elbow/wrist rotations -> G1 wrist joint targets (POSE mode).
The G1 elbow has a single pitch DOF, so the SMPL elbow's roll/yaw content is decomposed off (twist about the elbow axis vs. swing) and folded into the wrist joints, preserving the SMPL wrist's own rotation. Fills the six wrist entries of the 29-dim IsaacLab joint vector; everything else stays zero on the pose stream.
Attributes¶
Functions:¶
decompose_rotation_aa
¶
decompose_rotation_aa(rotation_aa: ndarray, axis: ndarray) -> tuple[ndarray, ndarray]
Split axis-angle rotations into twist about axis and the residual swing.
Returns wxyz quaternions (q_twist, q_swing) with q = q_twist ⊗ q_swing
(:func:eden.utils.geom.quat_swing_twist on the rotations' quaternions). A zero
rotation decomposes into identities (the reference divides by the zero angle there).
Source code in eden/extensions/teleop/sonic/wrist.py
wrist_joints_from_body_pose
¶
The 29-dim IsaacLab joint vector for a pose-stream frame (wrists only).
body_pose is the frame's (21, 3) (or flat 63-dim) local axis-angle
pose. All Euler readings are intrinsic XYZ; the per-side signs follow the
reference mapping verbatim.
Source code in eden/extensions/teleop/sonic/wrist.py
Trigger -> Dex3 hand joint targets (the reference's gripper gesture solver).
There is no Cartesian hand IK: the controller trigger fabricates synthetic fingertip positions (thumb tip fixed, the middle tip snapped onto it when the trigger passes 0.5), and the solver turns thumb-to-finger distances into a gesture choice plus a linear interpolation from the open pose to a preset closed pose. With trigger input only, the result is a binary open/close of the middle-finger power grasp.
Classes¶
G1GripperGestureSolver
¶
G1GripperGestureSolver(side: str)
Distance-based gesture selection + lerp to a preset Dex3 closed pose.
Source code in eden/extensions/teleop/sonic/hands.py
Functions:¶
generate_finger_data
¶
Synthesize (25, 4, 4) fingertip transforms from controller inputs.
The thumb tip sits at x=1; the middle tip joins it when the trigger passes
0.5 (closing the middle-gesture distance to zero). grip is accepted
for interface parity but unused, exactly like the reference.
Source code in eden/extensions/teleop/sonic/hands.py
hand_targets_from_triggers
¶
Convenience: both hands' Dex3 targets straight from the trigger values.
Source code in eden/extensions/teleop/sonic/hands.py
Robot feedback for the operator station (g1_debug channel, polled on demand).
The operator polls the robot's state only at mode transitions: entering
PLANNER_FROZEN_UPPER_BODY grabs the measured upper-body joints as the frozen
targets, and entering PLANNER_VR_3PT grabs the full measured configuration
for wrist recalibration. :class:RobotResetWatcher is a separate per-tick
subscriber for the robot's fall auto-reset counter — separate because
:meth:FeedbackReader.poll_feedback drains the conflated socket, and the
transition effects rely on finding a message there.
Attributes¶
UPPER_BODY_MJ_GATHER
module-attribute
¶
Classes¶
FeedbackReader
¶
Conflate-latest reader of the robot's g1_debug state.
Source code in eden/extensions/teleop/sonic/feedback.py
Attributes¶
Methods:¶
poll_feedback
¶
Read the newest robot state (if any) into the target fields.
Returns whether the measured body configuration arrived. quiet skips the
miss message, for callers that retry every tick and report once themselves.
Source code in eden/extensions/teleop/sonic/feedback.py
RobotResetWatcher
¶
Watches g1_debug's reset_count for robot-side fall auto-resets.
Owns its own conflate subscriber (PUB fans out to every subscriber). The counter is monotonic, so conflation can never hide an increment; the first observed value is the baseline, which also covers an operator (re)started after the robot already reset.
Source code in eden/extensions/teleop/sonic/feedback.py
Methods:¶
poll
¶
poll() -> bool
True exactly when a previously-seen reset_count has increased.
Source code in eden/extensions/teleop/sonic/feedback.py
G1 key-frame forward kinematics for 3-point calibration (Genesis-backed).
Computes the poses of the two wrist frames and the torso frame — with the
training-time local offsets applied — for a given 29-DOF joint configuration,
root fixed at the origin with identity orientation. This is the reference
frame the teleop encoder's vr_3point_local_* observations were trained
against, so the offsets must match
gear_sonic/config/manager_env/commands/terms/motion.yaml exactly.
The kinematics run on the same Genesis model the robot process simulates
(:class:~eden.options.robots.unitree_g1.G1Dofs29Dex3), via a single-env
kinematics-only scene (transient_robot_env) held open for the process
lifetime. One subtlety: Genesis re-origins the base link onto its inertial
frame, so with the FK root at identity the world origin is the solver base
frame, not the MJCF pelvis body frame the reference (Pinocchio) uses. The key
frames are therefore expressed relative to the pelvis body frame recovered via
get_pos/get_quat(relative=True), which cancels the re-origin exactly —
pinned against a MuJoCo reference in tests/extensions/teleop/sonic/test_g1_fk.py.
Attributes¶
G1_KEY_FRAMES
module-attribute
¶
G1_KEY_FRAMES = {'left_wrist': ('left_wrist_yaw_link', np.array([0.18, -0.025, 0.0])), 'right_wrist': ('right_wrist_yaw_link', np.array([0.18, 0.025, 0.0])), 'torso': ('torso_link', np.array([0.0, 0.0, 0.35]))}
Classes¶
G1KeyFrameFk
¶
G1KeyFrameFk(robot_options: G1Dofs29Dex3 | None = None)
FK for the calibration key frames, root pinned at the origin.
Builds the kinematics scene once (the first construction per process pays
the kernel compile, up to ~30 s) and reuses it for every query; requires
eden.init beforehand and a close() when done.
Source code in eden/extensions/teleop/sonic/g1_fk.py
Methods:¶
key_frame_poses
¶
Key-frame poses for a 29-joint configuration (MJCF order, radians).
Returns {key: {"position" (3,), "orientation_wxyz" (4,)}} with the
local offsets folded into the positions.
Source code in eden/extensions/teleop/sonic/g1_fk.py
Operator visualization: calibrated VR 3-point pose, G1 mesh, SMPL body (PyVista).
Port of the reference VR3Pt visualizer's rendered content, in one window:
- the three calibrated keypoints (left wrist light green, right wrist light blue, head orange) as balls with RGB frame triads,
- the synthesized head chain (origin -> torso offset -> neck along its local Z),
- a semi-transparent G1 mesh at the origin for spatial reference (geometry from the same MJCF asset the calibration FK loads); with waist tracking the three waist joints follow the calibrated neck orientation,
- optionally the 24 SMPL joints (lower body blue, upper red) with bones, anchored beside the robot — a pre-engage check that the avatar mirrors the operator.
Requires pyvista (the sonic-viz extra). The window renders inline on
the manager thread, exactly like the reference (visualization costs stream
latency; run without --vis-vr3pt for the tightest loop).
Attributes¶
SMPL_PARENTS_VIS
module-attribute
¶
SMPL_PARENTS_VIS = (-1, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9, 12, 13, 14, 16, 17, 18, 19, 20, 21)
Classes¶
Vr3PtPoseVisualizer
¶
Vr3PtPoseVisualizer(with_g1_robot: bool = True, enable_waist_tracking: bool = False, enable_smpl_vis: bool = False, axis_length: float = 0.08, ball_radius: float = 0.015, robot_opacity: float = 0.4, off_screen: bool = False)
One interactive window updated once per processed device frame.
Source code in eden/extensions/teleop/sonic/viz.py
Attributes¶
Methods:¶
on_frame
¶
Update the scene for one calibrated (3, 7) pose (+ optional SMPL joints).
Source code in eden/extensions/teleop/sonic/viz.py
Functions:¶
The robot-side half (the SONIC policy runtime and its ZMQ server) is in eden.extensions.policies.sonic; its policy
class is on the policies page.