Solo build · HW + SW Open source · MIT Gravely ZT X 52 · 2026

Autonomous
Zero-Turn Mower

A ground-up retrofit that turns a commercial gas zero-turn into a self-driving, iPad-controlled robot. RTK GPS, LiDAR, cameras, and on-device AI — engineered end-to-end across hardware, firmware, control software, and parametric CAD, and reproducible on any zero-turn by re-measuring a handful of dimensions.

±2cmRTK position
13TOPS on-device AI
24Printed parts
25/25Tests green
3Compute units
Interactive 3D · drag to orbit

The full machine, in your browser

Every component is modeled to the datasheet as one parametric assembly. This is the real exported geometry — drag to rotate, scroll to zoom. The retrofit hardware bolts onto a confirmed Gravely ZT X 52 (2021, Kohler), itself spec-matched to the FULL published envelope — 1968 × 1610 × 1039 mm, 1321 mm deck, treaded 20×10-8 drive tyres — with the GRAVELY and ZT X 52 lettering modeled as raised geometry, not a texture. The deck is hollow like the real one: hover the machine and the three 460 mm blades spin on their spindles — and the Phase-3 rig (power bagger, blower/trimmer boom, tow sprayer) rides along; pull the exploded-view slider to see every subsystem fly out.

assembly.glb · 3.39 × 1.36 × 1.80 m · full rig drag · scroll · pinch
loading 3D model…
0% 2.6″
ACCESSORY BAY on a phone: tap the AR icon to stand it on your lawn
tap a label for parts, wiring & order links
Dual RTK GPS mast LiDAR front mast Seat-mounted brain box Front + rear cameras Lap-bar actuators E-stop pedestal + PTO relay
Gravely ZT X 52 zero-turn mower
The base machine — a stock Gravely ZT X 52 before the retrofitphoto: Gravely
System architecture

Three brains, one bus

Not one black box — responsibility is split across compute that's purpose-built for each job, coordinating over MAVLink. A real-time flight controller drives and navigates; a Linux companion sees; a microcontroller closes the analog steering loop the autopilot can't.

The Driver

Pixhawk 6C

ArduPilot Rover · skid-steer
  • Owns drive: throttle-left / throttle-right
  • RTK waypoint missions + geofence
  • Hardware / RC / GCS failsafe chain
  • Fail-to-neutral on any link loss
The Eyes

Raspberry Pi 5 + Hailo-8L

13 TOPS NPU · vision + UI host
  • Camera AI on the Hailo NPU, on-device
  • Fuses LiDAR + ultrasonic → stop verdict
  • Serves one web UI: iPad + on-unit kiosk
  • Uploads coverage missions to the FC
The Hands

ESP32

Lap-bar PID · closed loop
  • Reads FC PWM setpoint + 2 position pots
  • PID-drives both lap-bar actuators
  • Fails actuators to neutral on e-stop
  • The loop the autopilot can't run directly
PixhawkMAVLinkPi 5
Sense
Dual RTK GPS±2cm
360° LiDAR10Hz
2× camerasAI
Overhead sonar1.6m
feeds
Think
Pixhawk 6Cnav
Pi 5 + Hailovision
ESP32PID
commands
Act
Lap-bar L/Rdrive
PTO relayblades
Throttle servoRPM
The control UI · running live

The interface, not a screenshot

This is the actual planned control UI — one web app that serves both the iPad over WiFi and the on-unit touchscreen. It's embedded here running on an in-browser telemetry simulator, so it arms, drives a coverage route, draws the mowed trail, and reacts to live hazards in real time. Tap the controls — they work.

LIVE — simulated telemetry

Tesla-clean, built for a touchscreen

A single status verb tells you the machine's state at a glance — PARKED → READY → MOWING — backed by a top-down mockup that turns red when blades engage and throws an obstacle arc when something's ahead.

Teach & repeat and draw-a-zone → auto-coverage — the planner fills the rows and uploads them as a mission.
Live OpenStreetMap with an RTK heading arrow and a green trail of exactly what's been mowed.
Front + rear AI camera feeds with grass-coverage % and live hazard boxes from the on-device model.
TILT, OVERHEAD & obstacle chips flip to warning/critical and cut the mission before the hazard.
On-screen E-STOP commands HOLD · disarm · blade-off — backing up the physical kill chain.
Precision

Centimeters, not "close enough"

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 fixes heading geometrically, so the machine tracks straight rows from the moment it starts.

±2 cm
Position
RTK-fixed ZED-F9P
~0.4°
Heading
moving baseline
few cm
Cross-track
row-to-row
13 TOPS
On-device AI
Hailo-8L

Teach & Repeat

Drive the path once; it repeats the RTK track. Routes persist on the Pi.

Auto-Coverage

Drive the perimeter; a boustrophedon planner fills the rows and uploads AUTO waypoints.

Overhead Clearance

Upward ultrasonic stops the machine below 1.6 m — it won't drive under low limbs.

Incline Safety

