Automatic Solar Panel Cleaning System using ESP32 | IoT Based Solar Panel Cleaner Project

Solar Panel Cleaning System Using ESP32

Solar Panel Cleaning System Using ESP32

This project creates a fully automated solar panel cleaning system powered by solar energy itself. The system features a self-sustaining power loop, WiFi control via smartphone, and automatic timed cleaning cycles. Perfect for maintaining solar panel efficiency!

Self-Powered
Solar Energy Loop
WiFi Control
ESP32 Web Server
5-20 Sec
Adjustable Timer
7.4V
Battery System

How the System Works

6V Solar Panel MT3608 Boost TP5100 Charger 2S BMS 7.4V Battery

Battery XL4015 Buck 5V ESP32 Web Server Phone Control

ESP32 5V Relay 6V Pump Cleaning

Here's the complete working explanation:

๐Ÿ”‹ Power Generation & Storage:

  • 6V Solar Panel captures sunlight and generates power
  • MT3608 Boost Converter increases voltage from 6V to ~9V (required for charging 7.4V battery)
  • TP5100 Charging Module safely charges the 2S lithium battery with constant current/constant voltage
  • 2S BMS provides protection against overcharge, over-discharge, short circuit, and balances both cells
  • 7.4V Li-ion Battery stores energy and becomes the main power source for the system

⚡ Power Distribution:

  • From battery, one line goes to XL4015 Buck Converter
  • XL4015 steps down voltage to stable 5V for ESP32
  • ESP32 runs continuously, hosting a web server for control

๐Ÿ“ฑ Control System:

  • ESP32 creates a WiFi access point and hosts a beautiful web interface
  • When you open the web page on your phone and press the power button:
  • ESP32 sends signal to the 5V Relay Module
  • Relay acts as a switch, connecting battery power directly to the 6V Water Pump
  • Pump sprays water on the solar panel for the set duration (5-20 seconds)
  • After timer ends, relay turns OFF and pump stops

๐Ÿ”„ The Complete Loop:

Sunlight → Solar Panel → Boost Converter → Charger → Battery → Buck Converter → ESP32 → Relay → Pump → Cleaning → Sunlight (repeats)

Completely automatic and self-sustaining! The system charges while cleaning, creating an endless cycle.


Materials Required

Core Components:


3D Printable Parts

Download the 3D printable enclosure files for the system:

Download 3D Files

Print these files using JLC3DP's professional 3D printing service:

Order 3D Prints Now

Power Management System

Stage Component Input Voltage Output Voltage Purpose
1 Solar Panel Sunlight 6V Primary power source
2 MT3608 Boost 6V 9V Boost for battery charging
3 TP5100 Charger 9V 8.4V (to battery) Charges 2S lithium battery
4 2S BMS 8.4V 7.4V (protected) Protection & cell balancing
5 XL4015 Buck 7.4V 5V Powers ESP32
6 Relay + Pump 7.4V (direct) 6V to pump Cleaning action

Step-by-Step Assembly

Power Circuit
Control Circuit
Testing

Power Circuit Assembly:

  1. Connect 6V solar panel to MT3608 boost converter input
  2. Adjust MT3608 potentiometer to output 9V (measure with multimeter)
  3. Connect MT3608 output to TP5100 charger input
  4. Connect TP5100 output to 2S BMS (B+ and B-)
  5. Connect 7.4V battery to BMS (B+ and B- for battery, P+ and P- for output)
  6. From BMS output, connect to XL4015 buck converter input
  7. Adjust XL4015 to output 5V (for ESP32)
  8. Add 470ยตF capacitor across power lines for stability

Control Circuit Assembly:

  1. Connect XL4015 5V output to ESP32 VIN and GND
  2. Connect relay module VCC to ESP32 5V, GND to GND
  3. Connect relay IN pin to ESP32 GPIO 23
  4. Connect 6V pump positive wire to relay COM (common) terminal
  5. Connect 6V pump negative wire to battery negative (GND)
  6. Connect battery positive (P+ from BMS) to relay NO (normally open) terminal
  7. Double-check all connections before powering on

Testing & Calibration:

  1. Place solar panel in sunlight - check battery voltage rises
  2. Verify XL4015 output is stable 5V
  3. Upload ESP32 code with your WiFi credentials
  4. Connect to ESP32's web interface using the IP address shown in Serial Monitor
  5. Test pump by pressing the power button on web interface
  6. Verify auto shutoff after set timer (default 10 seconds)
  7. Test all timer options: 5, 10, 15, 20 seconds
  8. Check battery recharging while system operates

๐ŸŽฅ Watch the video above for complete assembly and demonstration of the Solar Panel Cleaning System.


