Horizon: ~2 days
Platform: Marco (SO-101 on differential-drive trike, Dora-first)
Goal: Show a live AR overlay demo that proves technical depth, product taste, and direct fit for a farm-weeding robot — while Rowve finishes first-pass co-founder calls.
Shortly after you shipped your first ACT policy on SO-101, you connected on X with another builder who started his own SO-101 project. You encouraged him; you followed each other. He grew a large audience as a marketing-forward extrovert while you were quieter on the build side.
You showed him Marco a couple of months ago. He has since mounted his SO-101 on a tank-like base — very similar to Marco — aimed at weeding on farms. Improving agriculture with robots is a personal fantasy of yours, so you reached out about joining. He said he is looking for co-founders. The project is brand new; this week he is talking to many people.
Marco’s body and SO-101 are unchanged. As of 2026-08-19 the tank treads are gone: two large rear wheels on the same servos plus an unpowered pivoting caster. Same differential kinematics (in-place pivot still works); less slip so wheel odom can support mapping.
You pitched what you can bring:
- Dora-first stack instead of ROS (lighter, AI-friendly dataflows — already Marco’s direction)
- Phone AR overlays so an operator sees live what the robot plans to do and how it is labeling the world
- Concrete weeding effector thinking: lasers, steam, snippers, and pulse electrocution (root damage, low energy) — with the shared conclusion that the winner must balance low energy with quick and effective kill
This README is the execution plan for the next couple of days: a demo strong enough that when he finishes those calls, you are not another “interested engineer” — you are the person who already showed the product vision on hardware.
- You already have a working Dora robot stack on a differential + SO-101 form factor like his (Marco is now a trike; Rowve’s weeder is tank-like).
- You can make the robot’s intent visible and legible to a human via AR — critical for trust in field robotics.
- You can wire perception → labeled world → planned motion → operator UI quickly.
- Your ideas (AR, Dora, weeding effector tradeoffs, laser-as-proxy for “energy on target”) map directly onto Rowve’s farm use case.
Primary demo (must ship): Phone (or tablet) AR view of Marco planning a trip to a named place (“master bedroom”) and heading that way down the hallway — with the rear MARCO AprilTag nameplate (tag36h11 id 532) for AR pose.
Secondary demo (strongly desired): OAK-D object detection (shoe, chair, etc.) with AR cues (arrow + label from robot toward object; bounding boxes only if angles allow).
Stretch / top-of-funnel wow: Laser-on-target as a stop-and-align kill loop (LiteWeed), not “track while rolling” as v1. Detect a vegetation stand-in → halt → aim → timed beam → re-look. Creep-while-holding-beam is a bonus clip only. Prior art: docs/research/WEEDING_PRIOR_ART.md.
Prefer web AR over a native Android app for day 1–2 speed. You already have AR web-app experience to reuse.
┌─────────────────────┐ Dora graphs ┌──────────────────────┐
│ Marco (Pi 5 LeBot) │◄────────────────────►│ Phone / laptop web │
│ drive + odom │ HTTPS / JSON │ AR overlay UI │
│ map + named places │ │ (Three.js + WASM) │
│ OAK-D + arbiter │ │ AprilTag 36h11 │
│ intent / plan │ │ location buttons │
└─────────────────────┘ └──────────────────────┘
Operator flow (MVP):
- Point phone at the MARCO AprilTag nameplate on the rear plate → AR world locks to robot.
- Tap a location button (e.g. Master Bedroom) → robot plans/drives toward that labeled map pose.
- AR draws a path / hallway arrow showing intent (“heading to Master Bedroom”).
- If OAK-D sees a chair/shoe, AR shows an arrow + label from the robot toward that detection (box optional).
Why buttons first: Named goals beat free-form speech for a reliable 2-day demo. LLM routing can return later.
Coded on the Orange Pi (2026-08-12), validated live on Marco (2026-08-13) with Tailscale HTTPS, then YOLO arrows + object avoid on hardware (2026-08-15).
- Rear nameplate
tag36h11id 532, 133.5 mm;robot_T_tag+ tread anchors inconfigs/ar_marker.json - Versioned
GET /state(marco.state.v1) with measured/cmd wheel speeds + kinematictrack_width_m - Phone UI: AprilTag WASM worker + Three.js speed labels (cm/s) at mid-tread + 2.5 s instantaneous path
- Safe controls:
POST /actiontyped API, command IDs, mode/lease, STOP; tank dead-man unchanged; graph muxes wall-avoid through the HTTP arbiter - Room landmarks generated: sofa id 101, bathroom door id 201 (print A4 100%; measure after)
- Rsync to Marco; live tag lock on phone over Tailscale HTTPS on LeBot
- OAK-D YOLO nano detections on
/state+ AR arrows (label, confidence %, depth) in the path plane - Phone tabs: AR / OAK-D JPEG + YOLO boxes / Wrist JPEG (~5 fps); camera auto-starts on HTTPS
- OAK-D YOLO nano detections on
/state+ AR arrows (label, confidence %, depth) in the path plane - Object-cone avoid live (same graph as heading-balance); 3 m cap via
MAX_PATH_M=3.00 - Marker hallway graph live (sofa 101 @ 120 cm → right arc → bath 201 @ 110 cm); homing uses image bearing so the tag stays centered in the OAK-D
- Named-place go-to + intent label (
gotostill rejected)
Local preview: python3 robots/so101/scripts/serve_ar_local.py → http://127.0.0.1:8787/?demo=1
Next-dev prompt: HANDOFF_MAPPING.md. Locked cals: robots/so101/docs/DRIVE_CAL.md. Rowve narrative prompt: HANDOFF_ROWVE.md.
DRY_RUN=0 WALL_AVOID_ENABLE=1 MAX_PATH_M=3.00 on Marco. Graph detect=on, motors live. Path ~2.38 m then AVOID_PERSON / AVOID_BACKPACK (couch also in view): creep + yaw away, no ram. Operator: worked great. Sit-still: DRY_RUN=1 WALL_AVOID_ENABLE=0. Do not pkill -f "dora run" over SSH.
HUD is graph / tag / motion / Play / STOP plus AR / OAK-D / Wrist tabs. OAK JPEG from the wall-avoid DepthAI process; wrist JPEG via ffmpeg (not OpenCV V4L). Shoulder OAK points straight forward (camera_yaw_deg=0 in oakd_detect.json, shared with object-avoid). Hard-refresh the phone after graph restart.
Same Marco body + SO-101. Tank treads removed. Two large rear wheels on servos 7 / 8, unpowered front caster that pivots freely. Software still tank_*. Goal: less variable slip so odom is useful for mapping.
- Physical wheel radius is essentially the same as the old tread sprocket → start
wheel_radius_m=0.052235(banded LOCKED) and run the same linear 2 m (--distance-m 2.0 --x 0.1875). - Pass 1: odom 2.0010 m, tape 208.2 cm, yaw -0.02°, L/R delta -0.2 mm. New radius 0.054349. No steer trim in code (yaw-hold was the old left-drift compensator).
- Pass 2:
--no-yaw-hold. Odom 2.0039 m, tape 201.7 cm, yaw +0.79°, body ~5 cm right. New radius 0.054704. - Pass 3: radius 0.054704,
--no-yaw-hold. Odom 2.0001 m, tape 201.2 cm, yaw +0.81°. New radius 0.055029. - Pass 4: 360° at carpet
0.304899. Odom +360.12°, body ~495° (overshot 135°, no slip). New track 0.221819. - Pass 5: 360° at
0.221819. Odom +360.01°, body ~363°. New track 0.219992. - Pass 6: 360° nailed at
0.219992. LOCKED. - Pass 7: 20 cm/s. Odom 1.9970 m, tape 199.2 cm. Cruise LOCKED 0.20.
- Pass 8: accel 0.32. Odom 1.9941 m, tape 199.8 cm (2 mm shy). Radius + cruise 0.20 + accel 0.32 locked. Track later recailed to 0.22612 (skid 360). Full table:
robots/so101/docs/DRIVE_CAL.md. Next:HANDOFF_MAPPING.md. - Carpet profile is still the tread-era lock; do not use it until recaled.
dora-so101left stopped after the distance script.
Aqua rubber band on each of 32 treads per side. Hard-floor default is this profile:
- Bare-tread (superseded): radius 0.055721, track 0.422672
- LOCKED:
wheel_radius_m=0.052235,track_width_m=0.441318 - Linear:
--distance-m 2.0 --x 0.1875(0.1875is m/s). Pass 1 tape 188 cm → new radius. Pass 2 odom 2.0039 m, tape exact 200 cm. - Fast 360 skipped left reverse sprockets (~5×); track from 15° short estimate (not a clean spin). Do not repeat that pivot for cal.
Same day — YOLO avoid + Rowve video: heading-balance + object cone at 10 cm/s, then +30% (vx_mps=0.13, vx_creep_mps=0.104). Both worked great; operator captured a video. Speeds left at +30% in wall_avoid.json. MAX_PATH_M=3.00.
Wheels: skid track_width_m=0.22612 LOCKED; one-wheel 0.26029; ICC close 1.021 @ 12 cm, 1.050 @ 24 cm (different radii need different scales). Table: docs/DRIVE_CAL.md.
AprilTag size-range is face-on only. Yaw-in-place vs sofa 101: at ~22° bearing, size-range dropped ~36 cm (AABB grew) while stereo-at-tag moved ~10 cm; PnP collapsed to 0.34 m. Face-on, stereo was ~38 cm farther than size-range. Tag-odom loop “30 cm closer” was that AABB lie; operator: robot was back at the start, ~18° CCW overshoot from --skid-extra-deg 8. Occupancy now inflates hits 20 cm and skips stamps when |image_bearing| > 15°.
Graph tank_marker_hall_local.yml (no YOLO). Sofa 101 at 120 cm → 45 cm right arc → wall-avoid until bathroom 201 → turn at 110 cm, 90° left arc. Distance from tag size in the image (PnP once said 0.47 m across the room). Homing steers to center the tag in the OAK-D, not body-frame bearing_deg. Lost-tag search follows the last-seen side. Runner: DRY_RUN=0 IDLE_STOP=0 ./robots/so101/scripts/run_tank_marker_hall_local.sh.
Tag-anchored 2D map beside the hallway graph. First live paint used a tall 16–62% depth band (looked over the sofa; 3k wall cells). Mapping now uses a camera-height slice (46–54%, median per column), stamps only while a room tag is visible and nearly face-on (stamp_max_abs_bearing_deg 15), and inflates hits 20 cm. Node occupancy-mapper. GET /map + phone Map tab.
- Found Marco on LAN (
LeBot@192.168.1.140); SSH key auth from this Orange Pi (ssh marco) - Confirmed Pi Dora stack already installed (
dora 0.5.0, DepthAI, Feetech, teleop service, OAK-D + wrist cam) - Tank wheels 7 / 8 driven without leader arm; signs locked left=-1, right=+1 (same servos now drive the trike rear wheels)
- Surface profiles: hard floor = DEFAULT (
tank_drive.json); carpet LOCKED (tank_drive_carpet.json: radius 0.051111, track 0.304899, within a few ° of 360) - Carpet 2 m exact + carpet pivot locked; hard-floor profile preserved for usual house travel
- Handoff prompt written:
HANDOFF_ROWVE.md - OAK-D live — DepthAI headless smoke + Dora depth graph on Pi
- Wall / solid-surface avoid — heading-balance (ahead L/R of center) + Dora wrap; doc:
robots/so101/docs/WALL_AVOID.md - Dora wrap —
wall_cue+wall_avoid_controller+tank_driver(surface select, yaw-hold option, dead-man, pose) +robot_state_http(:8787/state); graphtank_wall_avoid_local.yml - Heading-balance NAILED — which wall the nose aims at; sweet-spot yaw; no yaw when front clear
Wall avoid findings (2026-08-11): yaw-hold alone drifts ~6 cm/1.5 m. Early sticky side-strip auto flipped / over-rotated into the opposite wall. Fix: compare depth just left vs right of image center (estimate_heading_balance); yaw away from nearer side; CLEAR when open ahead and balanced. Hallway 1.80 m retest corrected L then R correctly. Gains: yaw_imbalance_kp=42, yaw_max_degps=40, front_clear_m=1.45, deadband=0.10. Dora dry-run publishes pose + status over HTTP.
Frozen configs: robots/so101/configs/tank_drive*.json, wall_avoid.json
Test scripts: robots/so101/scripts/test_tank_*.py, test_wall_avoid.py, run_tank_wall_avoid_local.sh
- Voltage: leader 5V (7.4V motors); follower/tank 12V.
- Carpet ≠ hard floor. Default = hard floor. Carpet/high-grip: smaller radius + track. Hard-floor track on carpet overshot ~140° past 360° before carpet track lock. Recail both after the trike conversion.
- Contact slip / surface dominates. Treads (even banded) made odom a short-horizon prior. Trike is the attempt to make wheel ticks trustworthy for mapping.
- Tracks ≠ ideal wheels. ~2″ front→rear tread motion is gone; caster trail remains. Effective radius is still empirical per surface.
- Pivots slide centimeters on hard floor with treads; map frames walk if only wheels integrated. Recail
track_widthon the trike after linear radius. - Rowve demo: wheel odom = short-horizon prior until trike recail is locked; AR/named places follow vision-corrected pose.
- No short time-caps on distance runs; stop on odom (safety timeout only).
- Stop
dora-so101teleop when doing wheel-only tests (waits for leader USB). - Depth ≠ wheel gap — calibrate with tape; don’t treat slant range as chassis clearance.
- Heading = ahead L/R imbalance — sticky side strips caused same-direction over-rotate; CLEAR when open ahead and balanced.
- AprilTag mounted —
tag36h11id 532, 133.5 mm; mid-tread 6 cm outside black side edge; velocity 4 cm forward (configs/ar_marker.json) - Room tags ready to print — sofa 101, bathroom door 201 (
assets/ar_marker/room_*_A4.pdf) - Named places — waypoint JSON + button go-to (drive once, save pose from
/state) - Web AR client — speed + path + STOP in
robots/so101/web-ar/ - Apply to Marco — rsync + Tailscale Serve on LeBot + live lock id 532
- OAK-D object detect arrows — YOLO nano in the wall-avoid graph; object-cone avoid live 2026-08-15
- Stretch: laser-on-target
- Row follow (next): bottles as crop rows — in-the-moment corridor, not occupancy. Prompt:
HANDOFF_ROW_FOLLOW.md
Mapping: Paused 2026-08-22. Live hallway occupancy was not usable (sofa gap filled, hall heading lie, drifting bath tag). Write-up: robots/so101/docs/MAPPING_POSTMORTEM.md. Retry only after a 6-DoF IMU. Stereo stays for avoid / range-to-object.
See copy-paste prompt: HANDOFF_ROWVE.md. Wall avoid details: robots/so101/docs/WALL_AVOID.md.
Hardware and baseline — do this first so Day 1 is not blocked.
- Power base + arm bus; confirm leader/follower serial by-id paths if teleop needed
- Confirm tank drive motors addressable (IDs 7 / 8 on follower bus)
- Wrist cam + OAK-D Lite on USB3; DepthAI opens on Pi
- Print/affix the MARCO AprilTag nameplate on the rear white plate (
robots/so101/assets/ar_marker/) - Phone and Pi on same Wi‑Fi; Pi IP 192.168.1.140 (
ssh marco) - Dora on Pi smoke-checked (
dora 0.5.0)
Known starting point from the archived Dora state:
- Arm teleop + calibration graphs exist
- OAK-D color/depth/RGBD Dora nodes + scripts exist
- Perception LLM camera router exists (useful later; not required for location buttons)
- Tank drive/odom scripts now exist; Dora-wrapped
cmd_vel+ mapping still the remaining gap vs old ROS scaffolding inrobots/so101/docs/*_OLD_ROS.md
Outcome: Soft cmd_vel-style control and a believable odom frame for short indoor runs.
- Differential-drive tank commands (left/right wheel goals) — prototype scripts on Pi
- Odom distance from wheel feedback; tune signs + wheel radius on open floor
- Yaw-hold from wheel-odom heading error (critical — equal cmds alone drift)
- Tune
track_widthvia pivot test (hard floor + carpet profiles locked) - Wrap into Dora node + dead-man / stop on lost heartbeat
- Manual drive smoke test: forward / stop (pivot next)
Success: From SSH or a tiny local UI, Marco drives and pose roughly matches taped floor marks over a few meters.
Outcome: A map (even crude) with named poses and a go-to that looks intentional in the hallway.
Pragmatic options (pick the fastest that works):
- Pose graph of waypoints (fastest): drive once, save named poses (
master_bedroom,hallway,living_room) in a JSON map; go-to = path of waypoints + simple controller. - Lightweight occupancy / SLAM if time allows after waypoints work.
- Record / store map frame + named places
- Implement
go_to_place(name)→ publishes plan state:{goal, path_hints, status} - Expose plan + pose over HTTP or WebSocket for the AR client (JSON is enough)
- Location buttons in a minimal web page (even without AR yet): tap → robot moves
Success: Tap Master Bedroom → Marco starts down the hallway toward that place; phone/laptop shows live status text at minimum.
If drive + named go-to works, you have a co-founder story even before AR polish. Do not skip this gate for prettier overlays.
Outcome: Phone camera + marker → AR content anchored to Marco showing planned motion.
- Stand up a small web app (vanilla JS + Three.js + AprilTag WASM in
robots/so101/web-ar/; no MindAR) - Track
tag36h11id 532 (worker +robot_T_tag); preview mode without camera - Subscribe to Pi pose via
GET /state(poll; WS later) - Draw: left/right tread speed labels + 2.5 s instantaneous path (straight/arc/pivot)
- Draw: Goal label (“Master Bedroom”) + named-place path
- Keep UI sparse: brand/demo clarity over dashboard clutter
Success: Standing behind Marco in the hallway, AR clearly shows it is planning / heading to the master bedroom.
Outcome: Live “I see a chair that way” without requiring perfect 3D boxes.
- Run object detection on OAK-D stream (on-device YOLO/DepthAI model or Pi-side detector — whichever installs cleanest today)
- Emit detections:
{label, confidence, bearing_or_pixel, optional depth} - AR: arrow from robot toward detection + label (“chair”, “shoe”)
- Skip tight 3D boxes unless depth + extrinsic math is already behaving; arrows are enough for the pitch
Success: Point phone at robot; when OAK-D sees a chair, AR shows a labeled arrow in that direction. Live 2026-08-15; yaw retuned same day.
Maps to farm weeding (LiteWeed stop-and-align, not continuous tracking). Full write-up: docs/research/WEEDING_PRIOR_ART.md.
- Co-mount a small laser pointer with the targeting camera (wrist preferred; safe class; eye safety)
- Digital on/off from Dora (GPIO / relay / USB switch)
- Indoor “weed” = green blob (ExHSV/ExG) or a green stand-in on the floor — not a COCO chair
- Closed loop: detect → halt base → aim to blob centroid → laser on for dwell
f(blob area)→ re-look - Bonus clip only: creep while holding the beam (harder; not v1 success)
Success: Video of stop → aim → timed beam on the stand-in; AR showing candidate + dwell. Bonus: short creep clip.
Shared product question with Rowve: best weed kill that is low energy and quick/effective.
Read first: docs/research/WEEDING_PRIOR_ART.md — OpenWeedLocator (colour + YOLO → zone actuate) and LiteWeed (~$500, 4 W 450 nm, stop-and-align, 30–60 s dwell).
| Option | Pros | Cons / risks |
|---|---|---|
| Laser | Precise, no soil contact, demoable; LiteWeed shows 4 W / stop / ~30–60 s kills early pigweed/purslane/nutsedge at ~96% | Throughput (seconds per weed), eye safety, overburn to crop/soil, regulations |
| Steam | Non-chemical | Energy/water, slow, bulky |
| Snippers / mechanical | Familiar, physical certainty | Complexity, wear, miss roots, soil disturbance |
| Pulse electrocution | Low energy story; can damage roots | Safety, soil moisture variability, compliance |
| OWL-style spot spray | Cheapest detect→GPIO loop, proven in fallow | Still herbicide; not our chemical-free story |
Demo strategy: Indoor proxy is stop-and-align + dwell from blob size (LiteWeed control law), with AR showing the candidate and planned seconds. Creep-while-firing is optional wow, not the success bar. Colour ExHSV/ExG on a down-looking camera (wrist) is the weed stand-in — not COCO chairs. Pulse / mechanical stay in the talk track because LiteWeed’s dwell is too slow for high-density hectares/hour.
Perception after the hallway demo: vegetation gate (OWL) → optional crop-mask hybrid in-row → tiny plant detector only with our camera height. Forward OAK-D stays drive/avoid; weeding looks at the ground.
- Form factor match: “Same differential + SO-101 family as your weeder — here’s Marco live (trike wheels now; same kinematics).”
- Dora-first: Show one graph / dataflow; contrast with heavy ROS bringup for iteration speed.
- AR trust layer: Phone AR — named goal, hallway intent, detection arrows.
- Farm bridge: “Same stack: map rows → vegetation/weed labels → AR veto → stop-and-align energy on target.” OWL already does cheap detect→actuate; LiteWeed already does $500 laser stop-and-burn; we add trust + manipulator + Dora.
- Laser clip (if ready): Stop, aim, dwell from size; path to pulse / mechanical if throughput needs it.
- Ask: Co-founder seat focused on robot stack, perception, and operator UX (AR).
| Piece | Location |
|---|---|
| Dora SO-101 package | robots/so101/ |
| Teleop graph | robots/so101/graphs/mono_teleop_real_so101.yml |
| AprilTag nameplate (print) | robots/so101/assets/ar_marker/ |
| Room tags (sofa 101, bath 201) | robots/so101/assets/ar_marker/room_*_A4.pdf + configs/ar_room_markers.json |
| AR operator UI | robots/so101/web-ar/ + docs/AR_OPERATOR.md |
| Tank/trike forward / distance / rotate tests | robots/so101/scripts/test_tank_*.py |
| OAK-D / wrist / YOLO graphs + scripts | robots/so101/graphs/, robots/so101/scripts/ |
| Perception LLM router (optional later) | robots/so101/graphs/perception_llm_router_local.yml |
| Old ROS tank / Nav2 notes (reference only) | robots/so101/docs/NEXT_STEPS_OLD_ROS.md |
| Weeding prior art (OWL + LiteWeed) | docs/research/WEEDING_PRIOR_ART.md |
| Env | Pi: ~/Marco-Dora/dora-lerobot/.venv + LeRobot ~/LeRobot/le-robot/.venv for bus tests |
Likely new work for this sprint (to create as we go):
- Named-place map store + go-to planner/controller
- Phone HTTPS (Tailscale Serve on Marco) for live camera lock
- Optional laser GPIO node + stop-and-align detect graph (ExHSV blob, not COCO)
| Risk | Mitigation |
|---|---|
| Tank odom not in Dora yet | Waypoint map + short hallway; don’t need perfect Nav2. Trike recail is to make that odom less slippy. |
| AR pose noisy | Large marker, good lighting, stand close behind robot |
| Full 3D boxes hard | Arrows + labels only |
| Detection flaky | Few indoor classes; high confidence threshold; staged props |
| Scope creep | Ship named go-to + AR path first; laser is stretch |
| Wrong network / latency | Same Wi‑Fi, LAN only, no cloud for the live demo |
Must have
- Marco drives under Dora with usable short-range odom
- At least one named place (
master_bedroom) and button/go-to that starts hallway motion - Web AR locked to robot marker showing goal + direction/path intent live
Should have
- OAK-D detections with AR arrow + label for ≥1 object class
Nice to have
- Laser on/off from software (co-mounted with targeting cam)
- Stop-and-align dwell on a green stand-in; optional creep clip
When those boxes are checked, the conversation is no longer “I have ideas” — it is “here is the product layer your weeder will need, running on a twin of your robot.”