Blog / Reference

reference

The Build Doc

build-doc · Living document — the current source of truth. Episodes are snapshots; this stays current.

Neato D10 → ROS 2 Robot (Home Assistant Controlled)

A “brain transplant” project: gutting a bricked Neato D10 and rebuilding it as an open, locally-controlled robot.

Living document — last updated 2026-08-05. Findings below are confirmed from a hands-on teardown unless marked TBD (needs measurement) or ? (unconfirmed).


TL;DR

Neato went bust and Vorwerk killed the cloud in Q4 2025. The D10 still vacuums on a button press but is otherwise a dumb brick — no app, no scheduling, no integration.

⚠️ Premise correction (2026-08-04): this doc originally claimed the D10 is “bare-metal, no OS, nothing to root.” A later teardown step proved that wrong. Lifting the RF shield revealed a full NXP i.MX 8M Nano SoloLite (MIMX8MN1DVTJZAA) Linux computer — the BGA SoC (1× Cortex-A53 + 1× Cortex-M7, no GPU/NPU) plus NANYA DDR3, a Kingston 4 GB eMMC, and the matched NXP PCA9450 PMIC — sitting alongside the LPC51U68. The robot is a two-brain design: the i.MX 8M runs Linux (the real brain); the LPC51U68 is the real-time body controller (motors/encoders/sensors). So there is an OS here.

The other wall still stands — and it’s the decisive one. Research (2026-08-04) confirms the D8/D9/D10 boot chain is locked, with no public root path:

  • OpenNeato supports D3–D7 only and explicitly excludes the D8/D9/D10 — citing a “different board” and a “password-locked serial port.” That’s the very project that would have rooted these if it were feasible.
  • Neato firmware is encrypted/signed, decrypted at boot from secure key storage → on this i.MX that’s HABv4 secure boot (SoC fuses hold the key hash; unsigned images are rejected).

Decision — reuse ruled out, transplant confirmed. Even though there’s a real Linux brain in there, you’d face a password-locked console and a signed-boot wall with no known bypass — and the SoC is only a single-A53 SoloLite anyway (the Raspberry Pi 4 already on hand, quad A72, is far stronger for the intended ROS 2 + Nav2 + slam_toolbox stack). So: discard Neato’s electronics, keep the mechanically good and electrically standard parts (chassis, motors, LiDAR, battery, sensors), and drop in a Pi 4 + ESP32 running ROS 2, exposed to Home Assistant as an MQTT vacuum.

The transplant was always the right call — originally justified by “no OS to root” (wrong: there is a Linux SoC) but ultimately correct because of the locked boot chain (right). End goal unchanged: an open, locally-controlled ROS 2 robot driven from Home Assistant.

Sources: OpenNeato (renjfk) — D3–D7 scope, D8–D10 excluded · OpenNeato user guide — debug port pinout RX/3.3V/TX/GND · NXP i.MX 8M HABv4 secure boot guide

The single best discovery from the teardown: the LiDAR is a Neato LDS, the most reverse-engineered laser scanner in hobby robotics. That de-risks the hardest part of the whole project.


Component Inventory

Everything found inside, what happens to it, and why. Signal direction is relative to the component (IN = power/commands arriving; OUT = data leaving). Connector labels are the silkscreen names where known.

Salvaged (keep)

