Wi-Fi Based Water Tank Monitoring and Control System Using Wemos D1 Mini

In this tutorial we are going to build a water tank level system with automatic water pump on / off feature by using Wemos D1 mini wi-fi webserver to display water tank level on smartphone, using 4 water level sensors, ability to turn on water pump while water level goes low, turn off water pump while water level goes full level, manually turn on / off water pump from smartphone over wi-fi, let’s start.

You can build a Wi-Fi-based water tank monitoring and control system using a Wemos D1 Mini, four water level sensors, and a water pump. Here’s how the system works:


System Features

  1. Wi-Fi Web Server (D1 Mini): Hosts a web page to view water level and control the pump.
  2. Water Level Sensing (4 sensors): Detect tank levels (e.g., EMPTY, LOW, MEDIUM, FULL).
  3. Automatic Pump Control:
    • Turns ON when water level is LOW.
    • Turns OFF when water level reaches FULL.
  4. Manual Pump Control: Control pump manually from smartphone via Wi-Fi.

🔧 Components Required

  • 1x Wemos D1 Mini (ESP8266)
  • 4x Water level sensors (float switches or conductive sensors)
  • 1x Relay module (to switch pump)
  • 1x Water pump (with proper voltage rating)
  • Jumper wires, 5V power supply

🔌 Wiring Overview

Sensor PositionGPIO Pin (D1 Mini)
FULLD1 (GPIO5)
MEDIUMD2 (GPIO4)
LOWD3 (GPIO0)
EMPTYD4 (GPIO2)
Relay (Pump)D5 (GPIO14)

Note: Use pull-up or pull-down resistors if needed based on sensor type.


🧠 Logic Flow

Read all 4 water level sensors.

IF (manual mode == true):
    obey smartphone commands to turn pump ON/OFF
ELSE:
    IF water level <= LOW:
        turn ON pump
    ELSE IF water level == FULL:
        turn OFF pump

Display current water level and pump status on webpage.

🌐 Web Interface Features

  • View current water level (e.g., using bars or colors)
  • View pump status (ON/OFF)
  • Button to manually turn pump ON
  • Button to manually turn pump OFF
  • Toggle between Auto and Manual mode

💻 Example Code (ESP8266 Arduino Sketch)

Here’s a minimal version of the code:

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

const char* ssid = "YourSSID";
const char* password = "YourPassword";

ESP8266WebServer server(80);

// Pin Definitions
#define FULL_SENSOR D1
#define MED_SENSOR D2
#define LOW_SENSOR D3
#define EMPTY_SENSOR D4
#define PUMP_RELAY D5

bool manualMode = false;
bool manualPumpState = false;

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);

  pinMode(FULL_SENSOR, INPUT);
  pinMode(MED_SENSOR, INPUT);
  pinMode(LOW_SENSOR, INPUT);
  pinMode(EMPTY_SENSOR, INPUT);
  pinMode(PUMP_RELAY, OUTPUT);
  digitalWrite(PUMP_RELAY, LOW); // OFF

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("WiFi connected");

  server.on("/", handleRoot);
  server.on("/pump/on", []() {
    manualMode = true;
    manualPumpState = true;
    digitalWrite(PUMP_RELAY, HIGH);
    server.send(200, "text/html", "Pump turned ON manually");
  });

  server.on("/pump/off", []() {
    manualMode = true;
    manualPumpState = false;
    digitalWrite(PUMP_RELAY, LOW);
    server.send(200, "text/html", "Pump turned OFF manually");
  });

  server.on("/mode/auto", []() {
    manualMode = false;
    server.send(200, "text/html", "Switched to AUTO mode");
  });

  server.begin();
}

void loop() {
  server.handleClient();
  
  if (!manualMode) {
    bool full = digitalRead(FULL_SENSOR);
    bool low = digitalRead(LOW_SENSOR);

    if (!full && low) {
      digitalWrite(PUMP_RELAY, HIGH); // Turn pump ON
    } else if (full) {
      digitalWrite(PUMP_RELAY, LOW); // Turn pump OFF
    }
  } else {
    digitalWrite(PUMP_RELAY, manualPumpState ? HIGH : LOW);
  }
}