IMU pitch/roll watchdog, hard cutoff past 15°. No mowing across steep grades.

Safety

A layered kill chain

A 52" deck can kill. Safety isn't one switch — it's independent layers that each cut drive and blades, so no single failure leaves the machine moving. Blades stay disconnected until every layer is proven.

Physical e-stop
Normally-closed, wired in series with drive + PTO — cuts power independent of any code
RC kill switch
Independent radio cutoff for drive relay + blade relay
Software interlock
Evaluates incline (15°), overhead (1.6 m) & obstacle every cycle → cuts drive + PTO
Link / signal loss
ArduPilot failsafes hold; ESP32 fails actuators to neutral; PTO relay opens
Wiring · down to the pin

Every pin accounted for

The kill chain gets wired and tested first, then power, then signal. Each branch has its own fuse; every ground returns to one bus. This is the map — verbatim from docs/WIRING.md and the lapbar_controller.ino pin defs.

+12 VGND5 Vsignal / data
Full system wiring diagram — battery, fuse block, bucks, PM02, Pixhawk, kill chain, ESP32, actuators
Complete system wiring — power, the e-stop kill chain, and every signal path. open full size ↗

Power distribution

BranchFuseGaugeFeeds
Battery → fuse block30 A10 AWG tinnedthe whole system
Buck #1 (12→5 V, 5 A)5 A14 AWGRaspberry Pi 5
Buck #2 (12→5 V, 3 A)3 A16 AWGESP32, throttle servo, ultrasonic
PM02 → Pixhawk POWER12 APM02 leadsclean 5 V + battery V/I sense
Drive relay → BTS7960 V+ (×2)10 A14 AWGboth lap-bar actuators
PTO relay → clutchOEMOEM gaugeengages the deck blades

Pixhawk 6C connectors

PortToSignal
POWER1 (6-pin)PM02 → battery5 V in + V/I sense
GPS1 (10-pin UART)simpleRTK2BTX↔RX, RX↔TX, 5V, GND
TELEM2 / USB-CPi 5MAVLink UART (or Pixhawk USB → Pi USB)
RC INFlySky iA6BSBUS — manual override + kill
FMU SERVO1ESP32 GPIO34throttle-LEFT (skid-steer)
FMU SERVO3ESP32 GPIO35throttle-RIGHT
FMU SERVO5throttle servoengine RPM
FMU SERVO6PTO relay coilblade engage (fn = Relay)

ESP32 lap-bar controller — GPIO map

PinNetTo
GPIO34PWM in LPixhawk SERVO1
GPIO35PWM in RPixhawk SERVO3
GPIO36 (VP)pot L wiperLEFT actuator feedback
GPIO39 (VN)pot R wiperRIGHT actuator feedback
GPIO25e-stop senseE-STOP 2nd NC → GND (pull-up)
GPIO16 / 17L_RPWM / L_LPWMBTS7960 #1
GPIO18 / 19L_REN / L_LENBTS7960 #1 enables
GPIO26 / 27R_RPWM / R_LPWMBTS7960 #2
GPIO14 / 12R_REN / R_LENBTS7960 #2 enables
GPIO2status LEDonboard (solid=armed, blink=failsafe)

Actuator feedback pot — PA-14P 6-pin Molex colour code

Yellow+V (3V3)Bluewiper → ESP32 ADCWhiteGNDBlackmotor → BTS7960 M−Redmotor → BTS7960 M+

0–10 kΩ pot: +end → 3V3, wiper → ESP32 ADC, −end → GND. Solder + heat-shrink the motor + pot leads (vibration-critical); connectorize the power branches (XT60) and the Pixhawk JST-GH ports; conformal-coat every PCB + dielectric-grease every gland after testing.

Custom hardware

MowerCarrier — a board for the messy part

The wiring above is a rat's nest to build by hand — and the safety kill-chain is the last thing you want to get wrong twice. So it becomes one 2-layer board: the 30 A power tree, reverse-polarity + load-dump protection, the two 40 A safety relays, and the ESP32 lap-bar controller — every module plugs in by connector. The proven modules (Pixhawk, Pi, buck, BTS7960) stay off-board; the board is the glue, power, and safety. Full design package — schematic, placement, netlist, verified BOM — in hardware/pcb/.

+12 V+5 V+3V3signalGND
MowerCarrier interconnect schematic — power input & protection, fused branches, safety kill chain, ESP32 carrier
Interconnect schematic — power in & protection · fused branches · the hardware-AND-software kill-chain · ESP32 carrier. open full size ↗
MowerCarrier board placement drawing, 120 x 100 mm, 2-layer, 2oz copper
Rev A placement — 120 × 100 mm, 2-layer, 2 oz Cu, M3 mounts, high-current power zone. open full size ↗

The safety gate

Each 40 A relay energises only when the E-STOP is closed (coil high side) and the controller commands it on (coil low side, via a MOSFET). Either one dropping cuts motor power instantly — a hardware button press and a software fault both kill it. A second E-STOP contact signals the ESP32 for fail-to-neutral.

