Community project

Automatic Spray Cleaning Robot

Photo of Automatic Spray Cleaning Robot
Generated with AI

Gaming Purposes

Published November 3, 2026

This automatic spray cleaning robot combines an ESP32 web controller with an Arduino Uno motion controller to create a WiFi-enabled cleaning platform. The robot uses ultrasonic distance sensors to navigate autonomously, dual DC motors for movement, and a 12V submersible pump triggered by a relay module to spray cleaning solution.

The guide provides a complete wiring diagram connecting the L298N motor driver, HC-SR04 sensors, relay module, and power distribution through buck converters. Builders will receive the full firmware for both the ESP32 (web server and command interface) and Arduino Uno (motor and sensor control), plus step-by-step assembly instructions covering battery pack configuration, power supply setup, and safe testing procedures before autonomous operation.

Wiring diagram

Loading diagram…

Gather all the parts

QtyComponent
1

Standard L298N dual H-bridge motor driver module

A dual motor driver board that switches the left and right TT wheel motors from the battery pack.

1

HC-SR04

HC-SR04 ultrasonic distance sensor module with separate 40 kHz transmitter and receiver transducers, measuring 2 to 400 cm. A 10 us pulse on TRIG starts a measurement and ECHO stays high for the echo's round-trip time. This HC-SR04+ board runs from 3.3 V or 5 V.

1

HC-SR04

HC-SR04 ultrasonic distance sensor module with separate 40 kHz transmitter and receiver transducers, measuring 2 to 400 cm. A 10 us pulse on TRIG starts a measurement and ECHO stays high for the echo's round-trip time. This HC-SR04+ board runs from 3.3 V or 5 V.

1

HC-SR04

HC-SR04 ultrasonic distance sensor module with separate 40 kHz transmitter and receiver transducers, measuring 2 to 400 cm. A 10 us pulse on TRIG starts a measurement and ECHO stays high for the echo's round-trip time. This HC-SR04+ board runs from 3.3 V or 5 V.

1

12V DC Submersible Mini Water Pump

Two-wire brushed DC submersible water pump for small irrigation or circulation projects. It is an external load and must be driven through a relay, MOSFET, or motor driver; do not connect directly to an MCU GPIO.

1

MP1584 Buck Converter

adjusted to 5.0 V

Adjustable buck (step-down) DC-DC converter, 4.5-28 V in -> 0.8-20 V out, ~3 A. Configured to 5 V to step a 9 V / 12 V supply or battery pack down to the board's 5 V rail.

1

18650 Li-ion Cell

Molicel 2600 mAh

18650 lithium-ion cell, nominal 3.7 V, ~2500 mAh. Common for higher-capacity portable / battery-bank style projects; needs a holder and protection / charger circuit.

1

18650 Li-ion Cell

Molicel 2600 mAh

18650 lithium-ion cell, nominal 3.7 V, ~2500 mAh. Common for higher-capacity portable / battery-bank style projects; needs a holder and protection / charger circuit.

1

18650 Li-ion Cell

Molicel 2600 mAh

18650 lithium-ion cell, nominal 3.7 V, ~2500 mAh. Common for higher-capacity portable / battery-bank style projects; needs a holder and protection / charger circuit.

1

12 V TT gear motor — left wheel

The small geared DC motor that turns the robot's left wheel.

1

12 V TT gear motor — right wheel

The small geared DC motor that turns the robot's right wheel.

1

KY-019 5 V single-channel relay module

A small 5 V relay board that lets the Arduino turn the 12 V water pump on and off.

1

1 kΩ resistor for Arduino-to-ESP32 signal

1 kΩ

The upper resistor that reduces the Arduino's 5 V serial signal before it reaches the ESP32.

1

2 kΩ resistor for ESP32 serial signal

2 kΩ

The lower resistor in the divider that makes the Arduino status signal safe for the ESP32 receive pin.

1

Arduino Uno robot controller

The Arduino board that keeps running the motors, pump, and ultrasonic sensors while receiving Wi-Fi commands from the ESP32.

Assemble it in 8 steps

1. Keep the electronics dry

Mount the Arduino, ESP32 expansion board, L298N motor driver, MP1584 buck converter, relay, and pump where water cannot splash onto them. Keep the small MP1584 adjustment screw easy to reach.

  • Water touching the boards can short the battery and damage the electronics.