Arduino Code (ESP32)

ESP32 Code - Upload to ESP32
#include <WiFi.h>
#include <WebServer.h>

const char* ssid = "Your Wifi Name";
const char* password = "Your Password";

WebServer server(80);

const int relayPin = 23;   // ACTIVE LOW relay
bool pumpState = false;

unsigned long autoOffTime = 10000; // Default 10 seconds (10000ms)
unsigned long pumpStartTime = 0;

// ================= RELAY CONTROL =================
void setPump(bool state) {
  pumpState = state;

  if (pumpState) {
    digitalWrite(relayPin, LOW);   // ON (ACTIVE LOW)
    pumpStartTime = millis();
    Serial.println("Pump ON");
  } else {
    digitalWrite(relayPin, HIGH);  // OFF
    Serial.println("Pump OFF");
  }
}

// ================= WEB PAGE =================
String webpage() {
return R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=yes">
<title>Solar Pump Controller</title>

<style>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  background: #0f172a;
  color: white;
  font-family: 'Segoe UI', Arial, sans-serif;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

.container {
  max-width: 600px;
  width: 100%;
  background: rgba(30, 41, 59, 0.7);
  backdrop-filter: blur(10px);
  border-radius: 32px;
  padding: 24px;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 24px;
}

h2 {
  font-size: 24px;
  font-weight: 600;
  background: linear-gradient(135deg, #fff, #94a3b8);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.status-badge {
  display: flex;
  align-items: center;
  gap: 8px;
  background: rgba(255, 255, 255, 0.05);
  padding: 8px 16px;
  border-radius: 40px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.connection-led {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #f44336;
  box-shadow: 0 0 10px #f44336;
  transition: all 0.3s;
}

.connection-led.online {
  background: #4caf50;
  box-shadow: 0 0 15px #4caf50;
}

.connection-text {
  font-size: 14px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.9);
}

/* Status Cards */
.status-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-bottom: 24px;
}

.status-card {
  background: rgba(15, 23, 42, 0.6);
  border-radius: 20px;
  padding: 20px;
  border: 1px solid rgba(255, 255, 255, 0.05);
  text-align: left;
}

.status-label {
  font-size: 13px;
  color: #94a3b8;
  margin-bottom: 8px;
  text-transform: uppercase;
  letter-spacing: 1px;
}

.status-value {
  font-size: 28px;
  font-weight: 700;
  color: #fff;
}

.status-value.on {
  color: #4caf50;
  text-shadow: 0 0 15px #4caf50;
}

.status-value.off {
  color: #f44336;
  text-shadow: 0 0 15px #f44336;
}

/* ===== YOUR BUTTON DESIGN ===== */
.power-wrapper {
  display: flex;
  justify-content: center;
  margin: 30px 0;
  cursor: pointer;
  user-select: none;
}

.power-btn {
  width: 220px;
  height: 220px;
  border-radius: 50%;
  position: relative;
  cursor: pointer;
  transition: all 0.15s ease;
  background: radial-gradient(circle at 30% 30%, #444, #111 70%);
  border: 16px solid #c0c0c0;
  box-shadow:
    inset 0 10px 18px rgba(255,255,255,0.1),
    inset 0 -12px 25px rgba(0,0,0,0.8),
    0 15px 35px rgba(0,0,0,0.7);
}

.power-btn:active {
  transform: translateY(8px) scale(0.96);
}

/* Symbol */
.symbol {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
  width: 110px;
  height: 110px;
  position: relative;
  color: #aaa;
  transition: 0.3s;
}

.power-line {
  position: absolute;
  top: 5px;
  left: 50%;
  transform: translateX(-50%);
  width: 18px;
  height: 50px;
  background: currentColor;
  border-radius: 12px;
}

.power-circle {
  position: absolute;
  bottom: 5px;
  left: 50%;
  transform: translateX(-50%);
  width: 80px;
  height: 80px;
  border: 18px solid currentColor;
  border-top: 18px solid transparent;
  border-radius: 50%;
  box-sizing: border-box;
}

.led {
  width: 70px;
  height: 10px;
  border-radius: 6px;
  position: absolute;
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
  background: red;
  box-shadow: 0 0 12px red;
  transition: 0.3s;
  z-index: 2;
}

/* ON State */
.power-btn.on .symbol {
  color: #00ff88;
  text-shadow: 0 0 25px #00ff88;
}

.power-btn.on .led {
  background: #00ff88;
  box-shadow: 0 0 25px #00ff88;
}

/* Timer Section */
.timer-section {
  background: rgba(15, 23, 42, 0.6);
  border-radius: 24px;
  padding: 24px;
  margin: 24px 0;
  border: 1px solid rgba(255, 255, 255, 0.05);
}

.timer-title {
  font-size: 14px;
  color: #94a3b8;
  margin-bottom: 16px;
  text-transform: uppercase;
  letter-spacing: 1px;
}

.timer-buttons {
  display: flex;
  gap: 12px;
  justify-content: space-between;
  flex-wrap: wrap;
}

.timer-btn {
  flex: 1;
  min-width: 70px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: white;
  padding: 14px 8px;
  border-radius: 16px;
  font-size: 18px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.timer-btn.active {
  background: rgba(33, 150, 243, 0.15);
  border-color: #2196f3;
  box-shadow: 0 0 20px rgba(33, 150, 243, 0.3);
  color: #2196f3;
}

.timer-sec {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.5);
  font-weight: 400;
}

.timer-btn.active .timer-sec {
  color: #90caf9;
}

/* Timer Display */
.timer-display {
  background: linear-gradient(145deg, #0f172a, #1e293b);
  border-radius: 24px;
  padding: 24px;
  margin: 24px 0;
  border: 1px solid rgba(255, 255, 255, 0.05);
  text-align: center;
}

.timer-label {
  font-size: 14px;
  color: #94a3b8;
  margin-bottom: 12px;
  text-transform: uppercase;
  letter-spacing: 2px;
}

.timer-value {
  font-size: 64px;
  font-weight: 700;
  color: #2196f3;
  font-family: monospace;
  line-height: 1;
  text-shadow: 0 0 30px rgba(33, 150, 243, 0.5);
}

.timer-unit {
  font-size: 18px;
  color: rgba(255, 255, 255, 0.5);
  margin-left: 5px;
}

.progress-container {
  width: 100%;
  height: 8px;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 4px;
  margin-top: 20px;
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, #2196f3, #64b5f6);
  border-radius: 4px;
  transition: width 0.1s linear;
}

/* Disabled state for timer buttons */
.timer-btn.disabled {
  opacity: 0.5;
  pointer-events: none;
  cursor: not-allowed;
}

@media (max-width: 480px) {
  .container { padding: 16px; }
  .power-btn { width: 180px; height: 180px; border-width: 12px; }
  .symbol { width: 90px; height: 90px; }
  .power-line { width: 14px; height: 40px; }
  .power-circle { width: 65px; height: 65px; border-width: 14px; }
  .timer-value { font-size: 48px; }
  .timer-btn { min-width: 60px; font-size: 16px; }
}
</style>
</head>

<body>
<div class="container">
  <div class="header">
    <h2>⚡ Solar Panel Cleaning System </h2>
    <div class="status-badge">
      <span class="connection-led" id="connLed"></span>
      <span class="connection-text" id="connText">Connecting...</span>
    </div>
  </div>

  <!-- Status Cards -->
  <div class="status-grid">
    <div class="status-card">
      <div class="status-label">Pump Status</div>
      <div class="status-value off" id="pumpStatus">OFF</div>
    </div>
    <div class="status-card">
      <div class="status-label">System</div>
      <div class="status-value" id="systemStatus">Standby</div>
    </div>
  </div>

  <!-- YOUR POWER BUTTON -->
  <div class="power-wrapper" onclick="togglePump()">
    <div id="powerBtn" class="power-btn">
      <div class="led"></div>
      <div class="symbol">
        <div class="power-line"></div>
        <div class="power-circle"></div>
      </div>
    </div>
  </div>

  <!-- Timer Selection -->
  <div class="timer-section">
    <div class="timer-title">⏰ Auto Shutdown Timer</div>
    <div class="timer-buttons">
      <button class="timer-btn" onclick="setTimer(5)" id="timer5">5<span class="timer-sec">SEC</span></button>
      <button class="timer-btn active" onclick="setTimer(10)" id="timer10">10<span class="timer-sec">SEC</span></button>
      <button class="timer-btn" onclick="setTimer(15)" id="timer15">15<span class="timer-sec">SEC</span></button>
      <button class="timer-btn" onclick="setTimer(20)" id="timer20">20<span class="timer-sec">SEC</span></button>
    </div>
  </div>

  <!-- Timer Display -->
  <div class="timer-display" id="timerDisplay">
    <div class="timer-label">Time Remaining</div>
    <div>
      <span class="timer-value" id="timerValue">10</span>
      <span class="timer-unit">seconds</span>
    </div>
    <div class="progress-container">
      <div class="progress-fill" id="progressFill"></div>
    </div>
  </div>
</div>

<script>
// ==================== STATE MANAGEMENT ====================
let isPumpOn = false;
let remainingTime = 0;
let totalTime = 10; // in seconds
let timerInterval = null;
let commandInProgress = false;

// ==================== TOGGLE PUMP ====================
function togglePump() {
  if (commandInProgress) return;
  commandInProgress = true;
  
  fetch('/toggle')
    .then(() => {
      console.log('Toggle command sent');
      setTimeout(() => { commandInProgress = false; }, 300);
    })
    .catch(error => {
      console.error('Error:', error);
      commandInProgress = false;
    });
}

// ==================== SET TIMER ====================
function setTimer(seconds) {
  if (isPumpOn) {
    alert('Please turn OFF the pump first');
    return;
  }
  
  // Convert seconds to milliseconds for server
  let timeMs = seconds * 1000;
  
  // Update UI
  totalTime = seconds;
  document.getElementById('timerValue').textContent = seconds;
  
  // Update active button
  document.querySelectorAll('.timer-btn').forEach(btn => {
    btn.classList.remove('active');
  });
  document.getElementById('timer' + seconds).classList.add('active');
  
  // Send to server
  fetch('/setTimer?time=' + timeMs);
}

// ==================== UPDATE STATUS ====================
function updateStatus() {
  fetch('/status')
    .then(res => res.json())
    .then(data => {
      // Update connection status
      document.getElementById('connLed').className = 'connection-led online';
      document.getElementById('connText').innerHTML = 'Online';
      
      // Get button element
      let btn = document.getElementById('powerBtn');
      let pumpStatusEl = document.getElementById('pumpStatus');
      let systemStatusEl = document.getElementById('systemStatus');
      
      // Update pump state
      isPumpOn = data.pump;
      
      if (data.pump) {
        btn.classList.add('on');
        pumpStatusEl.innerHTML = 'RUNNING';
        pumpStatusEl.className = 'status-value on';
        systemStatusEl.innerHTML = 'ACTIVE';
        systemStatusEl.className = 'status-value on';
        
        // Calculate remaining time
        if (data.remainingTime !== undefined) {
          remainingTime = data.remainingTime;
          document.getElementById('timerValue').textContent = remainingTime;
          
          // Update progress bar
          let progress = (remainingTime / totalTime) * 100;
          document.getElementById('progressFill').style.width = progress + '%';
        }
        
        // Disable timer buttons
        document.querySelectorAll('.timer-btn').forEach(btn => {
          btn.classList.add('disabled');
        });
      } else {
        btn.classList.remove('on');
        pumpStatusEl.innerHTML = 'OFF';
        pumpStatusEl.className = 'status-value off';
        systemStatusEl.innerHTML = 'STANDBY';
        systemStatusEl.className = 'status-value';
        
        // Reset timer display
        document.getElementById('timerValue').textContent = totalTime;
        document.getElementById('progressFill').style.width = '0%';
        
        // Enable timer buttons
        document.querySelectorAll('.timer-btn').forEach(btn => {
          btn.classList.remove('disabled');
        });
      }
    })
    .catch(() => {
      // Connection error
      document.getElementById('connLed').className = 'connection-led';
      document.getElementById('connText').innerHTML = 'Offline';
      document.getElementById('connLed').style.background = '#f44336';
      document.getElementById('connLed').style.boxShadow = '0 0 15px #f44336';
    });
}

// ==================== LOCAL COUNTDOWN ====================
function startLocalCountdown() {
  if (timerInterval) clearInterval(timerInterval);
  
  timerInterval = setInterval(() => {
    if (isPumpOn && remainingTime > 0) {
      remainingTime--;
      document.getElementById('timerValue').textContent = remainingTime;
      
      let progress = (remainingTime / totalTime) * 100;
      document.getElementById('progressFill').style.width = progress + '%';
      
      // Visual effect for last 3 seconds
      if (remainingTime <= 3) {
        document.getElementById('timerValue').style.color = '#ff9800';
      } else {
        document.getElementById('timerValue').style.color = '#2196f3';
      }
    }
  }, 1000);
}

// ==================== INITIALIZE ====================
function initialize() {
  // Start status updates
  updateStatus();
  setInterval(updateStatus, 1000);
  
  // Start local countdown
  startLocalCountdown();
  
  // Set initial timer to 10 seconds
  setTimer(10);
}

// Start everything
initialize();
</script>
</body>
</html>
)rawliteral";
}

// ==================== ROUTES ====================
void handleRoot() {
  server.send(200, "text/html", webpage());
}

void handleToggle() {
  setPump(!pumpState);
  server.send(200, "text/plain", "OK");
}

void handleStatus() {
  // Calculate remaining time in seconds
  int remainingSeconds = 0;
  if (pumpState) {
    unsigned long elapsed = millis() - pumpStartTime;
    if (elapsed < autoOffTime) {
      remainingSeconds = (autoOffTime - elapsed) / 1000;
    }
  }
  
  String json = "{";
  json += "\"pump\":" + String(pumpState ? "true" : "false") + ",";
  json += "\"remainingTime\":" + String(remainingSeconds);
  json += "}";
  
  server.send(200, "application/json", json);
}

void handleTimer() {
  if (server.hasArg("time")) {
    autoOffTime = server.arg("time").toInt();
    Serial.print("Timer set to: ");
    Serial.print(autoOffTime / 1000);
    Serial.println(" seconds");
  }
  server.send(200, "text/plain", "OK");
}

// ==================== SETUP ====================
void setup() {
  Serial.begin(115200);
  Serial.println("\nStarting Solar Pump Controller...");

  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, HIGH);  // SAFE OFF (active LOW)

  // Connect to WiFi
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi");
  
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 30) {
    delay(500);
    Serial.print(".");
    attempts++;
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\n✅ WiFi Connected!");
    Serial.print("๐Ÿ“ก IP Address: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("\n❌ WiFi Failed!");
  }

  // Setup routes
  server.on("/", handleRoot);
  server.on("/toggle", handleToggle);
  server.on("/status", handleStatus);
  server.on("/setTimer", handleTimer);

  server.begin();
  Serial.println("๐Ÿš€ Server Started!");
}

