Skip to main content
DimOS uses Pink for Cartesian, EEF-twist, and engagement-relative teleoperation control. This guide explains how to tune that shared backend after a robot model and control task are wired. For the integration steps, see Adding a Custom Arm.

Understand the two solve modes

Planning and streaming control use the same model and objective weights but run them differently: Streaming control does not run convergence retries or random restarts. It must produce one small, bounded command from the current command trajectory on every tick. PinkKinematicsConfig.dt is therefore a planning default; set the coordinator rate correctly instead of using dt to tune teleoperation speed.

Tune the common objective

Start with scalar configuration before adding robot-specific tasks:
skip
Costs are relative. Multiplying all costs by the same factor rarely changes the motion. First balance position and orientation, then add only enough posture cost to shape redundant joints. Pass the same scalar configuration to planning when both paths should start with the same objective:
skip

Customize the task stack

Subclass PinkPoseTargetSolver when scalar weights are insufficient—for example, when a robot needs per-joint posture weights or a manipulability task. Override _create_tasks(), call super(), and change only the required values:
skip
The common stack uses frame/<target frame> for frame tasks and posture/current when posture_cost > 0. A generic bimanual subclass can add shared tasks, and a robot-specific subclass can call super() and modify one value. Each returned dictionary must contain new task instances because Pink tasks are stateful and cannot be shared across control-task instances. Pass the solver class, not an instance. The coordinator constructs one solver for each control task:
skip
Use _before_solve() and _after_solve() only for a genuinely temporal Pink task. Most tuning belongs in _create_tasks().

Bound streaming commands

Teleoperation task parameters bound the QP output against the URDF and live hardware feedback. They are independent of the objective weights:
skip
Start hardware tests with conservative velocity caps. Use the scalar as the fallback, then lower individual proximal or high-torque joints when they move too aggressively. Every effective cap is also intersected with the joint’s URDF velocity limit. Unknown joint names and non-positive or non-finite values are rejected when the task is constructed. The 5 Hz low-pass filter attenuates high-frequency changes in successive Pink solutions. It is time-aware, so changing the coordinator tick rate does not change its cutoff. Lower the cutoff when commands visibly oscillate; raise it when smooth motion feels unnecessarily delayed. Gripper commands bypass this filter. The shared streaming solver also averages the three most recent bounded joint increments with oldest-to-newest weights 0.1, 0.3, and 0.6. The newest solution therefore dominates intentional direction changes, while alternating IK solutions are attenuated. Startup weights are normalized over the samples available, so the first command has no additional delay. Solver subclasses may override joint_increment_filter_weights for robot-specific tuning. The tracking-error limit must tolerate normal execution delay without allowing the command trajectory to run far ahead. Keep feedback tolerance small: it accounts for encoder noise, not extra workspace. Each Pink candidate is filtered against the previous accepted command and clamped to the per-joint/configured and URDF velocity step, the measured-state tracking window, and the inward position margin. Its bounded increment enters the weighted history, and the averaged command passes through the same safety envelope again. Disengagement, timeout, preemption, and E-STOP clear both command and increment history, so the next session starts from measured feedback. Invalid feedback or a failed Pink solve produces no new command for that tick.

Tune in order

  1. Verify joint names, order, base link, target frames, and startup forward kinematics.
  2. Use fake hardware to confirm that small translation and rotation targets move in the expected directions.
  3. Balance position and orientation with posture cost near zero.
  4. Add posture weights or a manipulability task to shape redundant motion.
  5. Exercise singular poses and joint limits; adjust damping and the inward posture margin.
  6. Move to hardware with conservative per-joint velocity caps. Tune the command filter for oscillation, then tune tracking error for measured latency and sensor noise.
  7. Test disengagement, target timeout, preemption, stop, and E-STOP. Each must clear the persistent command trajectory before re-engagement.

OpenArm canonical-zero example

OpenArm starts in the canonical all-zero pose, where both joint-4 coordinates are at their lower limits and the Cartesian Jacobian is rank-deficient. Its solver keeps the measured configuration as the streaming seed but uses a fixed inward-neutral posture target with both joint-4 coordinates at 0.3 rad. Its Pink objective follows the proven G1 bimanual tuning: 8:2 position-to- orientation costs, a 0.01 posture cost, and per-arm posture weights [4, 3, 0.1, 3, 1, 1, 0.1]. This keeps shoulders and elbows near a useful posture while leaving the redundant elbow-roll and wrist-yaw coordinates freer to refine the final pose. The 0.3 rad OpenArm joint-4 target is the only model-specific departure from G1’s zero posture and supplies a deterministic escape direction without a random restart or multi-iteration control tick. Treat this as a model-specific response to a verified startup singularity, not as a default reason to alter a robot’s home pose. Validate the complete IK path with fake hardware, then add a self-hosted test that loads the real model and takes bounded steps from its canonical startup pose.