cablepilot / configs /stringman.yaml
Ethgar's picture
First upload (private preview)
168e232 verified
Raw History Blame Contribute Delete
12.1 kB
# Stringman (Neufangled), "Arpeggio" layout, carbon 270 mm pole. Every number says where it came from.
# fw = github.com/nhnifong/cranebot3-firmware @ 370287a (2026-10-07). fw-sim = its simulator/ (MuJoCo model + bridge),
# whose own README lists which of its numbers are guesses. MEASURE = not in the docs or the firmware: a guess until
# measured on the real machine. Full table with reasoning: docs/MACHINE.md.
room:
# fw-sim: a 4 m square room, anchors (2,2,2) and (-2,-2,2). Docs: anchors ~13 cm below the ceiling. We keep the
# fw-sim footprint but put the mount points at 2.30 m (a 2.43 m ceiling, the common one): fw-sim's 2.0 m leaves the
# gripper no reach to a high shelf (lines flatten, tension grows as 1/sin(angle)).
side_m: 4.0
mount_z_m: 2.30
# corner mount points, xy, inset from the walls by the anchor's eyelet offset (fw definitions.arp_anchor_*_eyelet,
# ~5 cm). Order = the firmware's line order: 0 anchor0 direct, 1 anchor0 indirect (via post A), 2 anchor1 direct,
# 3 anchor1 indirect (via post B) (fw-sim tendons line_0..3).
mounts_xy: [[1.95, 1.95], [-1.95, 1.95], [-1.95, -1.95], [1.95, -1.95]]
indirect_run_m: [0.0, 3.93, 0.0, 3.93] # fixed cross-room run of the indirect lines (fw-sim: 3.9301 m)
lines:
# fw-sim: 4000 N/m per line at ~3 m (its README: "may be too stiff; real monofilament stretches considerably more").
# We write it as EA = k * L so a longer line (indirect, + 3.93 m) is softer: EA = 4000 * 3.0. MEASURE.
EA_N: 12000.0
damping_Ns_m: 40.0 # fw-sim kv 80 + tendon damping 4; halved: kv acts on a slack line too. MEASURE
max_force_N: 40.0 # fw spool_dm NO_CONN_TENSION_LIMIT 40 N (motor torque cut); fw-sim caps at 25 N
max_safe_tension_N: 28.0 # fw config_loader default (16 N backfill on older configs; observer default 16)
tension_floor_N: 0.1 # fw spool_dm TENSION_FLOOR_N: the spool reels in to keep this on every line
tension_kp_mps_per_N: 0.3 # fw spool_dm TENSION_KP
tension_corr_max_mps: 0.5 # fw spool_dm MAX_TENSION_CORRECTION_MPS
spool:
loop_hz: 50 # fw spool_dm LOOP_FREQ_HZ
max_line_speed_mps: 2.0 # fw spool_dm MAX_SAFE_LINE_SPEED
# Line acceleration. fw spool_dm MAX_ACCEL 0.01 and config max_accel 0.3 go to the DaMiao ACC register in units the
# code does not state; fw-sim floors it at 5 rad/s^2 "so the simulator is usable". 5 rad/s^2 x 0.229 m/rev / 2pi
# (72-73 mm spool, fw definitions.damiao_*) = 0.18 m/s^2 of line. MEASURE (step a line speed, log encoder).
line_accel_mps2: 0.18
meters_per_rev: 0.229 # pi x 73 mm (fw definitions.damiao_full_spool_diameter_fishing_line)
host:
# fw observer.speed_limit(): 0.35 m/s (0.45 with firmware gate "speed_0.45"); the norm of the summed velocity.
speed_limit_mps: 0.35
velocity_key_ttl_s: 2.0 # fw observer INPUT_VELOCITY_TTL_S: a keyed velocity expires 2 s after its last update
estimator_hz: 60 # fw position_estimator.predict_forwards (visual update 30 Hz, commanded-vel 30 Hz)
# move_direction_speed adds downward_bias -0.04 to unit vectors; with speed=None (the LeRobot path, and ours) the
# velocity is used as given. Line speeds come from finite differences at the ESTIMATED gantry position, once per
# command (KINEMATICS_STEP_SCALE 10): our plant does the same every planner tick.
body:
# Gantry + pole + gripper hang as ONE rigid body from the point where the 4 lines meet (fw-sim; fw swing.py treats it
# as a pendulum). Masses: fw-sim estimates ("nothing in the firmware repo records them"). MEASURE (kitchen scale).
gantry_mass_kg: 0.35
pole_mass_kg: 0.045
gripper_mass_kg: 0.60
finger_mass_kg: 0.04
hook_r_m: 0.055 # fw-sim (from gantry.glb): lines clip to cross arms 55 mm out, on the diagonals
hook_z_m: 0.02
pole_top_z_m: -0.06
pole_bottom_z_m: -0.329 # fw-sim: pole_offset_carbon270 - grommet height 0.1167
gripper_origin_z_m: -0.4457 # fw definitions.pole_offset_carbon270 (0.5757 - 0.13)
gripper_cog_dz_m: 0.055 # fw definitions.gripper_cog (y-up gripper frame: 55 mm above the gripper origin)
# Swing: fw definitions.pole_length_carbon270 = 0.3089 m effective (measured with 'polecal'): period 1.115 s. The
# gripper inertia is solved at build time so the twin's swing period matches it (fw-sim is ~10 % long, its README).
swing_length_m: 0.3089
# swing damping on the body's rotation (N m s): zeta ~ 0.02 = c / (2 I_O omega), I_O ~ 0.12 kg m^2, omega 5.8 rad/s,
# 1/e in ~9 s. fw-sim has none (its swing never decays); fw observer fits a decay during 'polecal'. MEASURE.
swing_damping_Nms: 0.03
finger_pivot_x_m: 0.025 # fw-sim: two fingers pivot at x = +-0.025, close side to side, geared on one servo
finger_pivot_dz_m: 0.041 # pivot above the gripper origin (fw-sim, gripper.glb)
finger_len_m: 0.110 # pivot to pad centre, down (fw-sim pad_left pos y -0.110)
pad_inset_m: 0.019 # pad centre inside the pivot when closed (fw-sim pad pos x -0.019)
pad_half: [0.006, 0.022, 0.030] # fw-sim pad box half sizes (x across, y along, z vertical)
finger_travel_deg: 59.0 # fw gripper_arp_server.FINGER_TRAVEL_DEG; firmware reports -90 (open)..90 (closed)
camera_dy_m: 0.026 # fw definitions.gripper_camera: 27 mm toward the nose, tilted 9.06 deg back
camera_dz_m: 0.0026
camera_tilt_deg: 9.06
camera_fovy_deg: 42.557 # fw-sim: 2 atan(192/493), cameraCalWide; stream 684x384 @ 54 fps
camera_res: [684, 384]
laser_dy_m: -0.050 # fw-sim rangefinder site (0, 0.0025, 0.0502) in the gripper frame
laser_dz_m: 0.0025
gripper:
loop_hz: 60 # fw gripper_arp_server DT = 1/60
wrist_range_deg: [0, 1080] # docs python_api; fw-sim joint +-3 pi about the neutral heading
wrist_max_dps: 180.0 # MEASURE: docs ease_wrist peak 90 deg/s default; servo ST3215 class ~ 300 deg/s
finger_max_dps: 360.0 # MEASURE (firmware-degrees per s on the -90..90 scale, ~118 real deg/s)
action_timeout_s: 0.2 # fw ACTION_TIMEOUT: wrist/finger speeds zero after 0.2 s without a command
# ----------------------------------------------------------------------------------------------- the hand (M3)
# Which hand hangs under the pole. stringman = the firmware gripper above (two fingers geared on one servo): M0-M2 and
# cdpr_wm_g1 were built on it. so101 = the SO-101's wrist_roll + gripper (two STS3215) and its wrist camera, for the
# pick-waste missions with real objects (2026-10-09). load_config(hand=...) overrides; so101 replaces `body`'s gripper
# numbers and `gripper`'s loop numbers with the ones below.
hand: stringman
so101:
# Source: armpilot/third_party/so101/so101_new_calib_camera.xml (onshape-to-robot from the SO-101 CAD) unless noted;
# geometry measured from its meshes in MuJoCo (2026-10-09). Hand frame = the SO-101 gripper link frame: origin on the
# wrist_roll axis at the follower's top face, z up along that axis (= the pole), jaws close along x, camera at +y.
hand_origin_z_m: -0.374 # MEASURE: pole bottom (-0.329) - 45 mm for the roll servo + adapter plate
roll_servo_mass_kg: 0.079 # the SO-101 `wrist` link (roll servo + bracket): stands in for servo + adapter. MEASURE
roll_servo_half: [0.0124, 0.0175, 0.0225] # STS3215 45.2 x 24.7 x 35 mm (datasheet), shaft along the pole
gripper_mass_kg: 0.087 # `gripper` link: gripper servo + fixed jaw (wrist_roll_follower)
gripper_com: [0.000214, 0.000245, -0.025187]
gripper_fullinertia: [2.75087e-05, 4.33657e-05, 3.45059e-05, -3.35241e-07, -5.7352e-06, -5.17847e-08]
jaw_mass_kg: 0.012 # `moving_jaw_so101_v1`
jaw_com: [-0.00157, -0.0193, -0.0300] # its inertial, rotated into the hand frame (jaw link is +90 deg about x)
jaw_diaginertia: [6.61e-06, 5.29e-06, 1.89e-06]
camera_mass_kg: 0.035 # wrist_camera_mount 0.020 + wrist_camera 0.015 (32x32 UVC module)
# gripper servo body (mesh bounds): x -0.015..0.0304, y -0.0193..0.0203, z -0.0358..-0.011
servo_box: {pos: [0.0077, 0.0005, -0.0234], half: [0.0227, 0.0198, 0.0124]}
# fixed jaw finger: inner face x = -0.0079 (the lerobot `gripperframe` site sits on it at z -0.098), tip z -0.1044
fixed_jaw: {x: [-0.0235, -0.0079], y: [-0.008, 0.008], z: [-0.1044, -0.011]}
# moving jaw: hinge at the anchor about hand -y (SO-101 `gripper` joint); finger in the hand frame at q = 0 relative to
# the anchor; inner face 15.8 mm from the fixed jaw's at q = 0, 1.9 mm at the joint's lower limit -10 deg (the pads,
# 1 mm each here, meet there)
jaw_anchor: [0.0202, 0.0188, -0.0234]
moving_jaw: {x: [-0.0123, 0.0045], y: [-0.0266, -0.0110], z: [-0.082, 0.010]}
pad_len_m: 0.030 # bottom 3 cm of each jaw's inner face is the pad
pad_mu: 1.0 # MEASURE: PLA jaws (often taped); Stringman's rubber pads were 1.5
jaw_closed_deg: -10.0 # SO-101 gripper joint lower limit: the pads touch (lerobot 0 = fully closed)
jaw_full_open_deg: 100.0 # joint upper limit: the moving jaw horizontal, tips 10 cm out
jaw_open_deg: 50.0 # closure 0 (MEASURE with the real toys and socks). Tip gap from the meshes
# (2026-10-09): 0 deg 16 mm, 30 deg 53, 50 deg 75, 70 deg 83 (widest), 100 deg 18. Opening
# lifts the moving tip: 50 deg puts it 19 mm above the fixed jaw's (-85 vs -104 mm)
swing_length_m: 0.217 # the twin's pendulum with this hand: period 0.935 s (2026-10-09). MEASURE: polecal
jaw_open_gap_m: 0.0745 # tip gap at jaw_open_deg (the mesh table above): the task centres the object in it
wrist_range_deg: [-157.2, 162.8] # wrist_roll joint limits (-2.7438, 2.8412 rad)
# STS3215. Joint side (damping, armature, friction): the `sts3215` class, a fit adapted from Open Duck Mini (so101
# README). Its kp 998 / kv 2.73 are NOT what the real gripper does: they saturate at 0.17 deg of error, so in the twin
# the load banged +-100 % while closing and read 100 % on every hold (hand_feel 2026-10-09). LeRobot's so_follower
# configure() sets P_Coefficient 16 (default 32) on every servo and, on the gripper, Max_Torque_Limit 500 (50 %),
# Protection_Current 250, Overload_Torque 25. Refit on the REAL gripper holding an 11 mm pen (armpilot runs
# 2026-10-07/08, Present_Load / 10 = % of max torque): squeezed 0.020 rad past contact -> 8.8-10.4 %; told fully
# closed, stopped 0.08 rad short -> 26-28 % held (31-44 % peak while closing); kp ~10 N m/rad fits both (6.8 + friction,
# 27 %). Slow free close (~0.2 rad/s, feel_grasp 2026-10-07): ~4 % load; damping 0.60 predicts 4.3 %, kept.
servo: {stall_Nm: 2.94, damping: 0.60, armature: 0.028, frictionloss: 0.052}
jaw_servo: {kp: 10.0, kv: 0.3, torque_limit_frac: 0.5} # kv: MEASURE (lerobot D_Coefficient 32, units unknown)
wrist_servo: {kp: 10.0, kv: 0.3, torque_limit_frac: 1.0} # MEASURE: same P_Coefficient 16, assumed the jaw's kp
servo_max_dps: 270.0 # MEASURE: STS3215 12 V datasheet 0.222 s / 60 deg no load. The host's ramp on the
# jaw and the wrist (lerobot sends goal positions; the servo moves at its own speed)
camera_pos: [0.0025, 0.0734, 0.0066] # `wrist_camera` link origin; its +z (optical axis) points at the jaw tips
camera_xyaxes: [1, 0, 0, 0, 0.906, -0.424] # MuJoCo looks along -z: z_cam = -(optical axis); 25 deg off straight down
camera_fovy_deg: 70.70 # armpilot calib/wrist.json (640x480, 1.54 px rms, 2026-10)
camera_res: [640, 480]
grasp_local: [-0.0079, 0.0, -0.098] # lerobot `gripperframe`: on the fixed jaw's face. An object of width w sits
# w/2 further along +x: OFF the pole axis (the tasks must rotate it by the wrist)
load_lsb_pct: 0.1 # STS3215 Present_Load: 0.1 % of max torque per count (Feetech memory table, 0-1000)
sim:
timestep_s: 0.004 # plant; checked against 0.002 (fw-sim) in tests/test_scene.py