Configuration¶
Every tool in PiperPilot (piper-collect, piper-replay, piper-infer, ...) reads the same YAML configuration. This page explains how the layering works, describes the two overlay files that ship with the repository, and then documents every key in the default configuration, block by block.
How configuration works¶
Configuration is built in three layers, each applied on top of the previous one:
piper_teleop/configs/default.yaml— always loaded first. It contains every key with a working default.--config file.yaml— an optional overlay. Only the keys you set in the overlay are overridden; everything else keeps its default. Paths starting with~are expanded.- CLI flags — applied last, on top of both files.
The merge is a deep merge (_deep_update in piper_teleop/config.py): nested dictionaries are merged key by key, so an overlay containing only teleop.limits.max_lin_vel touches exactly that one value. Any non-dictionary value — scalars and lists — replaces the default wholesale.
Lists are replaced, not merged
If your overlay sets impedance.kp, you must give all six values. If it sets cameras, it replaces the entire camera list — you cannot patch a single camera entry.
The CLI flags that map onto config keys (see CLI reference for the full flag list):
| Flag | Effect on config |
|---|---|
--input quest\|spacemouse |
sets input |
--sim |
sets arm.backend: fake (no hardware) |
--position |
sets arm.backend: piper (move_p position control) |
--impedance |
sets arm.backend: piper_mit (the default backend anyway) |
--hand left\|right |
sets quest.control_hand |
If you pass more than one backend flag, --sim wins, then --position, then --impedance.
Shipped overlays¶
Two overlay files live in the repository root.
A conservative overlay for your first sessions on the real arm. Use it until you are comfortable, then drop it (defaults are full speed/scale):
make collect CONFIG=first_run.yaml
piper-collect --config first_run.yaml # equivalent
piper-collect --config first_run.yaml --task "..."
| Key | Overlay value | Default | Effect |
|---|---|---|---|
arm.speed_percent |
50 |
100 |
firmware-side global speed governor at half |
teleop.pos_scale |
0.6 |
1.0 |
hand motion damped to 60% |
teleop.limits.max_lin_vel |
0.4 |
0.8 |
m/s cap halved |
teleop.limits.max_ang_vel |
1.5 |
2.5 |
rad/s cap reduced |
spacemouse.max_lin_vel |
0.08 |
0.15 |
m/s at full deflection |
spacemouse.max_ang_vel |
0.5 |
0.9 |
rad/s at full deflection |
See Your first teleop session for the walkthrough that uses it.
A diagnostic probe that answers one question: does the firmware gravity-compensate in MIT mode? It sets kp near zero (no spring to hold the arm up) with moderate kd (any fall stays slow and damped):
The procedure and how to interpret the result are described in Control modes & tuning.
| Key | Overlay value |
|---|---|
impedance.kp |
[0.5, 0.5, 0.5, 0.5, 0.5, 0.5] |
impedance.kd |
[1.0, 1.5, 1.2, 0.5, 0.5, 0.3] |
impedance.engage_on_start |
true |
Key reference¶
All units are meters, radians, and seconds unless noted otherwise.
input¶
| Key | Default | Meaning |
|---|---|---|
input |
quest |
teleop input device: quest or spacemouse |
quest¶
Settings for the Meta Quest 3 link (see the Quest guide).
| Key | Default | Meaning |
|---|---|---|
host |
127.0.0.1 |
via adb forward tcp:8735 tcp:8735 (wired USB) |
port |
8735 |
pose/JSON stream port |
control_hand |
right |
which controller drives the arm |
stale_timeout_s |
0.25 |
freeze arm target if no fresh pose within this window |
quest.video¶
Streams RealSense views into the headset as floating panels over passthrough. Video uses a separate port so the pose stream keeps absolute priority.
| Key | Default | Meaning |
|---|---|---|
enabled |
true |
stream camera panels into the headset |
port |
8736 |
MJPEG video port (separate from the pose stream) |
fps |
15 |
panel stream frame rate |
quality |
70 |
JPEG quality |
max_width |
640 |
maximum panel frame width |
teleop¶
The Quest clutch-based teleop controller.
| Key | Default | Meaning |
|---|---|---|
rate_hz |
100 |
control loop rate (move_p streaming) |
pos_scale |
1.0 |
controller motion → arm motion scale |
clutch_engage |
0.7 |
squeeze (grip) analog threshold to engage |
clutch_release |
0.3 |
hysteresis release threshold |
yaw_offset_deg |
0.0 |
extra rotation about robot +z applied to the XR→robot mapping (tune if the operator does not face the same direction as robot +x) |
teleop.filter¶
One-euro filter on the controller pose.
| Key | Default | Meaning |
|---|---|---|
min_cutoff |
1.2 |
Hz; lower = smoother but laggier at rest |
beta |
0.02 |
speed coefficient; higher = less lag during fast motion |
d_cutoff |
1.0 |
derivative cutoff |
teleop.limits¶
Safety envelope
These keys define the safety envelope for the real arm: the workspace box, per-tick velocity clamps, and the max_cmd_deviation tether that prevents catch-up lunges after blocking or faults. Widen them only deliberately, with clear space around the robot. Controllers refuse to engage while the end effector is outside the workspace box — run make home after power-on first.
| Key | Default | Meaning |
|---|---|---|
workspace_min |
[0.15, -0.35, 0.03] |
EEF position box lower corner, robot base frame |
workspace_max |
[0.55, 0.35, 0.50] |
box upper corner (Piper reach ~0.62 m — stay inside) |
max_lin_vel |
0.8 |
m/s cap on target displacement per tick |
max_ang_vel |
2.5 |
rad/s cap on target rotation per tick |
pitch_abs_max_deg |
88.0 |
keep clear of move_p's hard pitch limit (90 deg) |
max_cmd_deviation |
0.12 |
m; target is tethered to the measured EEF pose (prevents catch-up lunges after blocking/faults) |
spacemouse¶
Rate control: puck deflection → EEF velocity (see the SpaceMouse guide).
| Key | Default | Meaning |
|---|---|---|
device |
"" |
pyspacemouse device name; "" = auto-detect |
rate_hz |
100 |
control loop rate |
deadzone |
0.10 |
normalized deflection below this is ignored |
max_lin_vel |
0.15 |
m/s at full deflection |
max_ang_vel |
0.9 |
rad/s at full deflection |
axis_signs |
[1, 1, 1, -1, -1, -1] |
flip individual axes: x, y, z, roll, pitch, yaw (rotations inverted to match operator preference) |
gripper_button |
0 |
left button: toggle gripper |
record_button |
1 |
right button: short press = start/stop episode, long press (home_hold_s) = home; both buttons = discard |
chord_window_s |
0.2 |
single-button actions wait this long for a chord partner |
home_hold_s |
1.0 |
hold the record button this long to home the arm |
stale_timeout_s |
0.15 |
no HID report within this window → twist treated as zero |
arm¶
The pyAgxArm backend and CAN link.
arm.firmware must match the robot
firmware is a pyAgxArm PiperFW enum name and must match what robot.get_firmware() reports — on mismatch the tools refuse to start. Verified pairing on this arm: firmware S-V1.8-9 → V189. See Troubleshooting if startup fails here.
| Key | Default | Meaning |
|---|---|---|
backend |
piper_mit |
piper_mit = MIT impedance (default: host IK + per-joint spring-damper, compliant on contact, bounded-force safety); piper = move_p position control (firmware IK, tightest tracking; CLI --position); fake = full pipeline without hardware (CLI --sim) — see Control modes |
model |
PIPER_X |
pyAgxArm ArmModel enum name (PIPER / PIPER_X / ...) |
firmware |
V189 |
pyAgxArm PiperFW enum name — must match robot.get_firmware() |
interface |
socketcan |
CAN interface type |
channel |
can0 |
CAN channel |
bitrate |
1000000 |
CAN bitrate |
speed_percent |
100 |
firmware-side global speed governor |
enable_soft_joint_limits |
true |
enable soft joint limits |
home_joints |
null |
e.g. [0.0, 0.8, -0.6, 0.0, 0.6, 0.0]; null = don't auto-home |
impedance¶
Used by the piper_mit backend only. Torque law: T = kp*(q_des - q) - kd*v + t_ff. See Control modes for how this compares to position mode, and Benchmarks for measured tracking numbers with the shipped gains.
kp/kd are provenance-bound
The control backend and gains (backend, kp, kd, t_ff, gravity_ff, plus arm.firmware and recording.fps) are recorded in each dataset's provenance metadata. Changing any of them between sessions of the same dataset prints a prominent warning — your demonstrations would mix different arm dynamics. See Dataset format.
| Key | Default | Meaning |
|---|---|---|
kp |
[5, 25, 5, 8, 8, 5] |
N·m/rad — near SDK reference (10); tune on-robot: droops under gravity → raise; hums/buzzes → lower |
kd |
[0.3, 1.2, 0.3, 0.45, 0.45, 0.3] |
damping; too high amplifies velocity sensor noise into audible hum (SDK ref 0.8) |
t_ff |
[0, 0, 0, 0, 0, 0] |
manual feed-forward trim (N·m), added on top |
gravity_ff |
true |
snapshot the position servo's holding torques at MIT entry → no droop at/near entry pose, torque-smooth handover (auto-disabled if the post-entry deflection check fails) |
soft_start_s |
0.8 |
gain ramp at MIT entry (kills handover clunk) |
engage_on_start |
true |
enter impedance hold right at startup (false = only on first clutch engage) |
max_joint_vel |
1.5 |
rad/s per-joint target step clamp |
impedance.ik¶
Host-side damped-least-squares IK used in MIT mode.
| Key | Default | Meaning |
|---|---|---|
iters |
4 |
IK iterations per tick |
damping |
0.05 |
DLS lambda |
pos_tol |
0.002 |
m; position tolerance |
impedance.joint_limits_deg¶
Auto-intersected with the SDK per-model preset (PIPER_X: j4/j5 are ±89 in the SDK).
| Key | Default |
|---|---|
lower |
[-148, 2, -168, -87, -87, -178] |
upper |
[148, 178, -2, 87, 87, 178] |
gripper¶
| Key | Default | Meaning |
|---|---|---|
enabled |
true |
enable the gripper |
max_width |
0.07 |
0.07 or 0.10 depending on installed gripper stroke |
force |
2.0 |
newtons |
rate_hz |
50 |
gripper command rate (separate from arm rate) |
cameras¶
A list of RealSense color streams. Each entry has name, serial, width, height, fps.
Pin serials on multi-camera rigs
Empty serials auto-select unused cameras in enumeration order. For stable
front/wrist/back names, set your own serials in a git-ignored local overlay.
Overriding cameras replaces the entire list.
| Name | Default serial | Resolution |
|---|---|---|
cam_front |
"" (auto) |
640x480 @ 30 |
cam_wrist |
"" (auto) |
640x480 @ 30 |
cam_back |
"" (auto) |
640x480 @ 30 |
monitor¶
Local web dashboard served by piper-collect (see
Collecting datasets).
| Key | Default | Meaning |
|---|---|---|
enabled |
true |
serve the dashboard while collecting |
host |
127.0.0.1 |
bind address; 0.0.0.0 allows other LAN devices to view (unauthenticated — this exposes the camera views to the LAN) |
port |
8780 |
HTTP port |
recording¶
LeRobot dataset output (see Data collection and Dataset format).
| Key | Default | Meaning |
|---|---|---|
fps |
30 |
proprio/action rate; matches camera fps so every row has a near-simultaneous fresh frame — configure your trainer with action_frequency=30 (e.g. sampling stride 3 for a 10 Hz decision rate) |
root |
~/piper_datasets |
dataset root directory |
task |
"pick up the object and place it in the box" |
default language instruction (override with --task) |
robot_type |
piper |
robot type string written into the dataset |
chunks_size |
1000 |
episodes per dataset chunk |
save_raw_quest |
true |
also dump the raw quest stream per episode (debug) |
recording.video¶
ffmpeg encode settings for the camera mp4 files.
| Key | Default | Meaning |
|---|---|---|
codec |
libx264 |
video codec |
pix_fmt |
yuv420p |
pixel format |
crf |
23 |
constant rate factor (quality) |
preset |
veryfast |
encoder speed/size preset |
viz¶
| Key | Default | Meaning |
|---|---|---|
enabled |
false |
rerun visualization during teleop/collect |
spawn |
true |
spawn a local rerun viewer (false + --serve for headless) |