DIY IoT Rain Gauge & Weather Monitoring System Using Raspberry Pi Pico W

by Hemasangari in Circuits > Raspberry Pi

129 Views, 0 Favorites, 0 Comments

DIY IoT Rain Gauge & Weather Monitoring System Using Raspberry Pi Pico W

whole project.jpg
whole project.jpg
whole project(pvc pipe).jpg

This project presents a DIY IoT-based environmental monitoring system designed for outdoor campus deployment. The system measures rainfall, air temperature, humidity, and water temperature using a combination of off-the-shelf sensors and a custom-built tipping bucket rain gauge.

Rainfall is detected using a mechanical tipping bucket mechanism combined with a Hall effect sensor and processed by a Raspberry Pi Pico W. All sensor data is transmitted wirelessly to the cloud and visualised on a custom ThingSpeak dashboard, allowing real-time and remote monitoring.

Sensor readings are recorded automatically at 30-minute intervals to ensure stable operation while conserving power. This project was developed as part of an IoT Computing group project and provides clear, step-by-step documentation so that other students can easily replicate, modify, and deploy the system for environmental monitoring applications.

Supplies

To build this DIY IoT rain gauge system, you will need a combination of electronic components, mechanical parts, and basic tools for assembly and deployment.

Electronics

  1. Raspberry Pi Pico W (used for sensor reading and wireless data transmission)
  2. DHT22 Temperature and Humidity Sensor
  3. DS18B20 Waterproof Temperature Sensor
  4. Hall Effect Sensor Module (used to detect tipping bucket movement)
  5. Neodymium Magnet (attached to the tipping bucket)
  6. Breadboard
  7. Jumper Wires (Male–Male / Male–Female)
  8. USB Cable (for programming the Pico W)
  9. Portable Power Bank (used as the power source)

Hardware & Enclosure

  1. Weatherproof PVC Enclosure (to house the electronics)
  2. Cut Plastic Bottle (used as a protective cover for the water temperature sensor connection)
  3. 3D-Printed Tipping Bucket Rain Gauge Housing
  4. Funnel (to collect rainwater)
  5. Electrical Tape (used for insulating and bundling wires, and for extending cable length)
  6. Plastic Container or Housing (for rain gauge body)
  7. PVC Pipe (to protect the water temperature sensor cable)
  8. Screws, Nuts, and Washers (for mounting the rain gauge)
  9. Glue (for cable sealing and waterproofing)

Tools

  1. Drill (for making mounting and cable entry holes)
  2. Screwdrivers
  3. Hot Glue Gun

The System Architecture (IoT Flow)

This system follows a complete end-to-end IoT architecture consisting of three main layers: sensing, communication, and application.


At the sensing layer, multiple sensors are used to measure rainfall, air temperature, humidity, and water temperature. The Raspberry Pi Pico W collects data from these sensors at fixed intervals.


At the communication layer, the Pico W sends sensor data wirelessly to the cloud using its built-in Wi-Fi capability.


At the application layer, ThingSpeak is used to store and visualise the data on a custom-designed dashboard that can be accessed remotely.


The figure below illustrates the complete working flow of the system, from rainfall collection and bucket tipping to sensor detection, data processing, and cloud visualisation.


Environmental Parameters Collected

water temperature sensor.jpeg
Humidity and Temperature Sensor.jpeg
Screenshot 2026-01-10 140728.png

The system collects four parameters:

  1. Water Temperature (DS18B20)
  2. Air Temperature & Humidity (DHT22)
  3. Rainfall (DIY tipping bucket + Hall effect sensor)

All data is recorded every 30 minutes and transmitted to the cloud.

Images above show the actual sensors used for air temperature, humidity, water temperature, and rainfall detection.

Circuit Wiring Using Raspberry Pi Pico W

Circuit 1.jpg
Circuit2.jpg
circuit3.jpg