2. Make the three-cell battery pack

Use a proper 3-cell 18650 holder or a ready-made 3S pack; do not solder directly to loose cells. Put cell_1, cell_2, and cell_3 in series: cell_1 positive to cell_2 negative, then cell_2 positive to cell_3 negative. The free cell_1 negative is the pack negative; the free cell_3 positive is the pack positive.

  • Without a BMS, the cells have no protection against being drained too far, being overcharged, or a short circuit. Stop using the pack when its total voltage reaches 9.0 V, charge it only with a balanced 3S lithium charger, and disconnect it immediately if a cell or wire gets warm.
  • A 3-cell lithium pack can deliver enough current to melt thin wires or start a fire during a short circuit. Add an inline fuse close to the pack positive before normal use.

3. Set the 5 V buck supply

With the battery disconnected from every other module, connect the pack positive to buck_5v VIN (input power) and pack negative to buck_5v GND (ground). Connect the battery briefly and use a meter to adjust VOUT to exactly 5.0 V. Disconnect the battery again after setting it.

  • More than 5 V on the Arduino 5V pin, ESP32 5V/VIN pin, sensors, or relay can damage them.

4. Wire the Arduino and three distance sensors

Connect buck_5v VOUT to Arduino 5V (power) and buck_5v GND to Arduino GND (ground). For each sensor, connect VCC to the 5 V supply (power) and GND to the shared ground (ground). Front-left: TRIG to A0 (signal), ECHO to A1 (signal). Front: TRIG to A2 (signal), ECHO to A3 (signal). Front-right: TRIG to A4 (signal), ECHO to A5 (signal).

  • Do not join any TRIG or ECHO wires together; each sensor needs its own two signal wires.
  • Do not use the Arduino 3.3 V pin for these sensors.

5. Wire the ESP32 from the 5 V buck output

Do not use the expansion-board DC barrel jack. Connect esp32_wifi 5V/VIN to buck_5v VOUT (power) and ESP32 GND to the shared ground (ground). Connect ESP32 TX0/GPIO1 to Arduino D2 (Wi-Fi command signal). Connect Arduino D3 to the 1 kΩ resistor A end (signal); join the 1 kΩ resistor B end to ESP32 RX0/GPIO3 and the 2 kΩ resistor A end; connect the 2 kΩ resistor B end to shared ground. The two resistors reduce Arduino's 5 V reply signal to a safe ESP32 level.

  • Keep the ESP32 USB socket reachable because it needs its own Wi-Fi controller firmware flashed separately from the Arduino.
  • Never connect Arduino D3 directly to ESP32 RX0/GPIO3; the Arduino 5 V signal can damage the ESP32 input.
  • Never connect the 3-cell battery directly to ESP32 5V/VIN or to its barrel jack in this version.

6. Wire the L298N wheel driver

Remove the small 5V-EN jumper from the L298N. Connect buck_5v VOUT to L298N 5V (logic power), buck_5v GND to L298N GND (ground), and pack positive to L298N 12V (motor power). Leave ENA and ENB jumpers fitted. Connect IN1 to Arduino D6 (left wheel direction), IN2 to D9 (left wheel direction), IN3 to D11 (right wheel direction), and IN4 to D10 (right wheel direction). Connect the left motor to OUT1 and OUT2; connect the right motor to OUT3 and OUT4.

  • If one wheel turns the wrong way, disconnect the battery and swap only that motor's two wires.
  • Keep the battery disconnected while tightening terminals; a loose copper strand can short the battery.

7. Wire the pump relay

Connect relay VCC to buck_5v VOUT (power), relay GND to the shared ground (ground), and relay IN to Arduino D4 (control). Connect pack positive to relay COM (pump power), relay NO to pump PUMP+ (switched power), and pump PUMP- to pack negative/shared ground (ground).

  • Never connect the pump directly to an Arduino pin.
  • The relay is the pump switch; the 3-cell battery wires must stay on COM, NO, and the pump power wires only.

8. Test safely before driving