Honest scope

This is a real, fabricable design package — ready to open in KiCad, drop the BOM parts on, and route. The BOM has verified LCSC/JLCPCB part numbers with the tricky bits flagged (P-FET thermals, ESP32 header pitch, load-dump TVS). Rev B ships Gerbers + a one-click JLCPCB order.

CAD & manufacturing

Parametric · print-ready · reproducible

The entire mount system is one OpenSCAD model. Section 1 of params.scad holds the only per-machine measurements — change those and re-render to fit a different zero-turn. Every printable part is bed-fit checked and ships with a baked-in adhesion brim.

SpecValue
Printable parts24, all bed-fit verified
Target printerFlashForge Adventurer 3 · 150 × 150 mm bed
Bed adhesionBrim welded onto every part (slicer brim off)
MaterialASA / PETG — UV + heat tolerant, outdoor
Design toleranceSLOP 0.2 mm · clearance-fit 0.4 mm for FDM ±0.3 mm
Port to another ZTRRe-measure params.scad §1 → re-render → re-slice
All 24 core printed parts — exact bounding boxes (+8 Phase-3 attachment brackets in the repo gallery)
PartBounding box (X × Y × Z)Fits 145 mm
Enclosure foot40.0 × 40.0 × 26.0 mm
Equipment plate140.0 × 140.0 × 38.0 mm
Upper shelf139.3 × 124.0 × 10.0 mm
Lap-bar yoke (lower)127.1 × 46.0 × 10.0 mm
Lap-bar yoke (upper)68.0 × 46.0 × 10.0 mm
Frame-rail anchor (lower)57.0 × 71.1 × 46.0 mm
Frame-rail anchor (upper)57.0 × 28.4 × 46.0 mm
GPS mast clamp A104.0 × 57.6 × 50.0 mm
GPS mast clamp B104.0 × 37.6 × 50.0 mm
GPS antenna plate60.0 × 60.0 × 24.0 mm
LiDAR base A60.0 × 88.8 × 28.4 mm
LiDAR base B64.0 × 88.8 × 34.4 mm
LiDAR mast (lower)82.0 × 82.0 × 112.0 mm
LiDAR mast (upper)82.0 × 82.0 × 112.0 mm
LiDAR top plate123.7 × 123.7 × 14.0 mm
Camera base44.0 × 44.0 × 50.0 mm
Camera cradle31.0 × 50.0 × 30.5 mm
E-stop face58.0 × 58.0 × 4.0 mm
E-stop pedestal A92.8 × 83.1 × 139.1 mm
E-stop pedestal B92.8 × 50.0 × 30.4 mm
Relay box76.0 × 56.0 × 45.0 mm
Relay lid76.0 × 56.0 × 6.0 mm
Throttle-servo bracket56.9 × 44.0 × 42.0 mm
Nameplate badge114.0 × 34.0 × 4.0 mm

Auto-measured by cad/stl/export_stl.sh — every part is bbox-gated ≤ 145 mm on all axes before it ships. Brim-baked copies in stl/brim/.

Software · firmware · parts

Built and tested, end to end

Companion (Python)

  • HTTP + SSE telemetry, JSON control
  • Coverage planner & mission upload
  • Safety interlock + vision loop

Firmware

  • ESP32 lap-bar PID, fail-to-neutral
  • ArduPilot skid-steer params
  • Moving-baseline heading config

Tested

  • 25/25 backend tests green
  • Pure functions for safety & planning
  • Full wiring / pinout diagrams

Every part, with order links

Real, in-stock parts — links verified against live listings, no fabricated SKUs. Two build tiers: functional ≈ $1,644, or precision ≈ $1,812 with the dual-antenna heading kit. Amazon · Adafruit · eBay · vendor-direct where each is cheapest.

Navigation & compute≈ $710 vendor-direct

Pixhawk 6C flight controllerArduPilot Rover — drive, RTK nav, failsafes
simpleRTK2B (u-blox ZED-F9P)±2 cm RTK position board
ANN-MB-00 GNSS antenna (×1–2)IP67 multiband; 2nd = moving-baseline heading
Raspberry Pi 5, 8 GBcompanion computer — vision + UI host
Raspberry Pi AI HAT+ (Hailo-8L)13 TOPS on-device inference
Holybro PM02 power moduleclean 5 V + battery V/I to the FC (required)

Perception & sensors≈ $170

Slamtec RPLidar A1M8360° 2D obstacle ring, ~12 m
Raspberry Pi Camera Module 3 (×2)front obstacle/vision + rear feed
JSN-SR04T waterproof ultrasonicoverhead / tree-limb clearance

Steering, drive & safety≈ $420

Progressive Automations PA-14P (×2)12 V linear actuators, position feedback, IP-rated
BTS7960 (IBT-2) 43 A driver (×2)one dual H-bridge per actuator
ESP32 DevKitclosed-loop lap-bar PID, fail-to-neutral
22 mm IP65 e-stop (NC) + 40 A relaysthe hardware kill chain — wired first