All sensors share a common 3.3V (3V3) and GND to reduce wiring complexity and ensure stable voltage. Separate GPIOs are used for each signal to avoid interference and simplify debugging.

  1. DHT22 → GPIO 15 (air temp & humidity)
  2. DS18B20 → GPIO 2 (water temperature)
  3. Hall Effect Sensor → GPIO 0 (rainfall pulses)

Wiring photos show Pico placement, jumper routing, and connections. Use them together with the details below to reproduce the circuit accurately.


Wiring Details:

Rain Gauge (Hall Effect Sensor)

  1. Signal: GPIO 0 (Pin 1) – Yellow wire
  2. VCC: 3.3V (Pin 30) – Red wire
  3. GND: Pin 25 – Black wire


DHT22 (Air Temperature & Humidity)

  1. Data: GPIO 15 (Pin 20) – White/Yellow wire
  2. VCC: 3.3V (Pin 30) – Red wire
  3. GND: Pin 25 – Black wire


DS18B20 (Water Temperature)

  1. Data: GPIO 2 (Pin 4) – Yellow wire
  2. VCC: 3.3V (Pin 30) – Red wire
  3. GND: Pin 18 – Black wire

How the Tipping Bucket Rain Gauge Works

Screenshot 2026-01-09 162356.png
Screenshot 2026-01-10 152758.png

The rain gauge used in this project operates based on the tipping bucket principle. Rainwater is collected through the funnel (Part 1) and directed into a small dual-chamber tipping bucket mechanism (Part 2) located inside the rain gauge housing.

  1. As rainwater accumulates in one side of the tipping bucket (Part 2), its weight gradually increases.
  2. Once a fixed volume of water is reached, the bucket tips to the opposite side and empties the collected water.
  3. This tipping action resets the bucket and allows the next measurement cycle to begin.

Each bucket tip represents a known amount of rainfall.

  1. By counting the number of tipping events over time, the system is able to calculate total rainfall in millimetres.
  2. The calculated rainfall data is then transmitted and displayed on the dashboard.

As shown in the exploded diagram above, the rain gauge consists of the following main components:

  1. Funnel (Part 1) – Collects rainwater and directs it into the tipping bucket.
  2. Tipping bucket mechanism (Part 2) – Alternates between two chambers to measure rainfall volume.
  3. Protective grid (Part 3) – Prevents leaves, insects, and debris from entering the funnel.
  4. Housing (Part 4) – Encloses the internal mechanism and provides structural support.
  5. Sensor cable – Connects the internal Hall effect sensor to the microcontroller.

This numbered component layout allows the rain gauge to be easily assembled, understood, and replicated by others following this documentation.

Magnetic Detection Using a Hall Effect Sensor

Hall Effect.jpeg

Instead of using a mechanical reed switch, this project uses a Hall effect sensor to detect the movement of the tipping bucket. A small magnet is attached to the bucket, and the Hall effect sensor is mounted inside the rain gauge housing along the path of the magnet.

When the bucket tips, the magnet passes close to the Hall effect sensor and produces a digital output pulse. This pulse is detected by the Raspberry Pi Pico W and counted as a single rainfall event. Each detected pulse corresponds to one bucket tip.

Using a Hall effect sensor improves system durability by eliminating mechanical contact and reducing wear. This makes the rain gauge more reliable and suitable for long-term outdoor deployment. The image above shows the actual Hall effect sensor module used to detect the tipping motion of the rain gauge bucket.

Assembling the DIY Rain Gauge

Assembly1.jpeg
Assembly2.jpeg
Screenshot 2026-01-09 135926.png

The rain gauge assembly begins by positioning the tipping bucket mechanism securely inside the rain gauge housing.

  1. The bucket is checked to ensure it can move freely and tip smoothly without any obstruction, as accurate movement is essential for reliable rainfall measurement.

Next, the Hall effect sensor is fixed inside the housing at a position where the magnet attached to the tipping bucket passes closely during each tip.

  1. The distance between the magnet and the sensor is carefully adjusted to ensure consistent and reliable pulse detection without physical contact.