ComponentVerdictPowerConn INConn OUTRole in new build / notes
Chassis, wheels, casterKeep———Physical platform. Host the Pi + ESP32 here once the old board is out.
Drive wheel motors ×2 (brushed DC + encoder)Keep260-0016, 14.4 V nominal; stall ~2.1 A (L) / ~2.4 A (R), measured 2026-08-04Motor power from driver (JST to old board)Encoder pulses (channel count TBD)Locomotion + odometry. Wire to an H-bridge; read encoders on the ESP32. Motor terminals = the two chunky solder posts flanking the encoder disc.
Wheel encoders ×2 (on the wheel motors)KeepLogic supply TBD (3.3 or 5 V)Encoder Vcc/GNDA + B pulses (quadrature)LEGO WHEEL ENCODER ASY: 915-1055 REV, board marked STD-3. Disc is solid, not slotted → magnetic (ring magnet + Hall), not optical. Dirt-tolerant, which suits a vacuum. Harness CONFIRMED 2026-08-05: 6 wires = 2 thick power + 4 encoder = quadrature (Vcc/GND/A/B), direction-aware — good for odometry + slam_toolbox. Sensing only — carries no motor power.
Roller brush motor (brushed DC)Keep (optional)905-0460-RoHS 14.4VDC (batch 215I31); stall ~7.6 A (winding R ~1.9 Ω, measured 2026-08-05)Motor power (JST)Hall tacho (Vcc/GND/signal)Needed only if the robot should actually vacuum. Harness = 2 thick power + 3 thin Hall tacho → free RPM / jam detection into an ESP32 GPIO. Drive via Cytron MD13S (bidirectional → auto-unjam possible in software). See Motor Current + BoM.
Side brush motor (brushed DC)Keep (optional)Small can, 2-wire; stall ~0.5–0.7 A (winding R 20–30 Ω, measured 2026-08-05)Motor power (2-wire, soldered to tabs)—Front-corner sweeper — flicks debris toward the suction. 2 wires + EMI cap, no sensor, spins one way. Was missing from the original “four motors” inventory; found 2026-08-05. Drive via logic-level MOSFET module + flyback diode. Photo brush-motor.jpg.
Blower / vacuum motorKeep (optional)EVERFLOW F121225BU (AFX19bR) — DC14.4V 2.0AMP, dated 2021-08-1314.4 V + PWM inTach out (RPM)Brushless blower with integrated driver — needs NO H-bridge. 4 wires CONFIRMED. Feed it 14.4 V, PWM it straight from an ESP32 GPIO. See Blower section.
LiDAR — Neato LDS 2.2 (290-1044 REV 4)Keep (star part)Logic 5 V (~45 mA idle / ~135 mA spinning); TX/RX at 3.3 V; spin motor separateJ2 MAIN (5 V + 3.3 V UART), J3 MOTOR (host-driven PWM)J2 MAIN: 3.3 V UART 8N1 @ 115200 — distance packetsThe reason this project is feasible. Read on Pi/ESP32; drive spin motor in a closed loop off the reported RPM. See LiDAR section below.
Battery — Li-ion 14.4 V 4S2PKeep12 V (empty) → 16.8 V (full); 6200 mAh / 89 WhDock charge (was via old board)6-pin JST: power + thermistor/sensePowers everything through a buck converter. Map the 6 pins before use — only 2 are main +/−; others are temp/sense.
Front bumper switches ×4KeepYou supply reference V (no logic-level concern)GPIO pull-upDigital contact closureBUMP SWITCH 290-0056 REV.8 — mechanical tactile click-switches, 4 across the front bumper. Read on ESP32 GPIO + internal pull-up, active-low, ~5 ms debounce. Confirmed NO 2026-08-06 (beeps when pressed) → LOW = hit. Photo front-bumper-switch.jpg.
Wheel-drop / lift switch (“dead-man’s”)KeepYou supply reference VGPIO pull-upDigital contact closureDT-08 lever microswitch (3A 125VAC) in the wheel arch — detects a drive wheel dropping (robot lifted / over an edge). Typically one per drive wheel. Same GPIO+pull-up as bumpers. Confirmed NO 2026-08-06 — mechanism: on the ground the wheel is held up (lever open/HIGH); lifting lets the wheel fall, which presses the lever (closed/LOW) → LOW = wheel dropped / robot lifted → stop drive. Same active-low polarity as bumpers. Photo wheel-arch-dead-mans-switch.jpg.
Cliff / drop sensors (IR)Keep (safety-critical)~3.3 V (0–3 V analog swing per prior art → native ADC, no level shifter)5-wire JST ZH 1.5 mm; two sensors gang → 10-pin at old boardAnalog reflectance → ESP32 ADCLOUIE DRP SENSOR 290-1023 REV 2 (© 2017) — downward IR reflectance eye, discrete IR emitter + phototransistor (two clear windows); stops it driving off stairs. Host-driven: emitter is strobed/modulated and the phototransistor sampled synchronously for ambient rejection — do the same in firmware; not a clean high/low, threshold in software. Wire convention (prior art): black = GND, red = Vcc; brown/yellow/green = emitter-drive + signal (which-is-which → quick functional test at Step 4). ≥2 confirmed (gang-of-2); full count pending full undercarriage removal. A static DC diode test is defeated by an on-board decoupling cap — don’t bother; power it and strobe. Photos under-carriage-sensor.jpg, cliff-sensor-front.jpg, cliff-sensor-connector.jpg, cliff-sensor-harness-cut-tails.jpg.
Side / wall sensor (IR)KeepSame as cliff — ~3.3 V analogSame LOUIE DRP 290-1023 boardAnalog reflectance → ESP32 ADCConfirmed 2026-08-06: the side/wall sensor is the same LOUIE DRP 290-1023 part as the cliff sensor (silkscreen matches) — identical wiring, 3.3 V analog, and firmware strobe/sample. Overrides the forum “Sharp GP2Y0A51 wall sensor” lead — that Sharp module does not apply to this D10. One sensor type covers both cliff + wall.
Rear speakerKeep (nice-to-have)via amp——Small speaker at the back. Drive with a class-D mono amp (PAM8302-class) fed from an ESP32 DAC pin for audio cues (stuck/docked/bin-full). No build dependency.

