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

Flood Monitor Python Code

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)
2 views7 pages

Flood Monitor Python Code

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

MicroPython source code for NodeMCU ESP8266 — save to the board as [Link]

# ============================================================================
# FLOOD MONITORING & AUTOMATIC POWER CUT-OFF SYSTEM
# MicroPython version for NodeMCU ESP8266
# ============================================================================
#
# 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) detects when the ground is
# so soaked ("saturated") that it cannot absorb more 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. 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. Readings are uploaded to ThingSpeak over Wi-Fi for remote monitoring.
#
# HOW TO PUT THIS ON THE NODEMCU:
# -------------------------------
# 1. Download the ESP8266 MicroPython firmware (.bin) from [Link]
# 2. Flash it once using esptool:
# pip install esptool
# esptool --port COM3 erase_flash
# esptool --port COM3 write_flash 0 [Link]
# 3. Install the Thonny editor ([Link]), select interpreter
# "MicroPython (ESP8266)", open this file and save it to the board
# as: [Link]
# (a file named [Link] runs automatically every time the board
# powers on - exactly what we need)
#
# HOW TO READ THIS FILE:
# ----------------------
# Everything above "MAIN PROGRAM" at the bottom is either a setting,
# a pin definition, or a function (a named block of code). The actual
# program is the while-True loop at the very bottom, which runs forever.
# ============================================================================

# ---------------------------------------------------------------------------
# LIBRARIES (all of these are built into MicroPython - nothing to install)
# ---------------------------------------------------------------------------
from machine import Pin, ADC, time_pulse_us, disable_irq, enable_irq
import time # for delays and timing
import network # for connecting to Wi-Fi
import socket # for talking to the ThingSpeak server

# ---------------------------------------------------------------------------
# USER SETTINGS - the ONLY values you should need to change
# ---------------------------------------------------------------------------
ENABLE_CLOUD = False # True = upload readings to ThingSpeak
WIFI_SSID = "YourWiFi" # your Wi-Fi network name
WIFI_PASS = "YourPassword" # your Wi-Fi password
TS_API_KEY = "YOUR_KEY_HERE" # from [Link] (free account)

# How high above the FLOOR the ultrasonic sensor is mounted, in cm.
# MEASURE THIS WITH A TAPE RULE after mounting. If the sensor hangs
# 250 cm above the floor and measures 240 cm to the surface below,

Flood Monitoring System - [Link] (MicroPython) Page 1


# the water is 250 - 240 = 10 cm deep.
MOUNT_HEIGHT_CM = 250.0

# Water depths (measured from the floor) that trigger each stage:
LEVEL_WARNING_CM = 10.0 # Stage 2 fires at 10 cm of water
LEVEL_CRITICAL_CM = 25.0 # Stage 3 fires at 25 cm (set BELOW your sockets!)

# Hysteresis stops the alarm flip-flopping when water hovers exactly at a


# threshold (small waves). Once WARNING is on, the water must drop a clear
# 3 cm below the line before the system stands down.
HYSTERESIS_CM = 3.0

# Soil moisture threshold (raw ADC 0-1023; capacitive sensor reads LOWER
# when WET). Calibrate: watch the printed values with the probe dry
# (about 700-800) and dipped in water (about 300-400), pick the midpoint.
SOIL_WET_RAW = 450

# ---------------------------------------------------------------------------
# PIN MAP
# MicroPython uses the chip's real GPIO numbers, not the "D" labels printed
# on the NodeMCU board. The translation is:
# D0=16 D1=5 D2=4 D3=0 D4=2 D5=14 D6=12 D7=13 D8=15 A0=ADC(0)
# ---------------------------------------------------------------------------
trig = Pin(5, [Link]) # D1 -> HC-SR04 Trig
echo = Pin(4, [Link]) # D2 -> HC-SR04 Echo
# !!! through a 1k + 2k voltage
# divider - Echo outputs 5V, the
# ESP8266 survives only 3.3V !!!
rain = Pin(0, [Link], Pin.PULL_UP) # D3 -> FC-37 digital out (LOW=wet)
df_tx = Pin(12, [Link], value=1) # D6 -> DFPlayer RX (1k in series)
# idle state of a serial line is HIGH
relay_ac = Pin(13, [Link], value=1) # D7 -> relay ch1 (AC cut-off)
siren = Pin(16, [Link], value=1) # D0 -> relay ch2 (external siren)
buzzer = Pin(15, [Link], value=0) # D8 -> active buzzer
led = Pin(2, [Link], value=1) # D4 -> red LED (onboard LED shares
# this pin and is INVERTED: 0 = on)
soil = ADC(0) # A0 -> soil moisture analog out