Once the internal components are secured, the rain gauge housing is placed on top of the weatherproof enclosure to determine the correct mounting position.

  1. Mounting points are marked, and mounting holes are drilled on the enclosure lid.

The rain gauge housing is then secured to the enclosure using screws.

  1. All screws are tightened firmly to ensure the housing remains stable and does not shift due to wind or heavy rainfall during outdoor deployment.

This mounting method provides both mechanical strength and long-term durability for outdoor use.

Weatherproof Enclosure Design

Screenshot 2026-01-09 162404.png

To ensure reliable outdoor operation, all electronic components are housed inside a weatherproof PVC enclosure. Cable entry points are carefully sealed using glue and to prevent water ingress and protect the internal electronics.

The rain gauge is mounted on top of the enclosure to allow direct and unobstructed rainfall collection. The water temperature sensor cable is routed through a PVC pipe, which provides additional protection against physical damage and environmental exposure.

A cut plastic bottle is used as a protective cover for the water temperature sensor connection. The bottle helps shield the sensor wiring and connector from direct exposure to water, dirt, and debris while still allowing the sensor probe to remain in contact with the water.

Using a recycled plastic bottle provides a lightweight, waterproof, and low-cost solution that is easy to modify and replace if needed. This approach also improves durability during outdoor deployment without adding significant cost to the system.


The image above shows the PVC electrical enclosure box used to house the Raspberry Pi Pico W and other electronic components. An IP-rated PVC enclosure was selected to provide protection against dust and water, ensuring long-term durability and safe outdoor operation of the system.

Temperature Sensor Calibration

Initial testing showed the DHT22 reading higher than local ambient values from a weather app. A constant −20 °C offset was applied in code to correct this systematic error.

This practical calibration is sufficient for relative environmental monitoring, which is the project’s objective, rather than laboratory-grade accuracy.


# --- B. Measure DHT22 ---
air_temp = 0.0
air_hum = 0.0
try:
dht_sensor.measure()
# ALERT: You are subtracting 20C. Ensure this is intentional!
air_temp = dht_sensor.temperature() - 20.0
air_hum = dht_sensor.humidity()
except Exception as e:
print("DHT Error:", e)


ThingSpeak Configuration

WhatsApp Image 2026-01-09 at 11.04.54.jpeg
WhatsApp Image 2026-01-10 at 15.12.43.jpeg

ThingSpeak was used as the cloud platform for data storage and visualisation. A custom dashboard was designed with a focus on clear UI/UX to make the data easy to understand.


This is step by step to connect with ThingSpeak :


  1. Log into the ThingSpeak account.
  2. Create new channel and name after the team's name. Add some description (if needed).
  3. Add the field of parameters to be measured. In this case, we are measuring:

Field 1: Temperature

Field 2: Humidity

Field 3: Water Temperature

Field 4: Rainfall

Field 5 : Master Switch (to turn off and on the switch, optional)

  1. Save the channel.
  2. Get the API keys for setting on Thonny coding and HTML dashboard coding.


The data will be represented in graph which x-line is time recorded and y-line is the data measurement numbers.

Programming the System Using Thonny

The Raspberry Pi Pico W was programmed using MicroPython through the Thonny IDE, chosen for its direct MicroPython support and ease of debugging and deployment.

Core Program Functions

The program performs the following functions:

  1. Reads data from all connected sensors
  2. Counts rainfall events from the tipping bucket rain gauge using an interrupt-based approach
  3. Applies a manual temperature calibration offset (−20.0 °C) to the air temperature readings
  4. Uploads sensor data to ThingSpeak using HTTP requests
  5. Sends data at 30-minute intervals to balance power consumption and data resolution

Wi-Fi Configuration and Connectivity

The Pico W connects to a local Wi-Fi network to enable cloud communication with ThingSpeak.

  1. Wi-Fi credentials are defined in the configuration section of the code
  2. A stable Wi-Fi connection is required to prevent data loss
  3. The on-board LED turns on when the Pico W is successfully connected to Wi-Fi