Discarded

ComponentVerdictPowerConn INConn OUTWhy discarded
Mainboard (520-0394 Rev.B) — NXP i.MX 8M Linux SoC + LPC51U68 MCUDiscard (reuse ruled out — locked boot chain)14.4 V in; regulates restAll harness JSTs + USB-C—Correction: not bare-metal — carries a full i.MX 8M Nano SoloLite Linux computer (DDR3 + 4 GB eMMC + PCA9450 PMIC) beside the LPC51U68 body controller. Reuse investigated and rejected: D8–D10 boot chain is locked (password-locked console + signed/HAB secure boot; OpenNeato excludes these models). Replaced by Pi 4 + ESP32.
WiFi/BT module (under perforated RF shield)Discard3.3 V (from board)On-board + U.FL antenna lead—Only ever talked to Neato’s dead cloud. The Pi has its own WiFi.
Button / UI board (neato, TP1–TP22)Discard5 V6-pin JST to mainboardButton presses / LED statusSits under the power + reset buttons. Pi/ESP32 handle any controls you want. TP1–TP22 are just factory test pads.
USB-C “SW Update” service portDiscard (with mainboard)———Vendor firmware-update channel; useless without Neato’s signed images.

Target Architecture

Three layers, clean separation of concerns.

1. Home Assistant — scheduling + notifications

  • Robot appears as HA’s built-in MQTT Vacuum entity: start / stop / return-to-base commands, plus status (docked / cleaning / error / battery).
  • All scheduling is normal HA automation (“weekdays 09:00 → start”). No scheduling logic on the robot.
  • HA subscribes to the robot’s state topic to react (“if error → notify”).
  • MQTT, not Zigbee. Zigbee is for low-power battery sensors and is the wrong tool for a WiFi robot. Publishing/subscribing topics is MQTT — the Pi speaks it natively over WiFi.

2. Raspberry Pi 4 — the brain

  • ROS 2 with Nav2 (path planning + coverage) and slam_toolbox (mapping + localization from LiDAR + odometry).
  • One small bridge node (~100 lines Python): subscribes to the MQTT command topic → issues ROS cleaning goals; publishes status (battery / stuck / done / lost) back to MQTT.

3. ESP32 — real-time motor + sensor co-processor

  • Linux isn’t real-time, so timing-critical work lives here: PWM to motor drivers, counting encoder pulses, polling bumper/cliff/wall sensors, reading battery voltage.
  • Connects to the Pi over USB serial. Runs micro-ROS so it joins the ROS 2 graph directly — the Pi sees sensors/encoders as native ROS topics.

Status reporting (derived once sensor data flows)

StatusHow it’s derived
StuckMotors commanded, but encoders show no movement
Low batteryBattery voltage below threshold → return-to-base / notify
FinishedNav2 coverage complete → publish docked/done
Lostslam_toolbox localization confidence drops (least clean signal — localization loss is a known headache)

The LiDAR (Neato LDS 2.2)

The make-or-break component, and it landed on the easy side.

  • What it is: a Neato Laser Distance Sensor (“Piccolo”/LDS family) — the same lineage as the famous XV-11 scanner, reverse-engineered by the community since ~2010.
  • Interface: one data line, 8N1 UART, 3.3 V logic, 115200 baud — consistent across every LDS version. Feeds straight into a Pi/ESP32 UART.
  • Two connectors: J2 MAIN (5 V power + serial data) and J3 MOTOR (spin motor). The host drives the motor itself via PWM, closing the loop on the RPM the LDS reports in its own packets to hold a steady spin.
  • Existing code to adapt (not write from scratch): C++ Raspberry Pi classes that read coordinates and run the motor loop, Python packet decoders, and ROS drivers (xv_11_laser_driver, neato packages). Packet format is documented (header, index, speed, distance readings, checksum).
  • Chip side: an LM393 comparator (U1) — the classic LDS laser-detection circuit, confirming the standard design.
  • Only open item: this is the newer LDS 2.2 (2019); most reference code targets the ~2010 XV-11. Same architecture, so expect minor adaptation at most. First job when the logic analyzer arrives: clip onto J2’s data line, capture, and confirm baud + packet layout against the documented format.

The Blower (EVERFLOW F121225BU) — no driver required