Enclosure, power & wiring≈ $260

IP67 enclosure 221×170×114 mmthe brain box, w/ glands + plate
12-circuit waterproof fuse block + busdistribution, per-branch fusing
Tinned marine wire + silicone hookup10/14 AWG power, 22 AWG signal
SS M3–M6 kit + brass heat-set insertsfasteners for the printed brackets
Raspberry Pi Touch Display 2 (on-unit screen)7″ DSI — the same UI, mounted on the machine

Configure your build

YOUR BUILD $1,644 prices as verified on the order sheet, 2026-06-26

Full itemized order sheet with live prices lives in cart/ORDER.md (machine-readable ORDER-SHEET.csv). ArduSimple ships from the EU; bundle the Holybro and PiShop orders to save on shipping.

Teaching it to see

Building the obstacle brain

Off-the-shelf detectors know "car" and "dog" — they don't know a garden hose, a sprinkler head, or a child's toy left in the grass, and they can't tell you how far. So the perception model is trained for this job: what's in a yard, is it in my path, and how many meters away — small enough to run entirely on the Hailo-8L, no cloud.

INPUT

Front camera

Pi Camera 3 frame, 1536×864, downscaled to a 640×384 tensor at 20 fps.

BACKBONE

MobileNetV3-lite

INT8 feature extractor, chosen to fit the Hailo-8L's 13 TOPS with headroom for two heads.

HEAD 1

Obstacle detection

Yard-specific classes — person, child, pet, toy, hose, sprinkler, generic obstacle + grass.

HEAD 2

Monocular depth

Per-pixel depth, supervised by the LiDAR so a single camera can estimate meters-to-target.

FUSION

Stop verdict

Detections × depth × LiDAR ring × ultrasonic → is a hazard in the stop-zone, and how close.

13 TOPS on-device (Hailo-8L) INT8 quantized, compiled to .hef <30 ms end-to-end, no cloud stop-zone: cx 0.28-0.72, cy ≥ 0.55 LiDAR gives depth ground truth

How the model gets trained

No hand-labeling thousands of images. The machine labels its own data: every teach-and-repeat run records camera frames with a time-synced LiDAR scan, and the LiDAR does the hard part — it already knows where the solid things are.

01 · COLLECT
Drive & record

Teach runs capture front-camera frames + synced 360° LiDAR + IMU pose. Real yards, real light, real clutter.

02 · AUTO-LABEL
LiDAR + big model

The LiDAR ring supplies sparse depth truth; a large open-vocab model pre-labels boxes. A human only spot-corrects.

03 · TRAIN
Two-head fine-tune

Detection head on the yard classes; depth head self-supervised against the LiDAR points. Hard-negative mining on near-misses.

04 · QUANTIZE
INT8 → .hef

Post-training quantization, then the Hailo Dataflow Compiler emits a .hef for the AI HAT+.

05 · SHADOW
Validate on the machine

Runs live but blade-gated, logging every verdict vs the LiDAR. It only gets to stop the deck once precision/recall hold.

Vision is one voice, never the only one — every stop is cross-checked against the LiDAR and ultrasonic, and the blade stays disconnected until the model is proven in the field. See software/companion/vision.py.

The code that runs it

Small, pure, testable

The parts that can hurt someone are pure functions with no hardware in them, so they're unit-tested off the machine — 25/25 green. The safety interlock, the vision verdict, the fail-to-neutral loop, and the MAVLink mission encoder, verbatim from the repo.

companion/safety.pypython
def evaluate(s):
    """(state) -> (allow_move, reason). Single most important blocker,
    checked in strict priority order. Pure + unit-tested."""
    if s.get("estop"):
        return False, "E-STOP"
    slope = slope_of(s.get("roll"), s.get("pitch"))
    if slope > MAX_SLOPE_DEG:               # 15 deg rollover cutoff
        return False, f"too steep ({slope:.0f} deg)"
    oh = s.get("overhead_m")
    if oh is not None and oh < MIN_OVERHEAD_M:
        return False, f"low branch ({oh:.1f} m)"
    if s.get("obstacle"):
        return False, "obstacle ahead - holding"
    return True, None

Layered safety interlock. One function, strict priority: e-stop > slope > overhead > obstacle. Whatever the AI thinks, this has the last word.

companion/vision.pypython
def evaluate(detections):
    """PURE. detections: [{cls, conf, box:[cx,cy,w,h]}...]
    -> (obstacle, range_m, objects, grass_pct)."""
    hazards = [d for d in detections
               if d["cls"] in HAZARD and d["conf"] >= CONF_MIN
               and _in_stop_zone(d["box"])]
    if hazards:
        nearest = max(hazards, key=lambda d: d["box"][1])   # largest cy = closest
        range_m = round((1.0 - nearest["box"][1]) * 6.0 + 0.4, 1)
        return True, range_m, objects, grass_pct
    return False, None, objects, grass_pct

Vision verdict. A hazard only stops the machine if it's in the path stop-zone; range is estimated from where it sits in frame.