If Wi-Fi connectivity is lost during operation, the system attempts to reconnect automatically. If reconnection fails, the Pico W performs a full reset to restore network functionality.

Autonomous Operation and Reliability

This firmware turns the Pico W into a fully autonomous environmental monitoring system designed for long-term outdoor deployment.

  1. The script runs continuously in a main loop that manages:
  2. Wi-Fi connectivity
  3. Sensor acquisition
  4. Data upload to ThingSpeak
  5. On startup, the system:
  6. Waits briefly for stabilization
  7. Initializes all sensors
  8. Starts a Watchdog Timer (WDT)

Watchdog Timer (Fail-Safe Mechanism)

A Watchdog Timer with an 8-second timeout is used as a fail-safe mechanism.

  1. The watchdog must be regularly “fed” by the program
  2. If the program freezes, encounters a network error, or becomes unresponsive, the watchdog automatically resets the Pico W
  3. This prevents the system from remaining stuck during long unattended deployments

ThingSpeak Integration

ThingSpeak is used for cloud data storage and visualization.

The following credentials are required:

  1. Channel ID
  2. Read API Key
  3. Write API Key
  4. The Channel ID is located at the top of the ThingSpeak channel page
  5. The API keys are found in the API Keys section
  6. All sensor readings are uploaded and recorded on the ThingSpeak platform

Sensor Data Acquisition Method

Two different data acquisition methods are used, depending on sensor type.

Rain Gauge (Interrupt-Type Sensor)

  1. The rain gauge is connected to an interrupt pin
  2. Each bucket tip immediately triggers an interrupt
  3. Rainfall is counted in the background without blocking the main program
  4. This ensures no rainfall events are missed, even during data uploads

Temperature Sensors (Polled-Type Sensors)

  1. DHT22 is used for air temperature and humidity
  2. DS18B20 is used for water temperature
  3. These sensors are read sequentially during each measurement cycle

A manual calibration offset of −20.0 °C is applied to the air temperature readings to compensate for internal enclosure heating effects.

Remote Control (Master Switch)

A remote Master Switch is implemented using ThingSpeak.

  1. Before collecting sensor data, the Pico W checks a designated ThingSpeak field
  2. If the field value is:
  3. 1 → The system measures sensors and uploads data
  4. 0 → The system enters standby mode

This allows the system to be remotely enabled or disabled during maintenance periods or dry seasons without physical access.

Smart Sleep Routine (Power Conservation)

To conserve power without triggering the Watchdog Timer:

  1. The 30-minute sleep interval is divided into 5-second sleep cycles
  2. During each cycle, the system:
  3. Feeds the Watchdog Timer
  4. Briefly wakes up
  5. Blinks the status LED
  6. Returns to sleep

This approach allows stable, long-term operation while preventing watchdog resets during extended sleep periods.

Error Handling and System Recovery

If a critical error occurs inside the main loop:

  1. The system waits briefly
  2. A full system reset is triggered using machine.reset()

This ensures the Pico W can recover automatically from unexpected runtime failures.

import network
import time
import machine
import urequests
from machine import Pin, WDT
import dht
import onewire, ds18x20
import gc # Garbage Collection

# ==========================================
# 🔧 CONFIGURATION
# ==========================================
SSID = 'WIFI_NAME'
PASSWORD = 'WIFI_PASSWORD'

# ThingSpeak Settings
CHANNEL_ID = 'YOUR_CHANNEL_ID'
READ_API_KEY = 'YOUR_READ_API_KEY'
WRITE_API_KEY = 'YOUR_WRITE_API_KEY'
THINGSPEAK_URL = 'http://api.thingspeak.com/update'

# --- SENSOR CONFIG ---
RAIN_PIN = 0
CALIBRATION_FACTOR = 1.42
DHT_PIN = 15
DS_PIN = 2
SLEEP_INTERVAL = 1800 # 30 Minutes in seconds