Label reads MODEL: F121225BU (AFX19bR) / DC14.4V 2.0AMP / 2021 08 13. Two things fall out of that:

  • Its current is printed on it: 2.0 A. No measurement needed. This was expected to be the hardest number to capture and it turned out to be free.
  • It is almost certainly a 4-wire brushless PWM blower, not a brushed DC motor. In Everflow’s naming the …BU suffix marks their 4-wire PWM family (their catalogue F126025BU and F129025BU are both PWM parts), while the otherwise-similar …BL variant is 3-wire tach-only. Four wires are visible on the harness: black, red, yellow, blue.

Consequence: the planned BTS7960 for the blower is deleted. A brushless fan carries its own commutation electronics, so:

  • Wiring is black = GND, red = +14.4 V, yellow = tach out, blue = PWM in (standard 4-wire fan convention — verify before trusting).
  • Speed control is a PWM signal direct from an ESP32 GPIO. ~25 kHz is the usual fan PWM frequency.
  • The tach line is a free RPM feedback signal — useful for detecting a clogged or jammed blower.
  • Do not put an H-bridge on it. Reversing a brushless fan just confuses its internal controller.
  • Do not measure its winding resistance. You’d be probing driver electronics, not a winding; the reading is meaningless.

Open item: confirm the wire count is 4 (not 2). Four confirms all of the above; two would mean it’s a plain brushed motor after all and a MOSFET/BTS7960 comes back on the list.


Motor Current Status

MotorRating sourceCurrentDriver decision
Blower/vacuumPrinted on label2.0 A✅ None needed — PWM direct from ESP32
Roller brushWinding resistance (2026-08-05)~7.6 A stall (R ~1.9 Ω) — 3× the wheels✅ Cytron MD13S (bidirectional, 13 A / 30 A peak)
Side brushWinding resistance (2026-08-05)~0.5–0.7 A stall (R 20–30 Ω)✅ logic-level MOSFET module (+ flyback diode)
Drive wheel ×2Winding resistance (2026-08-04)~2.1 A (L) / ~2.4 A (R) — matched pair, size to ~2.5 A✅ DRV8871 ×2 — TB6612 out (stall > its ~1.2 A continuous)

Method: rather than trying to catch a millisecond inrush spike on a 2000-count meter, derive stall current from winding resistance — stall ≈ 14.4 V ÷ R. No power applied, no risk. See neato-d10-measuring-motor-current.md. All 5 motors now measured — no current gates remain.


Bill of Materials

Already on hand

Raspberry Pi 4 · soldering iron + solder · multimeter · breadboard · Arduino (e.g. Elegoo starter kit — bench part-testing only, not the final co-processor).

Ordered

ItemPurposeSpec / confirmed partQty
ESP32 WROOM-32 (USB-C)Real-time motor/sensor co-processor (micro-ROS)Dual-core, 4 MB flash, CH340C2
8-ch logic analyzerDecode LDS 2.2 packets + encoder signals24 MHz clone + PulseView (sigrok)1
Heat-shrink assortmentWiring insulation2:1, assorted diameters1
Dupont jumper wiresBench prototypingM-M / M-F / F-F1 set
DRV8871 driverDrive the 2 wheel motorsAdafruit ADA3190 (6.5–45 V, 3.6 A peak, 2×GPIO). Ordered 2026-08-05.2
Cytron MD13S driverDrive the roller brushPi Hut “13A 6V-30V DC Motor Driver” SKU 106189 (MD13S = MD10C successor; bidirectional, 3.3/5 V logic, PWM+DIR, 20 kHz). Ordered 2026-08-05.1
Gravity MOSFET moduleDrive the side brushDFRobot Gravity MOSFET Power Controller (3.3–10 V logic trigger, 5–36 V/20 A; ⚠️ 1 kHz max switching = on/off only; ⚠️ add flyback diode). Ordered 2026-08-05.1

Sourcing elsewhere (spec locked, not yet ordered)

ItemPurposeSpec / search termNote
5 V rail (UBEC)14.4 V battery → 5 V for the Pi7 A switching UBEC (2–7S / 5.5–35 V in, 7 A continuous, 5.25 V ±0.5 V out, 300 kHz, ~92% eff., over-temp + reverse-polarity protection). ~£6–8 eBay/AliExpress. Meter to ~5.25 V before wiring the Pi.Chosen for thermal headroom (loafs at ~3.5 A load → cool, which the sealed chassis needs). ⚠️ ships from China (long lead) — order early. Rejected: 3 A modules (brown out Pi 4), XL4015 (non-sync, hot; bench-only), Pololu D36V50F5 (correct but £38).
Speaker ampDrive the rear speakerClass-D mono, 5 V, drives 4 Ω+, single-ended analog input (ESP32 DAC). Reference: Adafruit PAM8302 (ADA5647).Optional; no build dependency.