firmware/lapbar_controller.inoc++
void loop() {
  bool estop = digitalRead(PIN_ESTOP);          // HIGH = NC opened
  bool lostL = (now - lastEdgeL) > FAILSAFE_MS; // FC signal lost?
  bool fail  = estop || lostL || lostR;
  // fail -> command bars to CENTER, then cut the bridges (coast to neutral)
  int tgtL = fail ? (POT_L_MIN+POT_L_MAX)/2 : targetFromPwm(pulseL);
  if (fail) {
    drive(step(pidL, tgtL, potL));              // PID toward neutral
    if (centered) disableAll();                 // FAIL-TO-NEUTRAL
  }
}

Fail-to-neutral. The ESP32 the autopilot can't be: lose the signal or trip the e-stop and it drives the bars to center, then coasts.

companion/mav.pypython
def to_mission_items(waypoints):
    """[[lat,lon]...] -> MISSION_ITEM_INT (NAV_WAYPOINT).
    lat/lon scaled to 1e7 ints as MAVLink requires. Unit-tested."""
    return [dict(seq=i, command=MAV_CMD_NAV_WAYPOINT,
                 current=1 if i == 0 else 0, autocontinue=1,
                 lat=int(round(lat * 1e7)),
                 lon=int(round(lon * 1e7)), alt=0.0)
            for i, (lat, lon) in enumerate(waypoints)]

Mission upload. Coverage rows become ArduPilot AUTO waypoints — lat/lon scaled to the 1e7 ints MAVLink expects.

How it was built

Engineered with an AI agent loop

A build this broad — mechanical, electrical, firmware, ML, safety — normally takes a team. It was done solo by driving AI agents hard and precisely: decomposing the problem, constraining every prompt, and verifying the output instead of trusting it. The prompt engineering was the multiplier.

Decompose, then parallelize

Split the machine into subsystems with hard interfaces, then ran research agents in parallel — one sourcing RTK, one the actuators, one the safety chain — each with its own acceptance spec.

Constrain to the datasheet

Every CAD prompt carried exact dimensions and a bed-fit rule; parts that overhung the 150 mm printer were rejected by an automated gate, not by eye.

Verify, don't trust

Dead ASINs caught by cart-math, an IP-rating error caught by re-reading the datasheet, XSS caught in review — outputs were adversarially checked before they shipped.

a representative sourcing prompt — constraint + verification, not vibes
You are sourcing the linear actuators for a bladed outdoor robot.
Constraints: 12V, ≥150 lbf, ≥100mm stroke, IP65+, with a
position-feedback potentiometer (closed-loop steering). Verify each
part on its live page - never invent an ASIN or a spec. Return a
table: part, real link, price-read-off-page, and the one spec that
would disqualify it. # flagged the PA-14P: genuine IP54, not IP66 -> needs a rod boot
generating a printable part — parametric + self-checking
Write the LiDAR mount in OpenSCAD, fully parametric off params.scad.
The RPLidar A1M8 base is a teardrop 96.74 x 70.28 mm with a
4-hole trapezoid bolt pattern - NOT 3 holes on a circle
(verify against the datasheet drawing). It must split to fit the
150 mm bed and print without supports. # caught: my first pass
assumed 3 holes on a bolt circle - the datasheet drawing showed a 4-hole trapezoid.
forcing verification into the loop
After every STL export, run export_stl.sh and reject any part whose
bounding box exceeds 145 mm on any axis. Don't eyeball it - fail the build
and tell me which part and by how many mm. # this gate caught the
equipment plate at 179.5 mm and the e-stop pedestal at 145.1 mm before they ever printed.

The pattern throughout: tight constraints in, structured output back, an automated or adversarial check before it counts. That's what let one person carry a multi-subsystem robotics build end-to-end.

Down to the millimetre

Every number that matters

The whole kit is parametric off one file — so it has to be exact. These are the real, sourced dimensions the CAD is built from: the machine, the parts that bolt to it, and the tolerances the printer actually holds. Change SECTION 1 of params.scad and it re-fits any zero-turn.

Adapt it to YOUR mower — enter 6 measurements, get your SECTION 1

The worksheet is docs/MEASURE.md (10 minutes with a caliper). Type your numbers and copy the block into cad/params.scad — all 32 brackets re-generate to fit your machine. Prefilled with the Gravely reference values.


        
      

The machine — Gravely ZT X 52

Overall (Kohler 915256)1968 × 1610 × 1039 mm (L×W×H)
Curb weight615 lb (279 kg)
Cutting deck1321 mm (52″), 11-ga fabricated
Rear drive wheels20×10-8 → Ø 508 mm, 254 mm wide
Front casters11×6-5 → Ø 279 mm
Wheelbase1170 mm (derived from the 1968 mm overall; tape-measure to confirm)
Seat pan height580 mm off ground (no ROPS)
Frame rail tube50.8 mm square
Lap-bar tubeØ 25.4 mm round steel (MIC to confirm)
Lap-bar spacing / throw560 mm centre-centre / 90 mm travel
Powertrain24 hp Kohler 7000 (725 cc) · Hydro-Gear ZT-2200 · 7 mph