# ==========================================
# ⚙️ SENSOR SETUP
# ==========================================

# 1. Rain Gauge
rain_sensor = Pin(RAIN_PIN, Pin.IN, Pin.PULL_UP)
rain_tips = 0
last_rain_time = 0

def rain_interrupt(pin):
global rain_tips, last_rain_time
current_time = time.ticks_ms()
if time.ticks_diff(current_time, last_rain_time) > 100:
rain_tips += 1
last_rain_time = current_time

rain_sensor.irq(trigger=Pin.IRQ_FALLING, handler=rain_interrupt)

# 2. DHT22 (Air)
dht_sensor = dht.DHT22(Pin(DHT_PIN))

# 3. DS18B20 (Water)
ds_pin = Pin(DS_PIN)
ds_sensor = ds18x20.DS18X20(onewire.OneWire(ds_pin))
roms = []
try:
roms = ds_sensor.scan()
except Exception as e:
print("DS18B20 Scan Error:", e)

# ==========================================
# 📡 WIFI CONNECTION
# ==========================================
def connect_wifi():
led = machine.Pin("LED", machine.Pin.OUT)
led.off()

wlan = network.WLAN(network.STA_IF)
wlan.active(True)

# Clean disconnect if needed
if wlan.isconnected():
wlan.disconnect()

print("Connecting to WiFi...", end="")
wlan.connect(SSID, PASSWORD)

# Wait for connection with timeout
max_wait = 20
while max_wait > 0:
if wlan.isconnected():
break
max_wait -= 1
time.sleep(1)
print(".", end="")

if wlan.isconnected():
print("\nWiFi Connected! IP:", wlan.ifconfig()[0])
led.on()
return True
else:
print("\nWiFi Connection Failed!")
led.off()
return False

# ==========================================
# 🔄 MAIN LOOP
# ==========================================
time.sleep(2)
print("System Started.")

# Initialize Watchdog Timer (Timeout = 8 seconds)
# NOTE: You MUST feed this every <8 seconds or the board resets.
wdt = WDT(timeout=8000)

while True:
try:
wdt.feed() # Feed dog at start of loop
gc.collect() # Clean up RAM

# Check WiFi
wlan = network.WLAN(network.STA_IF)
if not wlan.isconnected():
print("WiFi lost! Attempting reconnect...")
if not connect_wifi():
print("Reconnect failed. Resetting board...")
time.sleep(2)
machine.reset() # Hard reset fixes stuck WiFi chips

# ------------------------------------------------
# STEP 1: CHECK MASTER SWITCH
# ------------------------------------------------
print("\nChecking Master Switch...")

# Use a short timeout logic manually if possible, strictly feed WDT
check_url = f"https://api.thingspeak.com/channels/{CHANNEL_ID}/fields/5/last.json?api_key={READ_API_KEY}"

system_status = 0

try:
wdt.feed() # Feed before network request
resp = urequests.get(check_url)
data = resp.json()
resp.close()

if isinstance(data, dict):
# Safely convert to int
val = data.get('field5', '0')
system_status = int(val) if val is not None else 0
else:
system_status = 0

except Exception as e:
print("Status Check Failed (Network Error):", e)
system_status = 0

# ------------------------------------------------
# STEP 2: ACT BASED ON STATUS
# ------------------------------------------------
if system_status == 1:
wdt.feed()
print(">>> STATUS: ON. Measuring sensors...")

# --- A. Measure Rain ---
rainfall_mm = rain_tips * CALIBRATION_FACTOR
print(f"Rain Tips: {rain_tips} | Rainfall: {rainfall_mm:.2f} mm")
rain_tips = 0

# --- B. Measure DHT22 ---
air_temp = 0.0
air_hum = 0.0
try:
dht_sensor.measure()
# ALERT: You are subtracting 20C. Ensure this is intentional!
air_temp = dht_sensor.temperature() - 20.0
air_hum = dht_sensor.humidity()
except Exception as e:
print("DHT Error:", e)