Hold until measured (post-teardown specifics)

ItemPurposeSpec / search termBlocked on
Blower driverDrive the vacuum motorREMOVED — blower is a brushless PWM fan with its own driver. Optionally a small high-side MOSFET switch for hard on/off.✅ Resolved
JST connector kitTap harnesses without cuttingCliff/side sensor connector = JST ZH 1.5 mm, 5-pin (measured 6 mm pin1→pin5 span, 2026-08-06). Cliff harness was cut instead — sensor keeps its native mated plug as a permanent pigtail, so no kit needed there. Kit still useful for encoder/tacho/battery plugs (pitches TBD).Encoder/tacho/battery plug pitches still to measure
Logic level shifter5 V ↔ 3.3 V, only if a kept sensor/encoder/tacho is 5 V logicBidirectional moduleCliff/side sensors resolved: ~3.3 V, no shifter needed. Still confirm encoders + roller tacho (3.3 vs 5 V).
T10 Torx driver (long reach)Remaining recessed case screwsLong, slim shaft — T10 Torx screwdriver long reach precisionOptional but likely useful
Standoffs / mountsMount Pi + ESP32 in the chassisSizes TBDMeasure free space

Software (all free)

ROS 2 (Humble+) · Nav2 · slam_toolbox · micro-ROS · Mosquitto (or HA’s broker) · Home Assistant + MQTT integration.


Build Order

Sequenced so there are working milestones early and the hard nav work last.

  1. ESP32 + one motor driver → drive a single wheel from a serial command. Proves the toolchain; instant feedback.
  2. Add encoders → confirm distance-travelled readings (odometry foundation).
  3. LiDAR bring-up → power the LDS, drive its spin motor, capture J2 with the logic analyzer, confirm/adapt the packet decoder. (De-risked, but still the highest-value integration.)
  4. Wire the safety sensors → bump switches ×4 + wheel-drop on GPIOs; cliff sensors on ADC (after counting + metering Vcc). Cliff safety before the robot roams.
  5. Bare-bones MQTT bridge → HA sends start, robot just drives forward. Proves the whole HA → Pi → ESP32 → motor pipeline end-to-end before SLAM exists.
  6. slam_toolbox + Nav2 → mapping, localization, coverage. The 80%. Expect real time in Nav2 config tuning — documented, big community, but fiddly.

Open Risks

ItemStatus
LiDAR reverse-engineeringResolved — confirmed Neato LDS 2.2, standard interface, existing drivers. Only confirm LDS 2.2 packet format vs XV-11 with the analyzer.
Nav2 tuningThe real time-sink. Powerful but fiddly; everyone hits this wall.
“Lost” detectionLocalization-loss is the least clean status signal to detect reliably.
Blower current + driverResolved — 2.0 A on the label; brushless PWM fan needs no driver at all. Confirm 4-wire count.
Motor current drawRESOLVED — all 5 motors measured. Wheels ~2.1/2.4 A → DRV8871 ×2; roller ~7.6 A → Cytron MD13S; side brush ~0.7 A → MOSFET module; blower 2.0 A (label) → no driver. Parts vetted + ordered 2026-08-05.
Encoder channel countRESOLVED 2026-08-05 — 6 harness wires = quadrature, direction-aware. Good for odometry + slam_toolbox.
Cliff-sensor count + VccLargely resolved 2026-08-06 — Vcc ~3.3 V (0–3 V analog per prior art → native ADC, no shifter); ≥2 sensors (gang-of-2 into a 10-pin); side/wall sensor is the same LOUIE DRP 290-1023 part. Emitter is host-strobed, not static (a DC diode test is swamped by an on-board cap). Residual: full sensor count (pending full undercarriage) + which of brown/yellow/green is emitter-drive vs signal (quick functional test at Step 4).
Encoder / roller-tacho supply voltageTBD — 3.3 V or 5 V; determines whether a level shifter is needed on those signal lines.
Bump / wheel-drop switch logicResolved — mechanical contact closure, GPIO + pull-up; no logic-level risk.
Battery 6-pin pinoutTBD — identify power vs thermistor/sense before connecting.
Power rail⚠️ a 3 A 5 V rail browns out a Pi 4 under Nav2 (reboots / SD corruption) → buck must be 5 A continuous.
Internal mounting spaceTBD — measure once the old board is out.

References