Self-driving zero-turn mower retrofit — RTK GPS, LiDAR, cameras, on-device AI. Hardware + firmware + control software + parametric CAD.
Loading README and project media…
Most open-source mowers convert a toy robot-mower. This retrofits a 615 lb commercial gas zero-turn.
Parametric CAD, spec-matched to the full published envelope (1968 × 1610 × 1039 mm · deck 1321 mm · tyres 508/279 mm) — still renders · carrier PCB
Turn a seated zero-turn mower into a self-driving, iPad-controlled robot — RTK GPS + LiDAR + cameras + on-device AI — weatherproofed to live outdoors, and reproducible on any ZTR by re-measuring a handful of dimensions.
Status: 🔧 in active build. Design, CAD, firmware, control software, and docs are complete and verified (69/69 tests). The machine is currently being built in person — parts on order, brackets printing, wiring the kill-chain first. This is a multi-month, safety-gated build, documented as it happens — not a plug-and-play kit.
Confirmed reference machine: Gravely ZT X 52 · ~2021 · Kohler 7000 (24 hp, 725 cc) · electric PTO clutch.
🏁 New here? Start with
docs/00-BUILD-MANUAL.md— the end-to-end build sequence.🕹️ Already RC-controlled (tracked / slope mower)?
docs/ADAPT-RC-MOWER.md. The Pixhawk drops in front of the stock drive controller, so you skip the actuators and ESP32.🚜 Phase 3 — the whole rig:
docs/ATTACHMENTS.md— self-dumping power bagger, rotating blower/trimmer boom, FIMCO tow sprayer with speed-proportional dosing, TPMS, self-start ignition + choke, and the no-rut 3-point turn planner (physics inside).📓 How it evolved:
docs/DESIGN-LOG.md— every iteration, every wrong turn, constraints A–Z. What's next:docs/ROADMAP-75.md— 75 planned upgrades. Ordering:cart/ORDER.md+ machine-readablecart/ORDER-SHEET.csv. Building:docs/MEASURE.md(the 10-minute caliper session — do this first) ·docs/PRINT-QUEUE.md(40 parts, grams + hours) ·docs/TOOLS.md·docs/SUBSTITUTIONS.md·docs/CHANGELOG.md.🌐 Interactive writeup: zohebalvi.com/projects/autonomous-mower — 3D model, live control-UI demo, architecture, and the full parts list with order links.
⚠️ A 52″ mower deck can kill. Read
docs/BUILD.md§0 Safety before powering anything. Blades stay disconnected until drive + navigation + every failsafe is proven.
ArduPilot Rover on a Pixhawk 6C owns drive + RTK navigation + the failsafe chain (skid-steer fits a ZTR natively); a Raspberry Pi 5 + Hailo-8L (13 TOPS) rides shotgun for LiDAR obstacle-stop and camera AI, talking to the FC over MAVLink; an ESP32 closes the lap-bar position loop the autopilot can't drive directly. One web UI serves both an iPad over WiFi and an on-unit touchscreen.
┌───────────── SENSE ─────────────┐ ┌────── THINK ──────┐ ┌──── ACT ────┐
Dual RTK GPS (±2 cm, ~0.4° heading) ─────┼──► Pixhawk 6C ────────┼──► lap-bar L/R
360° LiDAR ──────────────┐ │ (ArduPilot Rover) │ (via ESP32)
Front + rear cameras ────┼──► Pi 5 + Hailo├── MAVLink ───────────┤
Overhead ultrasonic ─────┘ (vision/UI) │ ESP32 (lap-bar PID) ├──► PTO relay
└───────────────────────┘ (blades)
| Gravely ZT X 52 (Kohler 915256) | |
|---|---|
| Overall | 1968 × 1610 × 1039 mm (L×W×H) |
| Curb weight | 615 lb (279 kg) |
| Cutting deck | 1321 mm (52″), 11-ga fabricated |
| Rear drive wheels | 20×10-8 → Ø 508 mm, 254 mm wide |
| Front casters | 11×6-5 → Ø 279 mm |
| Seat pan height | 580 mm off ground (no factory ROPS) |
| Frame rail | 50.8 mm square tube |
| Lap-bar tube | Ø 25.4 mm round steel (MIC to confirm), 560 mm spacing, ~90 mm throw |
| Powertrain | 24 hp Kohler 7000 (725 cc) · Hydro-Gear ZT-2200 · 7 mph · electric PTO |
Retrofit parts are modeled to their datasheets — e.g. the PA-14P actuator (retracted 241.6 mm, Ø38.1 mm body, 6.35 mm clevis pin), simpleRTK2B (68.58 × 53.34 mm, Arduino-Uno footprint), RPLidar A1M8 (teardrop 96.74 × 70.28 mm base). See cad/params.scad.
autonomous-mower/
├── docs/
│ ├── 00-BUILD-MANUAL.md ← START HERE — Phase 0–6 build sequence
│ ├── BUILD.md ← full build & SAFETY spec (architecture, training, go/no-go)
│ ├── WIRING.md ← power distribution, kill-chain, per-module pinouts
│ ├── PRINT_GUIDE.md ← per-part orientation, brim, supports, plate batching
│ └── wiring-diagram.svg
├── cad/ ← parametric OpenSCAD — 40 print-ready parts
│ ├── params.scad ← ★ MASTER PARAMETERS — edit SECTION 1 to fit any ZTR
│ ├── assembly.scad ← whole machine; SHOW=all|body|black|retro for multi-material export
│ ├── mower.scad ← parametric mower mock (context)
│ ├── {enclosure,actuator_brackets,gps_mast,lidar_mount,camera_mount,controls_bracket,badge,attachments_brackets,sensor_mounts}.scad
│ ├── export_stl.sh ← export every PRINT_* part + bed-fit gate (rejects > 145 mm)
│ ├── bake_brims.sh ← welds a bed-adhesion brim onto every part
│ ├── render_gallery.sh · render_hero.py ← gallery stills · README orbit GIF + social card
│ └── stl/ · renders/ · vendor/
├── software/
│ ├── companion/ ← Python: app.py (HTTP+SSE), safety.py, vision.py, missions.py, mav.py
│ ├── ui/ ← the control UI (iPad + on-unit kiosk)
│ ├── tests/ ← 69 stdlib tests (safety, geofence, attachments, turns, coverage, keep-outs, vision, mission encoding)
│ └── deploy/ ← systemd service + Chromium kiosk autostart
├── firmware/
│ ├── lapbar_controller/ ← ESP32: FC-PWM → pot → BTS7960 position loop, fail-to-neutral
│ └── ardupilot/ ← rover_params.parm (skid-steer, RTK, geofence, moving-baseline)
├── hardware/pcb/ ← ★ MowerCarrier: power + kill-chain + ESP32 carrier board
│ └── kicad/ ← generated KiCad 9 project (ERC/DRC clean) + Gerbers — read REVIEW.md before fab
│ ├── schematic.svg · layout.svg · render-*.png ← exported from the KiCad project
│ └── README.md · netlist.md · BOM.md · FABRICATION.md
└── cart/ ← ORDER.md (real BOM + links), order.html
cd cad
# 1. Edit params.scad SECTION 1 to your machine — start from a profile in cad/profiles/
# (zt-x-52.scad = the reference build; generic-60in.scad = commercial-class starting point)
# or generate your block interactively: zohebalvi.com/mower → "Adapt it to YOUR mower"
openscad assembly.scad # see the whole machine
./export_stl.sh # export all printable parts + bed-fit check
./bake_brims.sh # weld brims for adhesion
./render_gallery.sh # machine stills; python3 render_hero.py re-shoots the orbit GIF (needs Pillow)
# slice cad/stl/brim/*.stl in FlashPrint for the Adventurer 3 (ASA or PETG)
./scripts/setup-dev.sh # uv + Python 3.12 .venv + OpenSCAD (+ --kicad: KiCad, Freerouting, arduino-cli)
./scripts/check.sh --full --sitl # every gate: tests, mypy, CAD asserts, renders, docs, SITL end-to-end
python3 software/companion/app.py --sim --port 8080
# open http://localhost:8080 — arm, draw a zone, run a coverage mission in simulation
python3 software/tests/test_backend.py # 69/69
./scripts/sitl.sh # the REAL ArduPilot Rover 4.7.1 firmware (SITL) behind the same UI
firmware/lapbar_controller/lapbar_controller.ino (Arduino IDE / arduino-cli).firmware/ardupilot/rover_params.parm in Mission Planner (skid-steer + RTK + failsafes; moving-baseline block commented for the dual-antenna heading upgrade).Teach-and-repeat — drive the path once, it repeats the RTK track.
Boundary → auto-coverage — drive the perimeter, the planner fills it with rows and uploads them as an ArduPilot AUTO mission. It handles real yards: L/U shapes (cell decomposition, routed transits), rows along the direction with the fewest turns, keep-outs (beds, trees, the shed — or spots the sonar keeps hitting), no-rut turns with perimeter laps for the headland, and shows m², minutes and % covered before it moves.
AI learns the yard — the camera/LiDAR model refines a semantic map over runs (Hailo).
The upgrade that matters is dual-antenna moving-baseline heading. One RTK antenna gives a great position but a noisy heading at a standstill; a second antenna on a ≥ 500 mm baseline fixes heading geometrically (~0.4°), turning ~10 cm cross-track into a few cm. Config in firmware/ardupilot/rover_params.parm.
| Position | ±2 cm RTK-fixed (u-blox ZED-F9P) |
| Heading | ~0.4° moving-baseline (dual antenna) |
| Cross-track | few cm, row to row |
| On-device AI | 13 TOPS (Hailo-8L), INT8, < 30 ms, no cloud |
No single failure leaves the machine moving. Blades stay disconnected until every layer is proven.
safety.py) — evaluates incline (15° cutoff), overhead (1.6 m), and obstacle every cycle; priority e-stop > slope > overhead > obstacle.Full pinout + kill-chain wiring in docs/WIRING.md.
Real, in-stock parts — verified against live listings, no fabricated SKUs. Two tiers: functional ≈ $1,644, or precision ≈ $1,812 with the dual-antenna heading kit. Full itemized sheet with links in cart/ORDER.md.
Open cad/params.scad → SECTION 1, re-measure the lap bars / frame / seat, re-render, re-slice. That's the whole port — the mounts, brackets, and masts regenerate around your numbers. Toro, Bad Boy, Spartan, Ferris, Scag, etc.
Design + CAD + firmware + control software + docs are complete and verified (49/49 tests). The build is in progress in person. Remaining work is hardware-gated: SITL/on-FC validation of the MAVLink handshake, training the Hailo .hef model, and the future fleet/RaaS layer. See docs/BUILD.md §12.
This project stands on the shoulders of a great open community — go star these too:
Adapting to another zero-turn and open a PR with your params.scad SECTION 1.hardware/pcb/: a routed, ERC/DRC-clean KiCad 9 project generated from one Python file, with Gerbers ready for review (REVIEW.md first). Remix it for your machine.CONTRIBUTING.md for the safety-gated workflow. Good first issues are labeled good first issue.Open source under the MIT License. ⚠️ This drives a machine with large blades that can kill — it's released for education/research; you are solely responsible for safe construction, testing, supervision, and local-law compliance. Keep blades disconnected until every failsafe is proven.
A personal robotics project by Zoheb Alvi.