# --- C. Measure DS18B20 ---
water_temp = 0.0
try:
if roms:
ds_sensor.convert_temp()
time.sleep_ms(750)
water_temp = ds_sensor.read_temp(roms[0])
except Exception as e:
print("DS18B20 Read Error:", e)

# --- D. Upload Data ---
print("Uploading data...")
request_url = (
f"{THINGSPEAK_URL}?api_key={WRITE_API_KEY}"
f"&field1={water_temp}&field2={air_temp}"
f"&field3={air_hum}&field4={rainfall_mm}"
)

try:
wdt.feed() # Feed before upload
resp = urequests.get(request_url)
print(f"Upload Success: {resp.text}")
resp.close()
except Exception as e:
print("Upload Failed:", e)

# --- E. SMART SLEEP (Feeding Watchdog) ---
print(f"Sleeping for {SLEEP_INTERVAL} seconds...")

# Divide sleep into 5-second chunks to keep Watchdog happy
cycles = SLEEP_INTERVAL // 5
for _ in range(cycles):
wdt.feed() # Keep system alive
time.sleep(5)

# Optional: Blink LED briefly to show it's alive
machine.Pin("LED", machine.Pin.OUT).toggle()
time.sleep(0.1)
machine.Pin("LED", machine.Pin.OUT).toggle()

else:
print(">>> STATUS: OFF. Standby.")
wdt.feed()
time.sleep(5)

except Exception as e:
print("Critical Loop Error:", e)
# If we hit a critical error, wait 10s then reset
time.sleep(10)
machine.reset()

Custom Dashboard

Screenshot 2026-01-09 105924.png

The dashboard displays system status, water temperature, ambient temperature, humidity, and rainfall rate. All values are updated automatically every 30 minutes and can be viewed remotely using a web browser or mobile device.


Full HTML code for dashboard :

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HemaRangers IOT Station</title>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;800&family=Orbitron:wght@500;700;900&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">

<style>
:root {
--skz-bg-start: #0f0c29;
--skz-bg-mid: #302b63;
--skz-bg-end: #24243e;
--skz-accent-green: #B7E632;
--skz-accent-pink: #FF0055;
--glass-bg: rgba(20, 20, 20, 0.85);
--glass-border: rgba(255, 255, 255, 0.1);
--text-main: #ffffff;
--text-dim: #a0a0a0;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: linear-gradient(135deg, var(--skz-bg-start), var(--skz-bg-mid), var(--skz-bg-end));
min-height: 100vh;
font-family: 'Montserrat', sans-serif;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
color: var(--text-main);
}
.dashboard-wrapper {
width: 100%;
max-width: 900px;
background: var(--glass-bg);
backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 50px rgba(0,0,0,0.5);
position: relative;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
border-bottom: 2px solid var(--skz-accent-green);
padding-bottom: 15px;
}
.brand-title {
font-family: 'Orbitron', sans-serif;
font-size: 1.5rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.brand-title i { color: var(--skz-accent-green); margin-right: 10px; }

/* NEW: Timestamp Style */
.last-update-text {
display: block;
font-size: 0.75rem;
color: var(--text-dim);
margin-top: 5px;
font-weight: 600;
}

.report-btn {
background: transparent;
border: 1px solid var(--text-dim);
color: var(--text-dim);
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-family: 'Orbitron', sans-serif;
font-size: 0.7rem;
transition: all 0.3s ease;
}
.report-btn:hover {
border-color: var(--skz-accent-green);
color: var(--skz-accent-green);
}
.control-section {
display: flex;
justify-content: center;
margin-bottom: 30px;
}
.toggle-wrapper {
display: flex;
align-items: center;
gap: 15px;
background: rgba(255,255,255,0.05);
padding: 15px 30px;
border-radius: 50px;
border: 1px solid rgba(255,255,255,0.1);
}
.status-label { font-family: 'Orbitron', sans-serif; font-size: 1rem; color: var(--text-main); }
.toggle-switch {
position: relative;
width: 60px;
height: 30px;
background: #333;
border-radius: 30px;
cursor: pointer;
transition: background 0.3s;
}
.toggle-switch::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 26px;
height: 26px;
background: #fff;
border-radius: 50%;
transition: transform 0.3s;
}
.toggle-active { background: var(--skz-accent-green); }
.toggle-active::after { transform: translateX(30px); }
.toggle-inactive { background: var(--skz-accent-pink); }
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
.card {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 12px;
padding: 20px;
text-align: center;
}
.card-header {
color: var(--text-dim);
font-size: 0.8rem;
text-transform: uppercase;
margin-bottom: 10px;
}
.value-display {
font-family: 'Orbitron', sans-serif;
font-size: 2rem;
font-weight: 700;
}
.unit { font-size: 0.8rem; color: var(--skz-accent-green); }
#system-msg {
display: block;
text-align: center;
font-size: 0.75rem;
margin-top: 10px;
color: var(--text-dim);
}
</style>
</head>
<body>
<div class="dashboard-wrapper">
<header>
<div>
<div class="brand-title">
<i class="fas fa-satellite-dish"></i> HemaRangers<span style="color:var(--text-dim)">-IOT</span>
</div>
<span class="last-update-text" id="time-display">Connecting...</span>
</div>
<button class="report-btn" onclick="downloadReport()">
<i class="fas fa-file-download"></i> 7-Day Report
</button>
</header>