// ==================== LOOP ====================
void loop() {
  server.handleClient();

  // Auto OFF timer check
  if (pumpState && (millis() - pumpStartTime >= autoOffTime)) {
    setPump(false);
    Serial.println("⏰ Auto OFF");
  }
}

Code Features:

  • Beautiful Web Interface: Custom-designed power button with status display
  • Adjustable Timer: 5, 10, 15, or 20 second cleaning cycles
  • Real-time Updates: Live countdown with progress bar
  • Connection Status: Visual LED indicator for WiFi connection
  • Mobile Responsive: Works perfectly on smartphones
  • Safety Features: Active LOW relay for failsafe operation
  • Auto Shutdown: Pump turns off automatically after timer expires

Setup Instructions:

  1. Change ssid and password to your WiFi credentials
  2. Upload code to ESP32 using Arduino IDE (install ESP32 board support first)
  3. Open Serial Monitor to see the IP address after connection
  4. Connect to the same WiFi network on your phone
  5. Open browser and enter the ESP32's IP address
  6. Control the pump from the beautiful web interface!

Wiring Connections

ESP32 Pin Connections:

Component ESP32 Pin Notes
Relay Module IN GPIO 23 Control signal (ACTIVE LOW)
Relay VCC 5V From XL4015 buck converter
Relay GND GND Common ground
ESP32 VIN XL4015 5V Power input
ESP32 GND XL4015 GND Power ground