void handleRoot() {
  String html = "<h1>Water Tank Status</h1>";
  html += "<p>Manual Mode: " + String(manualMode ? "ON" : "OFF") + "</p>";
  html += "<p>Pump State: " + String(digitalRead(PUMP_RELAY) ? "ON" : "OFF") + "</p>";
  html += "<a href='/pump/on'>Pump ON</a><br>";
  html += "<a href='/pump/off'>Pump OFF</a><br>";
  html += "<a href='/mode/auto'>Auto Mode</a><br>";
  server.send(200, "text/html", html);
}

📱 Access Web Server on Phone

  1. Connect smartphone to the same Wi-Fi as D1 Mini.
  2. Enter the D1 Mini’s IP (found via Serial Monitor) into your browser.
  3. Control and view tank status.

🛡️ Tips

  • Consider using debounce logic if float switches flicker.
  • Use opto-isolated relay module for pump safety.
  • Secure web interface if exposed outside local network.

✅ Updated Manual Mode Logic

Even in manual mode:

  1. If water level is FULL, pump must turn OFF, overriding manual state.
  2. If water level is already FULL, ignore manual ON command (don’t allow turning pump on).

🔄 Updated Code Snippet (Modified Sections Only)

Replace your existing manual ON handler and loop() logic with the following:

🔧 Manual Pump ON Endpoint – With Safety Check

server.on("/pump/on", []() {
  if (digitalRead(FULL_SENSOR)) {
    server.send(200, "text/html", "Tank is full. Cannot turn on pump.");
  } else {
    manualMode = true;
    manualPumpState = true;
    digitalWrite(PUMP_RELAY, HIGH);
    server.send(200, "text/html", "Pump turned ON manually.");
  }
});

🔁 Updated loop() Function – Auto-Off on Full Level in Manual Mode

void loop() {
  server.handleClient();

  bool full = digitalRead(FULL_SENSOR);
  bool low = digitalRead(LOW_SENSOR);

  if (full) {
    // Always turn off pump when tank is full (auto safety)
    digitalWrite(PUMP_RELAY, LOW);
    manualPumpState = false;
  } else if (!manualMode) {
    // Automatic mode logic
    if (low) {
      digitalWrite(PUMP_RELAY, HIGH); // Turn pump ON
    } else {
      digitalWrite(PUMP_RELAY, LOW);  // Turn pump OFF
    }
  } else {
    // Manual mode, only allow if not full
    digitalWrite(PUMP_RELAY, manualPumpState ? HIGH : LOW);
  }
}

✅ Summary of Features Now

  • Manual mode allows user control only if tank is not full.
  • If tank becomes full during manual mode, pump auto shuts off.
  • Manual mode won’t allow turning on pump when tank is already full.
  • Safe and semi-automatic protection built-in.

Would you like this updated into a full HTML+CSS mobile-friendly UI or a visual flow diagram of the logic?

Let’s enhance the web interface for your water tank system with a fancy, mobile-friendly UI that includes:

🌟 Features

  • Live Water Tank Visualization
  • Status Indicators (Pump, Mode, Water Level)
  • Stylish Buttons (Manual/Auto Mode, Pump ON/OFF)
  • Responsive Design (mobile & desktop)

💻 Enhanced Web Interface Code

Replace your handleRoot() function with the following code:

handleRoot() Function (Fancy HTML + CSS)