<div class="control-section">
<div>
<div class="toggle-wrapper">
<span class="status-label">SENSOR SYSTEM</span>
<div id="master-switch" class="toggle-switch" onclick="toggleSystem()"></div>
</div>
<span id="system-msg">Checking status...</span>
</div>
</div>

<div class="grid-container">
<div class="card">
<div class="card-header"><i class="fas fa-water"></i> Water Temp</div>
<div><span class="value-display" id="val-water">--</span><span class="unit">°C</span></div>
</div>
<div class="card">
<div class="card-header"><i class="fas fa-thermometer-half"></i> Temperature</div>
<div><span class="value-display" id="val-air">--</span><span class="unit">°C</span></div>
</div>
<div class="card">
<div class="card-header"><i class="fas fa-tint"></i> Humidity</div>
<div><span class="value-display" id="val-hum">--</span><span class="unit">g/m³</span></div>
</div>
<div class="card">
<div class="card-header"><i class="fas fa-cloud-rain"></i> Rainfall</div>
<div><span class="value-display" id="val-rain">--</span><span class="unit">mL</span></div>
</div>
</div>
</div>

<script>
const config = {
channelID: 'YOUR_CHANNEL_ID',
readKey: 'YOUR_READ_API_KEY',
writeKey: 'YOUR_WRITE_API_KEY'
};

const ui = {
water: document.getElementById('val-water'),
air: document.getElementById('val-air'),
hum: document.getElementById('val-hum'),
rain: document.getElementById('val-rain'),
switch: document.getElementById('master-switch'),
msg: document.getElementById('system-msg'),
time: document.getElementById('time-display') // NEW
};

let currentSystemState = 0;

async function updateDashboard() {
try {
// FETCH 1: Get SENSOR DATA (Fields 1-4)
const urlSensors = `https://api.thingspeak.com/channels/${config.channelID}/feeds/last.json?api_key=${config.readKey}`;
const respSensors = await fetch(urlSensors);
const dataSensors = await respSensors.json();

ui.water.innerText = format(dataSensors.field1);
ui.air.innerText = format(dataSensors.field2);
ui.hum.innerText = format(dataSensors.field3);
ui.rain.innerText = format(dataSensors.field4);

// UPDATE TIMESTAMP
if(dataSensors.created_at) {
const dateObj = new Date(dataSensors.created_at);
ui.time.innerText = "Last Update: " + dateObj.toLocaleString();
}

// FETCH 2: Get SWITCH STATE (Field 5 ONLY)
const urlSwitch = `https://api.thingspeak.com/channels/${config.channelID}/fields/5/last.json?api_key=${config.readKey}`;
const respSwitch = await fetch(urlSwitch);
const dataSwitch = await respSwitch.json();

const rawState = dataSwitch.field5;
const state = (rawState === null || rawState === undefined || rawState === "") ? 0 : parseInt(rawState);

updateSwitchUI(state);

} catch (error) {
console.error("Sync Error", error);
ui.msg.innerText = "Syncing...";
}
}

