AI drawing robot: a Waveshare RoArm-M2-S (4-DOF ESP32 arm, ST3215 serial-bus servos) drawing with a felt pen on a vertical bamboo board, driven from a Jetson AGX Thor. Open-loop IK + direction-aware backlash compensation, closed-loop encoder verification, and wrist-cam visual servoing for mark registration.
+x = INTO the paper (pen depth), +y = viewer-LEFT, -y = right, +z = up.
The drawing plane is y–z; x is the pen up/down axis.
~/bin/arm wraps arm.py. Motion commands drive the arm directly (retract-then-move
safe travel); the rest subprocess to the scripts below.
| cmd | what |
|---|---|
arm home |
center at safe clearance, pen up |
arm status |
print pose + servo loads (read-only) |
arm center / arm park / arm retract |
positioning |
arm pen up|down |
lift / lower pen |
arm setup |
hold at reference pose to set the 10 mm standoff |
arm draw [img.png] |
vectorize + draw an image (default: built-in test scene) |
arm preview [img] |
plan preview, no arm motion |
arm square |
compensated test square |
arm star |
servo-registered 5-point star |
arm check |
in-air accuracy / compensation verify |
arm calibrate |
wrist-cam servo calibration (Jacobian + contact pixel) |
arm servo-demo |
localize a dot from several views and re-ink it |
arm stream |
live global-cam MJPEG at http://192.168.1.40:8080/ |
arm photo [wrist|global] |
grab one frame to the scratchpad |
roarm.py—RoArmcontrol class: JSON-over-serial, bounds,move_verified(closed-loop),draw_to/draw_line(backlash-compensated, cartesian-subdivided),pen_up/pen_down,park.strokes.py— image → strokes pipeline: skeleton/contour/edge vectorize → order (2-opt, cut pen-up travel) →CanvasMappx→mm letterbox → execute.draw_image.py— Phase 1 driver (image → plan → draw, with y-mirror fix).servo.py/servo_draw.py— Phase 2 wrist-cam visual servoing (image Jacobian, mark localization, servo-registered star).calibration.json— measured source of truth: reference pose, standoff, depth, workspace, backlash constants, canvas bounds.servo_calib.json— wrist-cam JacobianJ, inverse, contact pixelp0.cam_stream.py,corner_teach.py,setup_standoff.py, plus characterization scripts (undertravel_char.py,compensated_verify.py,contact_visual.py, …).
- Serial:
/dev/ttyUSB0@ 115200. Opening the port resets the ESP32 (arm may move). - Cameras: wrist
/dev/video0, global/dev/video2(Innomaker U20CAM 1080p); heavy barrel distortion, purple cast (black ink reads purple). Wrist cam needs manual exposure (~200) to see ink on white paper. - Under-travel is deterministic/repeatable/speed-independent → software-corrected via
set_backlash()+draw_to(); residual closed viamove_verifiedand vision.
Setup: square the board, run arm setup, dial the pen to a 10 mm standoff, go.