void handleRoot() {
  bool full = digitalRead(FULL_SENSOR);
  bool med = digitalRead(MED_SENSOR);
  bool low = digitalRead(LOW_SENSOR);
  bool empty = digitalRead(EMPTY_SENSOR);
  bool pumpState = digitalRead(PUMP_RELAY);

  String html = R"rawliteral(
  <!DOCTYPE html>
  <html>
  <head>
    <title>Water Tank Monitor</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
      body {
        font-family: 'Segoe UI', sans-serif;
        text-align: center;
        background: #f0f4f7;
        padding: 20px;
      }
      h1 {
        color: #333;
      }
      .tank {
        width: 100px;
        height: 300px;
        border: 3px solid #333;
        margin: 20px auto;
        background: #e0e0e0;
        position: relative;
        border-radius: 10px;
        overflow: hidden;
      }
      .level {
        position: absolute;
        width: 100%;
        background: #00bfff;
        bottom: 0;
        transition: height 0.5s;
      }
      .status {
        margin: 10px 0;
        font-size: 18px;
      }
      .btn {
        padding: 10px 20px;
        margin: 5px;
        background: #0077cc;
        color: white;
        border: none;
        border-radius: 5px;
        font-size: 16px;
        cursor: pointer;
        transition: background 0.3s;
      }
      .btn:hover {
        background: #005fa3;
      }
      .btn.red {
        background: #cc0000;
      }
      .btn.green {
        background: #00aa00;
      }
      .mode {
        margin-top: 15px;
        font-weight: bold;
      }
    </style>
  </head>
  <body>
    <h1>🚰 Water Tank Monitor</h1>

    <div class="tank">
      <div class="level" style="height:)rawliteral";

  // Calculate height percentage for water level
  int level = full ? 100 : med ? 66 : low ? 33 : 0;
  html += String(level) + R"rawliteral(%"></div></div>

    <div class="status">Water Level: <strong>)rawliteral";
  html += (full ? "FULL" : med ? "MEDIUM" : low ? "LOW" : "EMPTY");
  html += R"rawliteral(</strong></div>

    <div class="status">Pump: <strong style="color:)rawliteral";
  html += pumpState ? "green" : "red";
  html += R"rawliteral(">)rawliteral";
  html += pumpState ? "ON" : "OFF";
  html += R"rawliteral(</strong></div>

    <div class="status">Mode: <strong>)rawliteral";
  html += manualMode ? "Manual" : "Automatic";
  html += R"rawliteral(</strong></div>

    <div>
      <a href="/pump/on"><button class="btn green">Pump ON</button></a>
      <a href="/pump/off"><button class="btn red">Pump OFF</button></a>
      <a href="/mode/auto"><button class="btn">Auto Mode</button></a>
    </div>

    <div class="mode">Built with ❤️ using Wemos D1 Mini</div>
  </body>
  </html>
  )rawliteral";

  server.send(200, "text/html", html);
}

🖼️ What This Looks Like

  • A vertical tank graphic with a blue water level that fills up.
  • Colored status indicators for pump and mode.
  • Interactive buttons for manual/auto and pump control.
  • Clean layout, mobile responsive via CSS.

🚀 Optional Upgrades

Would you like to add:

  • Live updates without refresh using JavaScript (AJAX)?
  • Progress bar animation for water level?
  • Icons or chart graphs?
  • Dark mode toggle?

Let’s add AJAX live updates so the web page automatically refreshes water level, pump state, and mode without reloading the whole page.


✅ What We’ll Do:

  1. Use AJAX (JavaScript) to fetch real-time data from a new /status endpoint.
  2. Update the water level, pump state, and mode every few seconds.
  3. Keep the fancy look and buttons intact.

🧠 Step-by-Step Code Updates


🆕 1. Add /status Endpoint (JSON format)

Add this to your setup():

server.on("/status", []() {
  bool full = digitalRead(FULL_SENSOR);
  bool med = digitalRead(MED_SENSOR);
  bool low = digitalRead(LOW_SENSOR);
  bool empty = digitalRead(EMPTY_SENSOR);
  bool pumpState = digitalRead(PUMP_RELAY);

  String level = full ? "FULL" : med ? "MEDIUM" : low ? "LOW" : "EMPTY";
  int percent = full ? 100 : med ? 66 : low ? 33 : 0;

  String json = "{";
  json += "\"level\":\"" + level + "\",";
  json += "\"percent\":" + String(percent) + ",";
  json += "\"pump\":" + String(pumpState ? "true" : "false") + ",";
  json += "\"mode\":\"" + String(manualMode ? "Manual" : "Automatic") + "\"";
  json += "}";
  server.send(200, "application/json", json);
});

✅ 2. Update handleRoot() – Add JavaScript for Live Updates

In your existing handleRoot() HTML, add this <script> inside <head>, and replace level, status, and mode sections with ids to be updated.

✨ Add This JavaScript:

<script>
function fetchStatus() {
  fetch("/status")
    .then(response => response.json())
    .then(data => {
      document.getElementById("levelLabel").innerText = data.level;
      document.getElementById("levelBar").style.height = data.percent + "%";
      document.getElementById("pumpStatus").innerText = data.pump ? "ON" : "OFF";
      document.getElementById("pumpStatus").style.color = data.pump ? "green" : "red";
      document.getElementById("modeLabel").innerText = data.mode;
    });
}
setInterval(fetchStatus, 2000); // every 2 seconds
window.onload = fetchStatus;
</script>

✅ 3. Modify HTML Elements to Use IDs

Replace this block:

<div class="level" style="height:XX%"></div>