Raise both wheels off the floor and keep the pump inlet in water before a long test. Plug the Arduino into USB and press Deploy, then connect the battery briefly. In automatic mode the pump runs for 5 seconds and rests for 5 seconds. The ESP32 commands are A for automatic mode; F, B, L, R for manual movement; S to stop; and P to switch the pump state.

  • Disconnect the battery immediately if any battery cell, wire, buck converter, relay, L298N, ESP32, or Arduino gets warm.
  • Do not charge the three loose series cells through the MP1584; it is not a lithium battery charger.

Review all connections

1. Connections between "cell_1" and "ESP32"

Functioncell_1ESP32
groundGND → 3-cell battery pack negative and common returnGND
power+V → 18650 Li-ion Cell GNDEXT

2. Connections between "cell_2" and "ESP32"

Functioncell_2ESP32
power+V → 18650 Li-ion Cell GNDEXT

3. Connections between "cell_3" and "ESP32"

Functioncell_3ESP32
power+V → MP1584 Buck Converter VINEXT
power+V → Standard L298N dual H-bridge motor driver module 12VEXT
power+V → KY-019 5 V single-channel relay module COMEXT

4. Connections between "buck_5v" and "ESP32"

Functionbuck_5vESP32
powerVOUT → regulated 5 V logic supply5V
groundGND → shared power returnGND

5. Connections between "sonar_front" and "ESP32"

Functionsonar_frontESP32
powerVCC → sensor power5V
groundGND → sensor groundGND

6. Connections between "sonar_left" and "ESP32"

Functionsonar_leftESP32
powerVCC → sensor power5V
groundGND → sensor groundGND

7. Connections between "sonar_right" and "ESP32"

Functionsonar_rightESP32
powerVCC → sensor power5V
groundGND → sensor groundGND

8. Connections between "pump_relay" and "ESP32"

Functionpump_relayESP32
powerVCC → relay power5V
groundGND → relay groundGND
powerNO → 12V DC Submersible Mini Water Pump PUMP+EXT

9. Connections between "pump" and "ESP32"

FunctionpumpESP32
groundPUMP- → pump ground returnGND

10. Connections between "motor_driver" and "ESP32"

Functionmotor_driverESP32
groundGND → motor driver groundGND
power5V → motor-driver logic power; remove its 5V-EN jumper5V
powerOUT1 → 12 V TT gear motor — left wheel M+EXT
powerOUT2 → 12 V TT gear motor — left wheel M-EXT
powerOUT3 → 12 V TT gear motor — right wheel M+EXT
powerOUT4 → 12 V TT gear motor — right wheel M-EXT

11. Connections between "uart_divider_top" and "ESP32"

Functionuart_divider_topESP32
digitalB → 2 kΩ resistor for ESP32 serial signal AEXT

12. Connections between "uart_divider_bottom" and "ESP32"

Functionuart_divider_bottomESP32
groundB → divider ground returnGND
digitalA → 3.3 V-safe status signal to ESP32 RXD/GPIO3GPIO 3

13. Connections between "arduino_uno_controller" and "ESP32"

Functionarduino_uno_controllerESP32
power5V → 5 V logic power5V
groundGND → common groundGND
digitalD2 RX → ESP32 TXD/GPIO1 Wi-Fi command signalGPIO 1
digitalD3 TX → 1 kΩ resistor for Arduino-to-ESP32 signal AEXT
digitalD4 → KY-019 5 V single-channel relay module INEXT
digitalD6 → Standard L298N dual H-bridge motor driver module IN1EXT
digitalD9 → Standard L298N dual H-bridge motor driver module IN2EXT
digitalD10 → Standard L298N dual H-bridge motor driver module IN4EXT
digitalD11 → Standard L298N dual H-bridge motor driver module IN3EXT
digitalA0 → HC-SR04 TRIGEXT
digitalA1 → HC-SR04 ECHOEXT
digitalA2 → HC-SR04 TRIGEXT
digitalA3 → HC-SR04 ECHOEXT
digitalA4 → HC-SR04 TRIGEXT
digitalA5 → HC-SR04 ECHOEXT

Deploy the firmware

#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ctype.h>

// ESP32 TXD/GPIO1 -> Arduino Uno D2
// Arduino Uno D3 -> 1k resistor -> ESP32 RXD/GPIO3
// ESP32 RXD/GPIO3 -> 2k resistor -> GND
const int UNO_COMMAND_TX_PIN = 1;
const int UNO_STATUS_RX_PIN = 3;
const char *AP_NAME = "MopBot";
const char *AP_PASSWORD = "MopBot123";