Retrofit parts — to the datasheet

PA-14P actuatorretracted 241.6 mm → extended 343.2 mm (100 mm stroke); body Ø 38.1 mm; 6.35 mm (¼″) clevis pin
simpleRTK2B (ZED-F9P)68.58 × 53.34 × 1.6 mm; 3× Ø 3.20 mm M3 (Arduino-Uno footprint)
RPLidar A1M8teardrop body 96.74 × 70.28 mm; Ø 70.04 mm turret; 51 mm tall
Pi Camera Module 325 × 24 mm; 14.5 mm mount-hole pitch
IP67 brain enclosure221 × 170 × 114 mm outer; ~204 × 154 mm mounting plate
GPS mast60 mm tube clamp · 2×100 mm truss · 82 mm flange; dual antennas ≥ 500 mm apart

Print tolerances — what the machine actually holds

Printer build volume150 × 150 × 150 mm (design envelope 145³, 5 mm bed margin)
FDM real-world tolerance± 0.3 mm typical on this class of printer
Design slop / clearance-fitSLOP 0.2 mm · clearance-fit 0.4 mm (holes, slots, mating faces)
Heat-set insertsM3–M5 brass, bores sized per insert datasheet
Every printable partbbox-checked ≤ bed by export_stl.sh; brim welded on
Built with

Standing on real hardware

No black boxes — every subsystem is a known, documented, buy-it-today component. Logos link to the makers; brand marks via logo.dev.

Gravelythe machineRaspberry PiPi 5 + camerasHailo8L · 13 TOPS NPUHolybroPixhawk 6C · PM02ArduPilotRover firmwareu-bloxZED-F9P RTKArduSimplesimpleRTK2BSlamtecRPLidar A1M8EspressifESP32Adafruitparts + guides
The iterative record

Designed in the open — every wrong turn logged

Nothing here was right the first time. The loop is model → render → measure against the spec sheet → find what's wrong → fix → re-export, and every iteration maps to a real commit. The full log — including the constraints A–Z that actually bit — lives in docs/DESIGN-LOG.md.

Two parts didn't fit the printerequipment plate + e-stop pedestal exceeded the 150 mm bed → resized, then an automated bbox gate so it can't regress
Both actuator listings 404'dagent-sourced ASINs rot — every order line re-verified on its live page before it counts
The lidar plate was wrongRPLidar's base is a teardrop with a trapezoid bolt pattern, not a circle — caught by a datasheet pass, plate redesigned
The 3D model was greysplit the CAD into colour groups (red paint / rubber / retrofit metal) and rebuilt the site model multi-material
Machinists can't cut STLOpenSCAD can't export STEP → built a DXF pipeline for flat parts + a mesh-sew STL→STEP converter for the rest
The width was 144 mm shorta 52″ deck's shell is wider than its cutting width — shell 1400 mm + 264 mm deflector = exactly the published 1610 mm
The deck ate the tyresat a 900 mm wheelbase the deck geometrically can't fit between the axles → wheelbase re-derived (1170 mm) from the published 1968 mm overall length
A sphere floated mid-aira rounded-box helper with radius > half a dimension leaves a stray minkowski sphere — found hovering beside the deck in a render
The blades had nowhere to livethe deck hull was solid → hollowed underneath like the real shell; three 460 mm blades on real spindles, spun by a baked glTF animation
Sourcing & fabrication

From files to metal

Every CAD source, drawing, and supplier is researched, verified, and logged — so the build is buildable by anyone. The retrofit parts print in ASA/PETG today; the same files go out to a metal shop when it's time to machine them in aluminium. Full sourcing sheet in docs/SOURCING-AND-FABRICATION.md.

Component CAD — official, free downloads

Pixhawk 6CSTEP ↗simpleRTK2B (ZED-F9P)STEP 8.3MB ↗RPLidar A1M8IGES + 2D ↗PA-14P actuatorSTEP + DWG ↗Raspberry Pi 5STEP ↗Pi Camera 3STEP ↗ESP32-DevKitCDXF ↗Kohler 7000 engineService manual ↗

Base machine — parts diagrams & manuals