Power System Connections:

From To Voltage
Solar Panel + MT3608 IN+ 6V
Solar Panel - MT3608 IN- GND
MT3608 OUT+ TP5100 IN+ 9V
MT3608 OUT- TP5100 IN- GND
TP5100 B+ BMS B+ Battery +
TP5100 B- BMS B- Battery -
BMS P+ XL4015 IN+ / Relay NO 7.4V
BMS P- XL4015 IN- / Pump - GND
XL4015 OUT+ ESP32 VIN / Relay VCC 5V
Relay COM Pump + Switched 7.4V

Applications

☀️ Residential Solar Panels

Maintain efficiency with regular cleaning

๐Ÿญ Solar Farms

Scalable design for multiple panels

๐Ÿš RVs & Boats

Off-grid solar maintenance

๐ŸŒฑ Greenhouses

Keep solar-powered systems clean

๐Ÿ”ง Note: This system is completely self-powered! The solar panel charges the battery while also powering the cleaning system. In full sunlight, the battery will remain charged even with regular cleaning cycles. For best results, adjust the timer based on your panel's dirt accumulation rate.


Why Choose This Design?

  • Self-Sustaining: Runs entirely on solar power, no external electricity needed
  • WiFi Control: Convenient smartphone interface from anywhere
  • Efficiency Boost: Clean panels can produce 15-25% more energy
  • Battery Protection: Multiple protection layers (BMS, chargers, converters)
  • Customizable: Adjustable timers for different cleaning needs
  • Scalable: Can be adapted for larger panels and pumps

Comments

Popular posts from this blog

Solar Tracking System

Arduino Code

Arduino Code Car Parking System