# Most cheap relay boards are ACTIVE LOW: writing 0 switches the relay ON.
# If yours is the opposite, swap these two numbers.
RELAY_ON = 0
RELAY_OFF = 1

# ---------------------------------------------------------------------------
# THE STATE MACHINE
# The system is always in exactly ONE of four named states. Each cycle we
# look at the sensors and decide whether to move to a different state.
# This keeps the logic clean instead of a tangle of if-statements.
# ---------------------------------------------------------------------------
STATE_NORMAL = 0 # dry, calm, nothing happening
STATE_RAIN = 1 # rain detected OR soil saturated - early warning
STATE_WARNING = 2 # water is physically rising on the floor
STATE_CRITICAL = 3 # flood level - power cut, evacuate

STATE_NAMES = ("NORMAL", "RAIN", "WARNING", "CRITICAL")

state = STATE_NORMAL # we start by assuming it is 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.
critical_latched = False

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

Flood Monitoring System - [Link] (MicroPython) Page 2


# The latest sensor readings, shared between functions:
water_level_cm = 0.0
rain_detected = False
soil_raw = 1024 # 1024 = totally dry

# ---------------------------------------------------------------------------
# DFPLAYER MINI DRIVER (written by hand)
#
# MicroPython has no DFPlayer library, but the module is controlled by
# simple 10-byte messages sent over a 9600-baud serial line. The frame is:
#
# byte 0: 0x7E start marker
# byte 1: 0xFF version
# byte 2: 0x06 length of the middle section
# byte 3: CMD the command (0x06 = set volume, 0x12 = play /mp3 track)
# byte 4: 0x00 "no reply needed"
# byte 5: ARG high byte \ the command's parameter,
# byte 6: ARG low byte / e.g. track number or volume
# byte 7: CHK high byte \ checksum = 0 minus the sum
# byte 8: CHK low byte / of bytes 1 to 6
# byte 9: 0xEF end marker
#
# The ESP8266's only full serial port is busy talking to the computer
# (that is how Thonny shows our print() messages), so instead we
# "bit-bang" the signal: we switch the TX pin high and low ourselves
# with exact 104-microsecond timing, which IS a 9600-baud serial signal.
# We only ever SEND to the DFPlayer, never listen, so this works fine.
# ---------------------------------------------------------------------------
BIT_US = 104 # one bit at 9600 baud lasts 1 / 9600 s = 104 microseconds

def _send_byte(b):
"""Send one byte as a software-serial frame: start bit, 8 data bits
(least-significant first), stop bit. Interrupts are paused so Wi-Fi
background work cannot stretch our bit timing."""
irq = disable_irq()
df_tx.value(0) # start bit (line drops LOW)
time.sleep_us(BIT_US)
for i in range(8): # 8 data bits, LSB first
df_tx.value((b >> i) & 1)
time.sleep_us(BIT_US)
df_tx.value(1) # stop bit (line returns HIGH)
time.sleep_us(BIT_US)
enable_irq(irq)

def df_command(cmd, arg):


"""Build a 10-byte DFPlayer frame for (cmd, arg) and send it."""
high = (arg >> 8) & 0xFF # split arg into two bytes
low = arg & 0xFF
checksum = 0 - (0xFF + 0x06 + cmd + 0x00 + high + low)
checksum &= 0xFFFF # keep it inside 16 bits
frame = bytes([0x7E, 0xFF, 0x06, cmd, 0x00, high, low,
(checksum >> 8) & 0xFF, checksum & 0xFF, 0xEF])
for b in frame:
_send_byte(b)
time.sleep_ms(30) # give the module a breath

def df_set_volume(vol):
df_command(0x06, vol) # 0x06 = set volume, range 0-30

def df_play_mp3(track):
df_command(0x12, track) # 0x12 = play /mp3/000N.mp3 from SD card

def play_voice(track):
"""Play one voice message, repeated VOICE_REPEATS (3) times.
The 4-second pause lets each clip finish - make it a little longer
than your longest recording."""
for _ in range(VOICE_REPEATS):
df_play_mp3(track)
[Link](4)

Flood Monitoring System - [Link] (MicroPython) Page 3