WebServer server(80);
String unoStatus = "Waiting for Arduino";
String lastCommand = "None";

void readUnoStatus() {
  static String line;
  while (Serial.available()) {
    char c = (char)Serial.read();
    if (c == '\r') continue;
    if (c == '\n') {
      if (line.length()) { unoStatus = line; line = ""; }
    } else if (c >= 32 && c <= 126 && line.length() < 40) {
      line += c;
    }
  }
}

void sendCommand(char c) {
  Serial.write(c);
  Serial.flush();
  lastCommand = String(c);
}

String json() {
  return String("{\"status\":\"") + unoStatus + "\",\"last\":\"" + lastCommand + "\"}";
}

const char PAGE[] PROGMEM = R"HTML(
<!doctype html><html><head><meta name="viewport" content="width=device-width,initial-scale=1"><title>Mop Bot</title><style>body{margin:0;padding:20px;text-align:center;background:#0f172a;color:#fff;font-family:Arial}.box{max-width:380px;margin:12px auto;padding:12px;background:#1e293b;border-radius:14px}.grid{display:grid;grid-template-columns:95px 95px 95px;grid-template-rows:76px 76px 76px;justify-content:center;gap:11px;margin:20px auto}button{border:0;border-radius:14px;color:white;background:#2563eb;font-size:16px;font-weight:bold;touch-action:manipulation;box-shadow:0 4px #1d4ed8}.stop{background:#dc2626;box-shadow:0 4px #991b1b}.auto{background:#059669;box-shadow:0 4px #047857}.pump{background:#9333ea;box-shadow:0 4px #6b21a8}.wide{width:300px;height:58px;margin:7px}.v{color:#5eead4;font-weight:bold}</style></head><body><h1>Mop Bot</h1><div class="box">Arduino: <span class="v" id="s">Loading...</span><br>Last: <span class="v" id="l">None</span></div><div class="grid"><i></i><button class="move" data-c="F">▲<br>Forward</button><i></i><button class="move" data-c="L">◀<br>Left</button><button class="stop" onclick="cmd('S')">■<br>STOP</button><button class="move" data-c="R">▶<br>Right</button><i></i><button class="move" data-c="B">▼<br>Back</button><i></i></div><button class="auto wide" onclick="cmd('A')">AUTO MODE</button><br><button class="pump wide" onclick="cmd('P')">PUMP ON / OFF</button><p>Hold a direction button to move. Releasing it stops the robot.</p><script>let t=null;function show(x){s.textContent=x.status;l.textContent=x.last}function cmd(c){fetch('/command?c='+c).then(x=>x.json()).then(show).catch(()=>s.textContent='Command failed')}function begin(c){cmd(c);clearInterval(t);t=setInterval(()=>cmd(c),250)}function end(){clearInterval(t);t=null;cmd('S')}document.querySelectorAll('.move').forEach(b=>{let c=b.dataset.c;b.onpointerdown=e=>{e.preventDefault();begin(c)};b.onpointerup=e=>{e.preventDefault();end()};b.onpointerleave=end;b.onpointercancel=end});onblur=end;setInterval(()=>fetch('/status').then(x=>x.json()).then(show).catch(()=>{}),1000)</script></body></html>
)HTML";

void home() { server.send_P(200, "text/html", PAGE); }
void status() { server.send(200, "application/json", json()); }
void command() {
  if (!server.hasArg("c") || server.arg("c").length() != 1) { server.send(400, "application/json", "{\"error\":\"Missing command\"}"); return; }
  char c = toupper(server.arg("c")[0]);
  if (String("AFBLRSP").indexOf(c) < 0) { server.send(400, "application/json", "{\"error\":\"Invalid command\"}"); return; }
  sendCommand(c);
  status();
}

void setup() {
  Serial.begin(9600, SERIAL_8N1, UNO_STATUS_RX_PIN, UNO_COMMAND_TX_PIN);
  WiFi.mode(WIFI_AP);
  WiFi.softAP(AP_NAME, AP_PASSWORD);
  server.on("/", home);
  server.on("/command", command);
  server.on("/status", status);
  server.begin();
}

void loop() {
  server.handleClient();
  readUnoStatus();
  delay(2);
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace, diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik