0% found this document useful (0 votes)
3 views7 pages

Flood Monitor Code Explained

Uploaded by

sajimoko2
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views7 pages

Flood Monitor Code Explained

Uploaded by

sajimoko2
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Flood Monitoring & Auto Power Cut-Off System

Arduino source code (NodeMCU ESP8266) with full explanatory comments — file: flood_monitor_explained.ino

/*
* ============================================================================
* FLOOD MONITORING & AUTOMATIC POWER CUT-OFF SYSTEM
* Fully commented (educational) version
* ============================================================================
*
* WHAT THIS SYSTEM DOES (in plain English):
* -----------------------------------------
* 1. A rain sensor on the roof detects the moment rain starts falling.
* 2. A soil moisture sensor (optional add-on) in the ground detects when
* the soil is so soaked ("saturated") that it can no longer absorb
* water — the condition that turns ordinary rain into a flood. The
* system runs perfectly without this sensor connected.
* 3. An HC-SR04 ultrasonic sensor mounted high up (e.g. on the ceiling
* of a veranda) points DOWN at the floor and measures the distance to
* whatever is below it. When water enters, the distance gets SHORTER.
* Water level = mounting height - measured distance.
* 4. The system escalates through 3 alert stages, each with a voice
* message played 3 times through a speaker:
* Stage 1: "Light rain detected" (rain or saturated soil)
* Stage 2: "Flood warning" (water 10 cm above floor)
* Stage 3: "Evacuate now" (water 25 cm above floor)
* 5. At Stage 3, BEFORE the water reaches the wall sockets, relay 1 cuts
* the AC supply line and relay 2 switches on an external siren.
* 6. Sensor readings are uploaded to the internet (ThingSpeak) over
* Wi-Fi so the flood can be monitored remotely on a phone.
*
* HOW TO READ THIS FILE:
* ----------------------
* The code runs from top to bottom in this order:
* setup() -> runs ONCE when the board powers on (prepares pins, etc.)
* loop() -> runs FOREVER, over and over, thousands of times a second
* Everything else is a "function" — a named block of code that setup()
* and loop() call when they need it.
* ============================================================================
*/

/* ---------------------------------------------------------------------------
* LIBRARIES
* A library is pre-written code that someone else made, which we "include"
* so we don't have to write it ourselves.
* ------------------------------------------------------------------------ */
#include <ESP8266WiFi.h> // Lets the NodeMCU connect to Wi-Fi networks
#include <SoftwareSerial.h> // Creates an extra serial port on normal pins
// (needed because the DFPlayer talks over
// serial, and the main serial port is busy
// talking to the computer for debugging)
#include <DFRobotDFPlayerMini.h> // Controls the DFPlayer Mini MP3 module
// Install via: Sketch > Include Library >
// Manage Libraries > search "DFRobotDFPlayerMini"

/* ---------------------------------------------------------------------------
* USER SETTINGS — these are the ONLY values you should need to change
* ------------------------------------------------------------------------ */

// Set to true if you want the system to upload readings to ThingSpeak.


// Set to false to run completely offline (the alerts still work!).
#define ENABLE_CLOUD false

const char* WIFI_SSID = "YourWiFi"; // Your Wi-Fi network name


const char* WIFI_PASS = "YourPassword"; // Your Wi-Fi password
const char* TS_API_KEY = "YOUR_KEY_HERE"; // From [Link] (free account)

// How high above the FLOOR the ultrasonic sensor is mounted, in centimetres.

Flood Monitoring System - flood_monitor_explained.ino Page 1


// MEASURE THIS WITH A TAPE RULE after you mount the sensor. If the sensor
// hangs 250 cm above the floor and it measures 240 cm to the surface below,
// then the water is 250 - 240 = 10 cm deep.
const float MOUNT_HEIGHT_CM = 250.0;

// The water depths (measured from the floor) that trigger each stage:
const float LEVEL_WARNING_CM = 10.0; // Stage 2 fires at 10 cm of water
const float LEVEL_CRITICAL_CM = 25.0; // Stage 3 fires at 25 cm of water
// (set this BELOW your lowest socket!)

// "Hysteresis" stops the alarm from rapidly switching on/off when the water
// hovers exactly at a threshold (e.g. small waves). Once the WARNING alarm
// is on, the water must drop 3 cm BELOW the threshold before it turns off.
const float HYSTERESIS_CM = 3.0;

// Soil moisture threshold. Capacitive sensors give a LOWER number when WET.
// CALIBRATE IT: open the Serial Monitor, note the value with the probe in
// dry soil (about 700-800) and in a cup of water (about 300-400), then pick
// a number in between.
const int SOIL_WET_RAW = 450;

/* ---------------------------------------------------------------------------
* PIN MAP — which wire goes to which pin on the NodeMCU
* "#define NAME value" just gives a pin a human-readable name.
* ------------------------------------------------------------------------ */
#define PIN_TRIG D1 // Ultrasonic TRIG: we pulse this to fire a sound ping
#define PIN_ECHO D2 // Ultrasonic ECHO: goes HIGH until the echo returns.
// !!! MUST pass through a voltage divider (1k + 2k)
// because the sensor outputs 5V and the ESP8266
// pins can only survive 3.3V !!!
#define PIN_RAIN D3 // FC-37 rain sensor DIGITAL output (LOW when wet)
#define PIN_DF_RX D5 // We LISTEN here -> connect to DFPlayer TX pin
#define PIN_DF_TX D6 // We TALK here -> connect to DFPlayer RX pin
// (put a 1k resistor in series on this wire — the
// DFPlayer expects 5V logic, 3.3V works but the
// resistor stops noise/clicking sounds)
#define PIN_RELAY_AC D7 // Relay channel 1: cuts the AC supply line
#define PIN_SIREN D0 // Relay channel 2: switches the external siren
#define PIN_BUZZER D8 // Small active buzzer (backup if speaker fails)
#define PIN_LED D4 // Red warning LED (shares the onboard LED, which
// is wired "inverted": LOW = light ON)
#define PIN_SOIL A0 // The ONLY analog pin on the ESP8266 — used by
// the capacitive soil moisture sensor

// Most cheap 4-channel relay boards are "ACTIVE LOW": sending LOW switches
// the relay ON. If your board is the opposite, just swap these two lines.
#define RELAY_ON LOW
#define RELAY_OFF HIGH

/* ---------------------------------------------------------------------------
* THE STATE MACHINE
* Instead of dozens of tangled if-statements, the system is always in
* exactly ONE of four "states". Each loop, we look at the sensors and
* decide whether to move to a different state. This makes the logic easy
* to reason about and easy to extend.
* ------------------------------------------------------------------------ */
enum SystemState {
STATE_NORMAL, // dry, calm, nothing happening
STATE_RAIN, // rain detected OR soil saturated — early warning
STATE_WARNING, // water is physically rising on the floor
STATE_CRITICAL // flood level — power cut, evacuate
};
SystemState state = STATE_NORMAL; // we always start assuming it's dry

// SAFETY LATCH: once we reach CRITICAL and cut the power, this flag keeps
// us there even if the water drops again. Wet sockets must NEVER get power
// back automatically — a human must inspect and press the reset button.
bool criticalLatched = false;

// Track numbers of the MP3 files on the DFPlayer's SD card.

Flood Monitoring System - flood_monitor_explained.ino Page 2


// The SD card must contain a folder named "mp3" with files named EXACTLY:
// 0001.mp3, 0002.mp3, 0003.mp3
#define TRACK_LIGHT_RAIN 1 // "Light rain detected, stay alert"
#define TRACK_FLOOD_WARN 2 // "Flood warning, water level rising"
#define TRACK_EVACUATE 3 // "Evacuate now, evacuate now"
#define VOICE_REPEATS 3 // each message is played 3 times

/* ---------------------------------------------------------------------------
* GLOBAL OBJECTS AND VARIABLES
* "Global" = visible to every function in the file.
* ------------------------------------------------------------------------ */
SoftwareSerial dfSerial(PIN_DF_RX, PIN_DF_TX); // the wire to the DFPlayer
DFRobotDFPlayerMini dfPlayer; // the DFPlayer driver object
bool dfOk = false; // did the DFPlayer start OK?

// millis() returns how many milliseconds the board has been on. By storing
// the time of our last action and comparing, we can do things "every N
// seconds" WITHOUT using delay() — delay() freezes the whole program, which
// would make the buzzer and LED stutter.
unsigned long lastRead = 0; // when we last read the sensors
unsigned long lastUpload = 0; // when we last uploaded to the cloud
unsigned long lastBeep = 0; // when we last toggled the buzzer/LED
const unsigned long READ_INTERVAL = 2000; // read sensors every 2 s
const unsigned long UPLOAD_INTERVAL = 30000; // upload every 30 s
bool beepOn = false; // is the buzzer/LED currently on or off?

// The latest sensor readings, shared between functions:


float waterLevelCm = 0; // water depth above the floor, in cm
bool rainDetected = false; // true while the FC-37 senses rain
int soilRaw = 1024; // raw soil moisture (1024 = totally dry)

/* ===========================================================================
* setup() — runs ONCE at power-on
* ======================================================================== */
void setup() {
// Open the USB serial port so we can print debug messages to the
// computer (Tools > Serial Monitor, set to 115200 baud).
[Link](115200);

// Tell the chip which pins are inputs and which are outputs.
pinMode(PIN_TRIG, OUTPUT);
pinMode(PIN_ECHO, INPUT);
pinMode(PIN_RAIN, INPUT_PULLUP); // PULLUP = pin reads HIGH by default;
// the sensor pulls it LOW when wet
pinMode(PIN_RELAY_AC, OUTPUT);
pinMode(PIN_SIREN, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
pinMode(PIN_LED, OUTPUT);

/* FAIL-SAFE STARTING POSITION:


* Relay 1 starts OFF. The AC line is wired through the relay's
* NC (Normally Closed) contact, so with the relay OFF, power flows
* normally. We only ENERGIZE the relay to break the line and cut power.
* Why this direction? If the ESP8266 ever crashes or loses power, the
* building keeps its electricity — the system "fails safe". */
digitalWrite(PIN_RELAY_AC, RELAY_OFF);
digitalWrite(PIN_SIREN, RELAY_OFF);
digitalWrite(PIN_BUZZER, LOW);
digitalWrite(PIN_LED, HIGH); // remember: onboard LED is inverted, HIGH = off

// Start talking to the DFPlayer at 9600 baud (its fixed speed).


[Link](9600);
if ([Link](dfSerial)) {
dfOk = true;
[Link](28); // volume range is 0 (silent) to 30 (maximum)
[Link](F("DFPlayer ready"));
} else {
// If the SD card is missing or wiring is wrong, we don't crash —
// we just fall back to buzzer-only alerts.
[Link](F("DFPlayer FAILED - check SD card & wiring. Buzzer-only mode."));

Flood Monitoring System - flood_monitor_explained.ino Page 3


}

// Only bother connecting to Wi-Fi if cloud upload is enabled.


if (ENABLE_CLOUD) connectWiFi();

[Link](F("Flood monitoring system is running."));


}

/* ===========================================================================
* loop() — runs forever, as fast as the chip can go
* Notice there is NO delay() in here. Everything is timed with millis()
* so all three jobs (sensing, alerting, uploading) run "at the same time".
* ======================================================================== */
void loop() {
unsigned long now = millis(); // current time-since-boot in milliseconds

// JOB 1: every 2 seconds, read the sensors and update the state machine.
if (now - lastRead >= READ_INTERVAL) {
lastRead = now;
readSensors();
evaluateState();
logStatus();
}

// JOB 2: continuously run the LED/buzzer pattern for the current state.
runOutputs(now);

// JOB 3: every 30 seconds, send the readings to ThingSpeak (if enabled).


if (ENABLE_CLOUD && now - lastUpload >= UPLOAD_INTERVAL) {
lastUpload = now;
uploadThingSpeak();
}
}

/* ===========================================================================
* SENSOR FUNCTIONS
* ======================================================================== */

/* Reads the ultrasonic sensor and returns the distance in centimetres.


*
* HOW AN ULTRASONIC SENSOR WORKS:
* We send a 10-microsecond pulse on TRIG. The sensor fires a burst of
* sound (too high-pitched for humans to hear) which bounces off the
* water/floor and comes back. The ECHO pin stays HIGH for exactly as long
* as the sound took to make the round trip. Since sound travels at about
* 343 m/s (0.0343 cm per microsecond), the distance is:
* distance = (echo_time * 0.0343) / 2
* We divide by 2 because the sound travelled there AND back.
*
* We take THREE readings and use the MIDDLE one ("median"). This throws
* away occasional garbage readings caused by splashes or rain noise —
* one bad reading can't fool a median, but it would ruin an average. */
float readUltrasonicCm() {
float r[3]; // an array to hold the 3 readings
for (int i = 0; i < 3; i++) {
digitalWrite(PIN_TRIG, LOW); delayMicroseconds(2); // ensure clean start
digitalWrite(PIN_TRIG, HIGH); delayMicroseconds(10); // the 10 us trigger
digitalWrite(PIN_TRIG, LOW);
// pulseIn measures how long ECHO stays HIGH, giving up after 30 ms
// (30 ms = about 5 m, beyond the sensor's range) so we never hang forever.
unsigned long dur = pulseIn(PIN_ECHO, HIGH, 30000);
r[i] = (dur == 0) ? -1 : dur * 0.0343 / 2.0; // -1 means "no echo received"
delay(40); // small pause so echoes from one ping don't pollute the next
}
// Sort the 3 numbers smallest-to-largest (a tiny "bubble sort")...
for (int i = 0; i < 2; i++)
for (int j = i + 1; j < 3; j++)
if (r[j] < r[i]) { float t = r[i]; r[i] = r[j]; r[j] = t; }
return r[1]; // ...and return the middle one: the median.
}

Flood Monitoring System - flood_monitor_explained.ino Page 4


/* Reads all three sensors and stores the results in the global variables. */
void readSensors() {
float dist = readUltrasonicCm();

// Only accept the reading if it makes physical sense (a positive number


// not wildly larger than the mounting height). Otherwise we KEEP the
// previous good reading — during a flood, a broken sensor must not make
// the system think the water has vanished.
if (dist > 0 && dist <= MOUNT_HEIGHT_CM + 50) {
waterLevelCm = MOUNT_HEIGHT_CM - dist; // convert distance to depth
if (waterLevelCm < 0) waterLevelCm = 0; // can't have negative water
}

// FC-37: its onboard comparator pulls the digital pin LOW when the
// board detects water. Sensitivity is set with the small blue
// potentiometer screw on its driver board.
rainDetected = (digitalRead(PIN_RAIN) == LOW);

// analogRead returns 0-1023. For a capacitive soil sensor:


// dry soil ≈ 700-800, soaked soil/water ≈ 300-400.
soilRaw = analogRead(PIN_SOIL);
}

/* ===========================================================================
* THE BRAIN — deciding which state we should be in
* ======================================================================== */
void evaluateState() {
// Rule zero: once latched in CRITICAL, we never leave. Full stop.
if (criticalLatched) { state = STATE_CRITICAL; return; }

SystemState next = state; // assume we stay put unless a rule says otherwise
bool soilSaturated = (soilRaw < SOIL_WET_RAW);

switch (state) {

case STATE_NORMAL:
// From calm, we can jump straight to ANY higher stage — a flash
// flood won't politely pass through each stage in order.
if (waterLevelCm >= LEVEL_CRITICAL_CM) next = STATE_CRITICAL;
else if (waterLevelCm >= LEVEL_WARNING_CM) next = STATE_WARNING;
else if (rainDetected || soilSaturated) next = STATE_RAIN;
break;

case STATE_RAIN:
if (waterLevelCm >= LEVEL_CRITICAL_CM) next = STATE_CRITICAL;
else if (waterLevelCm >= LEVEL_WARNING_CM) next = STATE_WARNING;
// Only relax back to NORMAL when BOTH the rain has stopped AND the
// soil has drained — saturated ground can still flood after rain ends.
else if (!rainDetected && !soilSaturated) next = STATE_NORMAL;
break;

case STATE_WARNING:
if (waterLevelCm >= LEVEL_CRITICAL_CM) next = STATE_CRITICAL;
// Note the "- HYSTERESIS_CM": the water must fall a clear 3 cm
// below the warning line before we stand down. Without this, water
// sloshing at exactly 10.0 cm would switch the alarm on and off
// every two seconds.
else if (waterLevelCm < LEVEL_WARNING_CM - HYSTERESIS_CM)
next = (rainDetected || soilSaturated) ? STATE_RAIN : STATE_NORMAL;
break;

case STATE_CRITICAL:
break; // handled by the latch at the top — nothing to decide
}

// If the state CHANGED this cycle, run the one-time actions (play the
// voice alert, flip relays). We only do these on the moment of change,
// otherwise the voice clip would restart every 2 seconds forever.
if (next != state) onStateChange(state, next);

Flood Monitoring System - flood_monitor_explained.ino Page 5


state = next;
}

/* One-time actions performed at the moment we ENTER a new state. */


void onStateChange(SystemState from, SystemState to) {
switch (to) {

case STATE_RAIN:
playVoice(TRACK_LIGHT_RAIN); // "Light rain detected" x3
break;

case STATE_WARNING:
playVoice(TRACK_FLOOD_WARN); // "Flood warning" x3
break;

case STATE_CRITICAL:
criticalLatched = true; // lock the door behind us
digitalWrite(PIN_RELAY_AC, RELAY_ON); // energize relay 1 ->
// NC contact opens ->
// AC POWER IS CUT
digitalWrite(PIN_SIREN, RELAY_ON); // external siren screams
playVoice(TRACK_EVACUATE); // "Evacuate now" x3
[Link](F(">>> CRITICAL: MAINS CUT, SIREN ON, SYSTEM LATCHED <<<"));
break;

case STATE_NORMAL:
// Stand down all alert hardware when returning to calm.
digitalWrite(PIN_SIREN, RELAY_OFF);
digitalWrite(PIN_BUZZER, LOW);
digitalWrite(PIN_LED, HIGH); // inverted LED: HIGH = off
break;
}
}

/* Plays one MP3 track, repeated VOICE_REPEATS (3) times.


* The delay(4000) waits for the clip to finish before replaying — set it
* a little longer than your longest recorded message. */
void playVoice(int track) {
if (!dfOk) return; // speaker not working? skip — buzzer still covers us
for (int i = 0; i < VOICE_REPEATS; i++) {
dfPlayer.playMp3Folder(track); // plays /mp3/000<track>.mp3 from SD card
delay(4000);
}
}

/* ===========================================================================
* LED + BUZZER PATTERNS — runs every single loop, no delays
* Each state gets a distinct rhythm so even a deaf-and-mute system
* (broken speaker) still communicates urgency visually and audibly.
* ======================================================================== */
void runOutputs(unsigned long now) {
switch (state) {

case STATE_NORMAL:
break; // everything off, nothing to do

case STATE_RAIN:
// Gentle heartbeat: LED blinks once per second, no buzzer.
if (now - lastBeep >= 1000) {
lastBeep = now;
beepOn = !beepOn; // flip true<->false
digitalWrite(PIN_LED, beepOn ? LOW : HIGH);
}
break;

case STATE_WARNING:
// Urgent: fast triple-speed blink, buzzer chirping in sync.
if (now - lastBeep >= 300) {
lastBeep = now;
beepOn = !beepOn;

Flood Monitoring System - flood_monitor_explained.ino Page 6


digitalWrite(PIN_LED, beepOn ? LOW : HIGH);
digitalWrite(PIN_BUZZER, beepOn ? HIGH : LOW);
}
break;

case STATE_CRITICAL:
// Maximum alarm: LED solid on, buzzer screaming continuously.
digitalWrite(PIN_LED, LOW);
digitalWrite(PIN_BUZZER, HIGH);
break;
}
}

/* ===========================================================================
* CLOUD FUNCTIONS (only used when ENABLE_CLOUD is true)
* ======================================================================== */

/* Connects to the Wi-Fi network, but gives up after 15 seconds rather than
* waiting forever — the flood alerts matter more than the internet. */
void connectWiFi() {
[Link](WIFI_STA); // STA = "station" = normal client mode
[Link](WIFI_SSID, WIFI_PASS);
[Link](F("Connecting to WiFi"));
unsigned long t0 = millis();
while ([Link]() != WL_CONNECTED && millis() - t0 < 15000) {
delay(500);
[Link]('.');
}
[Link]([Link]() == WL_CONNECTED
? F(" connected!")
: F(" FAILED - running offline."));
}

/* Sends the four readings to ThingSpeak using a plain HTTP GET request.
* On [Link], make a free channel with 4 fields:
* field1 = water level (cm), field2 = rain (0/1),
* field3 = soil raw value, field4 = system state (0-3) */
void uploadThingSpeak() {
if ([Link]() != WL_CONNECTED) { connectWiFi(); return; }
WiFiClient client;
if (![Link]("[Link]", 80)) return; // server unreachable
String url = String("/update?api_key=") + TS_API_KEY +
"&field1=" + String(waterLevelCm, 1) +
"&field2=" + String(rainDetected ? 1 : 0) +
"&field3=" + String(soilRaw) +
"&field4=" + String((int)state);
[Link](String("GET ") + url +
" HTTP/1.1\r\nHost: [Link]\r\nConnection: close\r\n\r\n");
[Link]();
}

/* ===========================================================================
* DEBUG PRINTOUT — open the Serial Monitor (115200 baud) to watch this.
* Essential during calibration: you'll see the soil values and water level
* change live as you test with a bucket of water.
* ======================================================================== */
void logStatus() {
[Link](F("Water level: ")); [Link](waterLevelCm, 1);
[Link](F(" cm | Rain: ")); [Link](rainDetected ? F("YES") : F("no"));
[Link](F(" | Soil raw: ")); [Link](soilRaw);
[Link](F(" | State: "));
switch (state) {
case STATE_NORMAL: [Link](F("NORMAL")); break;
case STATE_RAIN: [Link](F("RAIN")); break;
case STATE_WARNING: [Link](F("WARNING")); break;
case STATE_CRITICAL: [Link](F("CRITICAL")); break;
}
}

Flood Monitoring System - flood_monitor_explained.ino Page 7

You might also like