# ---------------------------------------------------------------------------
# SENSOR FUNCTIONS
# ---------------------------------------------------------------------------
def read_ultrasonic_cm():
"""Measure distance with the HC-SR04 and return centimetres.

HOW IT WORKS: we pulse Trig HIGH for 10 microseconds. The sensor fires
a burst of sound (too high-pitched to hear) that bounces off the water
or floor and returns. The Echo pin stays HIGH for exactly the round-trip
time. Sound travels 0.0343 cm per microsecond, so:
distance = (echo_time * 0.0343) / 2 (divide by 2: there AND back)

We take THREE readings and keep the MIDDLE one (the median). A single
splash can ruin an average, but it cannot fool a median."""
readings = []
for _ in range(3):
[Link](0)
time.sleep_us(2)
[Link](1)
time.sleep_us(10) # the 10-microsecond trigger pulse
[Link](0)
# time_pulse_us measures how long Echo stays HIGH; we give up
# after 30000 us (about 5 m, beyond the sensor's range) so a
# missing echo can never freeze the program. It returns a
# negative number on timeout.
duration = time_pulse_us(echo, 1, 30000)
if duration > 0:
[Link](duration * 0.0343 / 2)
time.sleep_ms(40) # let old echoes die before the next ping
if not readings:
return -1 # -1 means "no valid echo this round"
[Link]()
return readings[len(readings) // 2] # the median

def read_sensors():
"""Read all sensors and update the shared variables."""
global water_level_cm, rain_detected, soil_raw

dist = read_ultrasonic_cm()
# Only accept the reading if it makes physical sense. Otherwise KEEP
# the previous good value - during a flood, a glitching sensor must
# not make the system believe the water has vanished.
if 0 < dist <= MOUNT_HEIGHT_CM + 50:
water_level_cm = max(0.0, MOUNT_HEIGHT_CM - dist)

# FC-37 pulls its digital pin LOW when the plate is wet. Sensitivity
# is set with the small potentiometer screw on its driver board.
rain_detected = ([Link]() == 0)

# ADC returns 0-1023. Capacitive soil sensor: dry soil reads about
# 700-800, soaked soil/water about 300-400.
soil_raw = [Link]()

# ---------------------------------------------------------------------------
# THE BRAIN - deciding which state we should be in
# ---------------------------------------------------------------------------
def evaluate_state():
global state, critical_latched

# Rule zero: once latched in CRITICAL we never leave. Full stop.


if critical_latched:
state = STATE_CRITICAL
return

nxt = state # assume we stay unless a rule fires


soil_saturated = (soil_raw < SOIL_WET_RAW)

if state == STATE_NORMAL:
# From calm we may jump straight to ANY higher stage - a flash
# flood will not politely pass through each stage in order.

Flood Monitoring System - [Link] (MicroPython) Page 4


if water_level_cm >= LEVEL_CRITICAL_CM:
nxt = STATE_CRITICAL
elif water_level_cm >= LEVEL_WARNING_CM:
nxt = STATE_WARNING
elif rain_detected or soil_saturated:
nxt = STATE_RAIN

elif state == STATE_RAIN:


if water_level_cm >= LEVEL_CRITICAL_CM:
nxt = STATE_CRITICAL
elif water_level_cm >= LEVEL_WARNING_CM:
nxt = STATE_WARNING
# Only relax to NORMAL when BOTH rain has stopped AND the soil
# has drained - saturated ground can still flood after rain ends.
elif not rain_detected and not soil_saturated:
nxt = STATE_NORMAL

elif state == STATE_WARNING:


if water_level_cm >= LEVEL_CRITICAL_CM:
nxt = STATE_CRITICAL
# Note the "- HYSTERESIS_CM": water must fall a clear 3 cm below
# the warning line before standing down, or sloshing water at
# exactly 10.0 cm would switch the alarm on/off every cycle.
elif water_level_cm < LEVEL_WARNING_CM - HYSTERESIS_CM:
nxt = STATE_RAIN if (rain_detected or soil_saturated) else STATE_NORMAL

# If the state CHANGED, run the one-time entry actions (voice alert,
# relays). Only on the moment of change - otherwise the voice clip
# would restart every cycle forever.
if nxt != state:
on_state_change(nxt)
state = nxt

def on_state_change(to):
"""One-time actions performed at the moment we ENTER a new state."""
global critical_latched

if to == STATE_RAIN:
play_voice(TRACK_LIGHT_RAIN) # "Light rain detected" x3

elif to == STATE_WARNING:
play_voice(TRACK_FLOOD_WARN) # "Flood warning" x3

elif to == STATE_CRITICAL:
critical_latched = True # lock the door behind us
relay_ac.value(RELAY_ON) # energize relay 1 ->
# NC contact opens ->
# AC POWER IS CUT
[Link](RELAY_ON) # external siren screams
play_voice(TRACK_EVACUATE) # "Evacuate now" x3
print(">>> CRITICAL: POWER CUT, SIREN ON, SYSTEM LATCHED <<<")

elif to == STATE_NORMAL:
# Stand down all alert hardware when returning to calm.
[Link](RELAY_OFF)
[Link](0)
[Link](1) # inverted LED: 1 = off

# ---------------------------------------------------------------------------
# LED + BUZZER PATTERNS - called every cycle, no blocking delays
# Each state gets a distinct rhythm, so even with a broken speaker the
# system still communicates urgency by sight and sound.
# ---------------------------------------------------------------------------
beep_on = False
last_beep = 0

def run_outputs(now_ms):
global beep_on, last_beep

if state == STATE_RAIN:

Flood Monitoring System - [Link] (MicroPython) Page 5


# Gentle heartbeat: LED blinks once per second, no buzzer.
if time.ticks_diff(now_ms, last_beep) >= 1000:
last_beep = now_ms
beep_on = not beep_on
[Link](0 if beep_on else 1)

elif state == STATE_WARNING:


# Urgent: fast blink, buzzer chirping in sync.
if time.ticks_diff(now_ms, last_beep) >= 300:
last_beep = now_ms
beep_on = not beep_on
[Link](0 if beep_on else 1)
[Link](1 if beep_on else 0)

elif state == STATE_CRITICAL:


# Maximum alarm: LED solid on, buzzer screaming continuously.
[Link](0)
[Link](1)

# ---------------------------------------------------------------------------
# CLOUD FUNCTIONS (only used when ENABLE_CLOUD is True)
# ---------------------------------------------------------------------------
def connect_wifi():
"""Join the Wi-Fi network, giving up after 15 seconds - the flood
alerts matter more than the internet."""
wlan = [Link](network.STA_IF) # STA = normal client mode
[Link](True)
if [Link]():
return wlan
print("Connecting to WiFi", end="")
[Link](WIFI_SSID, WIFI_PASS)
t0 = time.ticks_ms()
while not [Link]() and time.ticks_diff(time.ticks_ms(), t0) < 15000:
time.sleep_ms(500)
print(".", end="")
print(" connected!" if [Link]() else " FAILED - running offline.")
return wlan

def upload_thingspeak():
"""Send the four readings to ThingSpeak with 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)"""
try:
wlan = connect_wifi()
if not [Link]():
return
addr = [Link]("[Link]", 80)[0][-1]
s = [Link]()
[Link](5)
[Link](addr)
path = ("/update?api_key=" + TS_API_KEY +
"&field1=" + str(round(water_level_cm, 1)) +
"&field2=" + ("1" if rain_detected else "0") +
"&field3=" + str(soil_raw) +
"&field4=" + str(state))
[Link](b"GET " + [Link]() +
b" HTTP/1.1\r\nHost: [Link]\r\nConnection: close\r\n\r\n")
[Link]()
except Exception as e:
# Any network hiccup is printed and ignored - it must NEVER be
# allowed to crash the safety system.
print("Upload failed:", e)

# ---------------------------------------------------------------------------
# DEBUG PRINTOUT - watch this live in Thonny's shell. Essential during
# calibration: you will see soil values and water level change as you
# test with a cup and a bucket of water.
# ---------------------------------------------------------------------------
def log_status():

Flood Monitoring System - [Link] (MicroPython) Page 6


print("Water level: %.1f cm | Rain: %s | Soil raw: %d | State: %s" %
(water_level_cm, "YES" if rain_detected else "no",
soil_raw, STATE_NAMES[state]))

# ===========================================================================
# MAIN PROGRAM - runs forever
# Three jobs share the loop without ever blocking each other:
# every 2 s read sensors + update the state machine
# every cycle run the LED/buzzer pattern for the current state
# every 30 s upload to the cloud (if enabled)
# We time things with ticks_ms() instead of long sleeps, so the buzzer
# and LED patterns stay smooth.
# ===========================================================================
print("Flood monitoring system starting...")
df_set_volume(28) # speaker volume, 0 (mute) to 30 (max)
if ENABLE_CLOUD:
connect_wifi()
print("Running.")

last_read = 0
last_upload = 0
READ_INTERVAL_MS = 2000
UPLOAD_INTERVAL_MS = 30000

while True:
now = time.ticks_ms() # milliseconds since power-on

# JOB 1: sensors + state machine, every 2 seconds


if time.ticks_diff(now, last_read) >= READ_INTERVAL_MS:
last_read = now
read_sensors()
evaluate_state()
log_status()

# JOB 2: alert patterns, every single cycle


run_outputs(now)

# JOB 3: cloud upload, every 30 seconds


if ENABLE_CLOUD and time.ticks_diff(now, last_upload) >= UPLOAD_INTERVAL_MS:
last_upload = now
upload_thingspeak()

time.sleep_ms(20) # tiny rest - keeps Wi-Fi happy and


# stops the loop hogging the chip

Flood Monitoring System - [Link] (MicroPython) Page 7

You might also like