function format(val) {
return (val === null || val === "") ? "--" : parseFloat(val).toFixed(1);
}

function updateSwitchUI(state) {
currentSystemState = state;
if (state === 1) {
ui.switch.className = "toggle-switch toggle-active";
ui.msg.innerText = "SYSTEM IS ONLINE (Recording every 30 minutes)";
ui.msg.style.color = "var(--skz-accent-green)";
} else {
ui.switch.className = "toggle-switch toggle-inactive";
ui.msg.innerText = "SYSTEM IS OFFLINE (Standby Mode)";
ui.msg.style.color = "var(--skz-accent-pink)";
}
}

async function toggleSystem() {
const newState = (currentSystemState === 1) ? 0 : 1;

ui.msg.innerText = "SENDING COMMAND...";
ui.switch.style.opacity = "0.5";

try {
const url = `https://api.thingspeak.com/update?api_key=${config.writeKey}&field5=${newState}`;
const response = await fetch(url);
const result = await response.text();

if (result == "0") {
alert("⚠️ TRAFFIC JAM: Please wait 15 seconds before clicking again!");
ui.msg.innerText = "RATE LIMIT HIT - WAIT";
} else {
updateSwitchUI(newState);
}
} catch (err) {
alert("Error sending command");
} finally {
ui.switch.style.opacity = "1";
}
}

function downloadReport() {
const url = `https://api.thingspeak.com/channels/${config.channelID}/feeds.csv?api_key=${config.readKey}&days=7`;
window.location.href = url;
}

window.onload = () => {
updateDashboard();
setInterval(updateDashboard, 10000);
};
</script>
</body>
</html>

Testing the Rain Gauge Using Water

Testing DIY.jpeg

Before outdoor deployment, the rain gauge was tested using water to verify correct operation. Water was slowly poured into the funnel to simulate rainfall under controlled conditions.

During testing, each tipping action of the bucket was visually observed and compared with the pulse count received by the Raspberry Pi Pico W. The Hall effect sensor successfully detected each bucket tip and generated a corresponding digital pulse.

The detected pulses were transmitted to the cloud and displayed correctly on the dashboard as rainfall data. This testing confirmed that the rain gauge assembly, magnetic detection, and data transmission were functioning properly before long-term outdoor deployment.

System Deployment and Testing

Deployement.jpeg

After successful bench testing, the complete system was deployed outdoors at a selected campus location near a water source. The weatherproof enclosure was placed on a stable surface, and the rain gauge was mounted on top to allow unobstructed rainfall collection.

The water temperature sensor was positioned securely in the water, while the air temperature and humidity sensor remained inside the enclosure with sufficient ventilation. All cable connections were checked to ensure they were firmly secured and protected from environmental exposure.

The system was powered using a portable power bank, allowing flexible deployment without the need for a fixed power source. During deployment, the system operated continuously and transmitted sensor data automatically at 30-minute intervals.

Field testing confirmed stable operation, reliable sensor readings, and consistent updates on the cloud dashboard under real outdoor environmental conditions.

Conclusion

This project delivers a complete DIY IoT rain gauge system with reliable sensing, cloud visualization, and outdoor durability. Using a Hall effect sensor improves longevity, while practical calibration and testing ensure meaningful data.

All materials, wiring, assembly steps, calibration methods, and configurations are documented so others can fully replicate the system using readily available components.