No exact ZT X 52 CAD is published, so brackets are modeled from the OEM exploded parts diagrams + measured critical dims (the CAD's SECTION 1 params).

Machined in metal — instant-quote suppliers

Flat parts (e-stop face, relay lid, plates) go out as DXF for laser cutting (cad/dxf/); 3D mounts as STEP solids for CNC (cad/step/, generated from the STLs by stl_to_step.py). General tolerance ISO 2768-m / ±0.005″; anodize for outdoor.

SupplierProcessFilesAlloysBest for
SendCutSend ↗Laser · CNC · bend · tapSTEP / DXF6061·7075·304·316Best all-rounder — flat + CNC, one vendor
OSH Cut ↗Laser + bendDXF / STEP6061·7075·304·316Cheap nested flat/bent (US, 1–2 day)
Protolabs ↗CNC mill/turnSTEP6061·7075·304·316Fastest CNC — 1-day, in-house
Xometry ↗Laser · CNC · weldSTEP / DXF6061·7075·304·316Broad marketplace
eMachineShop ↗CNC + sheetSTEP / DXF6061·7075·304·316No-minimum US one-stop
PCBWay ↗CNC 3/5-axisSTEP6061·304·316Cheap small-qty CNC (China)

Prototype plan: flat brackets as DXF at OSH Cut + 3D mounts as STEP at SendCutSend / Protolabs — or the whole 24-part mixed order through SendCutSend / eMachineShop (US, no minimum).

Fabrication package — ready to send

The full quote package is generated and downloadable: 8 flat parts as DXF, 8 dimensioned drawing PDFs (title block, material, tolerance, finish), and 16 CNC parts as solid STEP — machinable geometry, not raw mesh. Grab everything in one click, or pick individual files, then upload straight into any supplier's instant quoter.

Download complete package — DXF + drawings + STEP (ZIP, 30 MB)
Quote sheetFABRICATION.md ↗ Flat parts8 × DXF ↗ Drawings8 × dimensioned PDF ↗ CNC solids16 × STEP ↗

Ballpark cost (public instant-quote rates, 6061-T6, qty 1, prototype — indicative, not a live quote):

GroupPartsProcessTypical eachGroup est.
Flat / laser8 DXFLaser-cut sheet~$8–30~$120–220
CNC 3-axis16 STEPMilled from billet~$35–120~$700–1,300
Full prototype set (+ ~$29 order min, anodize, shipping)~$900–1,600

Numbers are indicative of published SendCutSend / OSH Cut / Xometry rates — a binding price needs the files run through the supplier's own quoter under an account (a manual, human-in-the-loop step, not automated here). Every file above is upload-ready today.

Phase 3 — the whole rig

One brain, every yard tool

Once the machine holds ±2 cm lines at a controlled speed, every implement inherits that precision. Mowing was the beachhead — the same navigation stack now carries a self-dumping power bagger, a rotating blower + string-trimmer boom, a 30-gal tow sprayer with speed-perfect application, TPMS on all four tyres, and an ignition sequence so the machine starts itself. Design + tested policy layer today, hardware with the build — spec in docs/ATTACHMENTS.md.

Power bagger — dumps itself

Twin rear bins + deck duct (the Gravely Power Bagger / Exmark UltraVac pattern) with the manual lever replaced by an electric dump actuator. Fill is estimated from blade-time × camera grass coverage; the dump cycle is interlocked — stopped, blades off — so fall cleanups become: mow, drive to the pile, dump, resume. All by itself.

Blower + trimmer boom — edging & blowing

A slew-ring boom on the front corner carries a high-CFM blower volute and a string-trimmer head. The trimmer is gated exactly like a blade: armed only, ≤ 1 m/s, killed by the e-stop. Edging passes are just RTK routes along the geofence line with the boom pointed down.

FIMCO 30-gal sprayer — speed-perfect dosing

The 12 V pump follows ground speed: half speed = half flow = identical litres per square metre, and the pump pauses above 25°/s of yaw so headlands never get the double dose that burns herbicide stripes. Tank level, duty and litres applied stream in telemetry.

TPMS + ignition + hour meter

Valve-stem pressure sensors on all four tyres (a low tyre un-levels the deck and waves the cut long before it looks flat). The starter relay cranks the Kohler with temperature-scheduled choke and hard interlocks — PTO off, e-stop clear, ≤5 s crank — and the e-stop grounds the magneto. Engine hours persist; oil and blade service countdowns live on the dash.

The perfect turn — why this machine never pivots

A zero-turn pivot spins one wheel forward, one backward, in place — all of the yaw moment passes through two small contact patches as shear on the turf. Under 615 lb the root mat gives way and you get the classic ZTR divot at every row end. Keep both wheels rolling and the same manoeuvre transmits as rolling friction instead of shear — orders of magnitude gentler. So the coverage planner never pivots:

Row spacing ≥ 2rSmooth U — 90° arc, straight crossover, 90° arc; both wheels roll forward the whole time (the outside wheel just travels further)
Row spacing < 2r3-point K-turn (the tractor headland turn) — 90° arc out, straight reverse of 2r−spacing while the tail swings, 90° arc onto the next row; three gentle segments instead of one violent twist
Headlandsrows are inset by the turn radius so every turn stays inside the geofence; r = 1.2 m — the gentlest arc the ZT-2200 hydros hold accurately
The resultteardrop row-ends on the map instead of black semicircles of dead turf — and a sane drawbar angle when the sprayer is hitched
THE PIVOT (everyone else) torn wheels counter-rotate in place yaw = SHEAR on two patches → divot SMOOTH U (spacing ≥ 2r) both wheels always ROLLING forward rolling friction · zero scrub 3-POINT K (spacing < 2r) rev arc out · straight REVERSE · arc in 3 gentle segments, still no scrub

Implemented + tested: missions.plan_coverage_turns · geometry, headland containment and interlocks in the 45-test suite.

Watch it happen

Build videos — rails ready, camera pending

One short video per build phase lands here as the physical build progresses. The design is done on screen; this section is where it gets real.

Phase 1 — bench brain-box bring-up

RTK fix on the bench · first UI connect — coming with the build

Phase 2 — kill chain + lap bars

meter on the mushroom · bars centering on stands — coming with the build

Phase 3 — first autonomous pass

teach-and-repeat, blades off, open field — coming with the build

Straight answers

The questions everyone asks

Will it really mow unattended?

Not on day one, by design. Blades stay physically disconnected until drive, RTK navigation, and every failsafe is proven — jack stands first, then open-area driving, then supervised mowing. The commissioning sequence is a hard go/no-go checklist in docs/BUILD.md, and the whole build is operator-supervised R&D, not a consumer appliance.

Why not just buy a robot mower?

Consumer robots are electric toys next to a 615 lb commercial zero-turn with a 52″ fabricated deck — different cut quality, different acreage, different machine class. The commercial autonomy options (Greenzie, Scythe) run ~$25k+ or lease-only. This retrofit is ~$1.6–1.8k on top of a mower you already own.

Does it fit my mower?

Any lap-bar zero-turn, in principle. Every machine-specific dimension lives in SECTION 1 of cad/params.scad — measure five things (lap-bar tube, spacing, travel, frame rail, seat) and the brackets re-generate to fit. The reference build is a Gravely ZT X 52.

What does it actually cost?

≈ $1,644 functional, ≈ $1,812 with the dual-antenna heading upgrade — from a live-verified order sheet, not estimates. The configurator above breaks it down; the RTK GPS is the one cost you shouldn't trim.

What happens in rain?

The brain lives in an IP67 enclosure with cable glands; boards get conformal coating; connectors get dielectric grease; the one IP54 weak point (the actuator rod) gets a boot. It's built to live outdoors — but wet-grass mowing is a traction/safety call the incline monitor treats conservatively.

Why open-source something with blades?

Because the safety architecture is the most valuable thing to share: a hardware-AND-software kill chain, fail-to-neutral firmware, and a commissioning sequence that assumes failure. The commercial path (mowing-as-a-service on controlled sites) keeps liability where it belongs — with a trained operator, not a kit customer.

Where this sits in the landscape

TO SCALE — why "open-source mower" usually means a toy THIS RIG — 615 lb · 52″ deck typical robot mower ~25 lb · ~8.7″ cut cutting width 52″ vs ~8.7″ machine weight (turf authority, slopes, towing) 615 vs ~25 lb
Machine classApproachCost characterYou own it?
This retrofit615 lb commercial gas ZTR you already own · 52″ steel deckopen-source ArduPilot retrofit — CAD, firmware, PCB, docs all public≈ $1.6–1.8k in parts on top of your mowerYes — MIT, yours
OpenMower / Ardumowerconsumer robot-mower conversions — light, plastic-deck classopen-source, excellent communityhundreds of dollarsYes
Greenziecommercial mowers via OEM/partner integrationsproprietary retrofit softwareenterprise / fleet pricing (undisclosed)Licensed
Scythe M.52purpose-built electric autonomous 52″proprietary, mowing-as-a-servicepay-per-acre service modelNo — service
Husqvarna CEORAcommercial wire-free robot (EPOS RTK)proprietary systematic robot, small-blade deckpremium commercial, dealer-pricedYes

Characterizations from public positioning as of mid-2026; undisclosed pricing left undisclosed. The honest differentiator: everyone else either converts a toy or sells a closed platform — this puts open autonomy on the class of machine that already cuts real acreage.

Open source & what's next

Build it yourself — or scale it

The whole project is open source under the MIT License — CAD, firmware, control software, the carrier PCB, wiring diagrams, and the build manual. Most open-source mowers convert a toy robot-mower; this retrofits a 615 lb commercial gas zero-turn — which is what makes it reproducible on any ZTR and opens a path toward robot-mowing-as-a-service for commercial grounds and solar-farm vegetation management.

Source repository

Everything: cad/ · software/ · firmware/ · hardware/pcb/ · docs/ · cart/. Clone it, edit one params file, and print. MIT · 45/45 tests · 32 print-ready parts · carrier-board design package.

Open on GitHub Build manual

Build status — in progress

Design, CAD, firmware, control software, and docs are complete and verified — and the machine is currently being built in person. Parts on order, brackets printing, wiring the kill-chain first. A multi-month, safety-gated build, documented as it happens.

Join the build — it's a community thing

Building a different zero-turn? That's the most valuable contribution — a new machine profile makes the whole project more reproducible. Show your build, ask questions, or follow the devlog in GitHub Discussions. This project stands on a great open ecosystem — go star them too.

Discussions Contributing OpenMower ArduPilot Rover Ardumower/Sunray