With:

<div class="level" id="levelBar" style="height:0%"></div>

Replace water level text:

<div class="status">Water Level: <strong id="levelLabel">--</strong></div>

Replace pump status:

<div class="status">Pump: <strong id="pumpStatus" style="color:gray">--</strong></div>

Replace mode:

<div class="status">Mode: <strong id="modeLabel">--</strong></div>

🎯 Final Result

  • Web page loads once.
  • Then, every 2 seconds, it automatically:
    • Updates tank level
    • Shows current pump state
    • Displays Auto/Manual mode
  • No page reloads required ✅

Would you like:

  • A dark mode toggle?
  • A pump runtime timer?
  • LocalStorage to remember UI settings?

Here’s a complete Arduino sketch for your Wemos D1 Mini Water Tank Monitor and Pump Controller, including:

✅ Fancy responsive web UI
✅ Manual & Auto pump control
✅ Auto pump cutoff at FULL level (even in manual mode)
✅ Live AJAX updates every 2 seconds


✅ FULL Arduino Sketch

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

// WiFi credentials
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

// Web server on port 80
ESP8266WebServer server(80);

// GPIO pin definitions
#define FULL_SENSOR D1
#define MED_SENSOR D2
#define LOW_SENSOR D3
#define EMPTY_SENSOR D4
#define PUMP_RELAY D5

// Control variables
bool manualMode = false;
bool manualPumpState = false;

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);

  pinMode(FULL_SENSOR, INPUT);
  pinMode(MED_SENSOR, INPUT);
  pinMode(LOW_SENSOR, INPUT);
  pinMode(EMPTY_SENSOR, INPUT);
  pinMode(PUMP_RELAY, OUTPUT);
  digitalWrite(PUMP_RELAY, LOW); // Start with pump OFF

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected. IP: " + WiFi.localIP().toString());

  // Web routes
  server.on("/", handleRoot);
  server.on("/pump/on", handlePumpOn);
  server.on("/pump/off", handlePumpOff);
  server.on("/mode/auto", handleAutoMode);
  server.on("/status", handleStatus);

  server.begin();
}

void loop() {
  server.handleClient();

  bool full = digitalRead(FULL_SENSOR);
  bool low = digitalRead(LOW_SENSOR);

  if (full) {
    // Always turn off pump at full level
    digitalWrite(PUMP_RELAY, LOW);
    manualPumpState = false;
  } else if (!manualMode) {
    // Auto mode logic
    if (low) {
      digitalWrite(PUMP_RELAY, HIGH);
    } else {
      digitalWrite(PUMP_RELAY, LOW);
    }
  } else {
    // Manual mode logic
    digitalWrite(PUMP_RELAY, manualPumpState ? HIGH : LOW);
  }
}

// HTML UI handler
void handleRoot() {
  String html = R"rawliteral(
    <!DOCTYPE html>
    <html>
    <head>
      <title>Water Tank Monitor</title>
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <style>
        body { font-family: 'Segoe UI', sans-serif; text-align: center; background: #f0f4f7; padding: 20px; }
        h1 { color: #333; }
        .tank { width: 100px; height: 300px; border: 3px solid #333; margin: 20px auto; background: #e0e0e0; position: relative; border-radius: 10px; overflow: hidden; }
        .level { position: absolute; width: 100%; background: #00bfff; bottom: 0; transition: height 0.5s; }
        .status { margin: 10px 0; font-size: 18px; }
        .btn { padding: 10px 20px; margin: 5px; background: #0077cc; color: white; border: none; border-radius: 5px; font-size: 16px; cursor: pointer; transition: background 0.3s; }
        .btn:hover { background: #005fa3; }
        .btn.red { background: #cc0000; }
        .btn.green { background: #00aa00; }
        .mode { margin-top: 15px; font-weight: bold; }
      </style>
      <script>
        function fetchStatus() {
          fetch("/status")
            .then(response => response.json())
            .then(data => {
              document.getElementById("levelLabel").innerText = data.level;
              document.getElementById("levelBar").style.height = data.percent + "%";
              document.getElementById("pumpStatus").innerText = data.pump ? "ON" : "OFF";
              document.getElementById("pumpStatus").style.color = data.pump ? "green" : "red";
              document.getElementById("modeLabel").innerText = data.mode;
            });
        }
        setInterval(fetchStatus, 2000);
        window.onload = fetchStatus;
      </script>
    </head>
    <body>
      <h1>🚰 Water Tank Monitor</h1>
      <div class="tank"><div class="level" id="levelBar" style="height:0%"></div></div>
      <div class="status">Water Level: <strong id="levelLabel">--</strong></div>
      <div class="status">Pump: <strong id="pumpStatus" style="color:gray">--</strong></div>
      <div class="status">Mode: <strong id="modeLabel">--</strong></div>
      <div>
        <a href="/pump/on"><button class="btn green">Pump ON</button></a>
        <a href="/pump/off"><button class="btn red">Pump OFF</button></a>
        <a href="/mode/auto"><button class="btn">Auto Mode</button></a>
      </div>
      <div class="mode">Built with ❤️ on Wemos D1 Mini</div>
    </body>
    </html>
  )rawliteral";

  server.send(200, "text/html", html);
}

// AJAX status endpoint
void handleStatus() {
  bool full = digitalRead(FULL_SENSOR);
  bool med = digitalRead(MED_SENSOR);
  bool low = digitalRead(LOW_SENSOR);
  bool empty = digitalRead(EMPTY_SENSOR);
  bool pumpState = digitalRead(PUMP_RELAY);

  String level = full ? "FULL" : med ? "MEDIUM" : low ? "LOW" : "EMPTY";
  int percent = full ? 100 : med ? 66 : low ? 33 : 0;

  String json = "{";
  json += "\"level\":\"" + level + "\",";
  json += "\"percent\":" + String(percent) + ",";
  json += "\"pump\":" + String(pumpState ? "true" : "false") + ",";
  json += "\"mode\":\"" + String(manualMode ? "Manual" : "Automatic") + "\"";
  json += "}";
  server.send(200, "application/json", json);
}

// Button routes
void handlePumpOn() {
  if (digitalRead(FULL_SENSOR)) {
    server.send(200, "text/html", "Tank is full. Cannot turn on pump.");
  } else {
    manualMode = true;
    manualPumpState = true;
    digitalWrite(PUMP_RELAY, HIGH);
    server.sendHeader("Location", "/");
    server.send(303);
  }
}

void handlePumpOff() {
  manualMode = true;
  manualPumpState = false;
  digitalWrite(PUMP_RELAY, LOW);
  server.sendHeader("Location", "/");
  server.send(303);
}

void handleAutoMode() {
  manualMode = false;
  server.sendHeader("Location", "/");
  server.send(303);
}

🧪 Before Uploading

  1. Install ESP8266 board support in Arduino IDE (via Board Manager).
  2. Select Wemos D1 R1 or LOLIN(WEMOS) D1 mini as board.
  3. Replace ssid and password with your Wi-Fi credentials.

📲 Access on Your Phone

  1. Upload code to the D1 Mini.
  2. Open Serial Monitor → get IP address.
  3. Enter the IP address in your phone’s browser.

Would you like:

  • A dark mode version of the UI?
  • OTA firmware update support?
  • Pump runtime tracking (hours on)?

✅ Final Conclusion

You’ve now built a complete IoT-based Water Tank Monitoring System using a Wemos D1 Mini, which includes:


🔧 Key Functionalities:

  • Water Level Monitoring using 4 sensors: FULL, MED, LOW, EMPTY
  • Automatic Pump Control based on LOW/FULL levels
  • Manual Pump Control from smartphone via Wi-Fi
  • Failsafe: Pump automatically turns OFF at FULL level, even in manual mode
  • Smart Web Dashboard:
    • Responsive and mobile-friendly
    • Animated tank level display
    • AJAX-powered live updates (no refresh needed)
    • Intuitive control buttons

📡 Technologies Used:

  • ESP8266 (Wemos D1 Mini)
  • HTML/CSS/JavaScript (AJAX)
  • Arduino IDE & ESP8266WebServer
  • Wi-Fi networking

🚀 What You Can Add Next:

  • OTA (Over-the-Air) firmware updates
  • Email/SMS alerts when tank is low or full
  • Logging to Google Sheets / Blynk / MQTT
  • Runtime tracking & pump usage analytics
  • Dark/light mode toggle

If you’d like help with any of those upgrades or making a PCB, 3D-printed enclosure, or mobile app interface, I’d be happy to guide you.

You’re well on your way to a smart, automated home water system. 💧📲

Relatest posts

Leave Comments

Top