IoT Weather Reporting System with Arduino & Raspberry Pi LoRa Gateway
Deploy a comprehensive microclimate monitoring station with Arduino sensor nodes and Raspberry Pi LoRa gateway for long-range data collection in remote Nepali villages.
Required Hardware & Component Checklist
Curated components verified for this project. Check the items you need, adjust quantities, and add straight to cart:
IoT Weather Reporting System with Arduino & Raspberry Pi LoRa Gateway
Educational Notes
This project is designed to be accessible to students from Class 4 to Masters level, with complexity scalable to match different age groups and skill levels.
Learning Objectives:
- Understand basic electronics and circuitry principles
- Learn sensor applications and data collection techniques
- Develop problem-solving skills through hands-on building and troubleshooting
- Apply programming concepts to control hardware and process data
- Connect projects to real-world Nepalese contexts and challenges
Adaptability:
- For younger students (Class 4-8): Focus on assembling pre-built circuits, observing results, and understanding basic concepts
- For intermediate students (Class 9-12): Modify code, experiment with parameters, and explore underlying principles
- For advanced students (Undergraduate/Masters): Optimize designs, add features, conduct research extensions, and analyze performance
Safety Note: Always supervise younger students when working with electricity, heat, or moving parts.
Monitor microclimates across mountain valleys, agricultural terraces, and remote villages where cellular coverage is unreliable. This dual-architecture system uses Arduino-based sensor nodes communicating via LoRa (SX1278) to a central Raspberry Pi gateway that uplinks data via WiFi/Ethernet.
Hardware Bill of Materials (In Stock at Ghumti Pasal):
Sensor Node (Per Location - Build Multiple):
- Microcontroller: Arduino Nano / Pro Mini 3.3V 8MHz
- LoRa Radio: SX1278 LoRa Module 433MHz / 868MHz / 915MHz
- Temperature/Humidity: BME280 (Temp, Humidity, Pressure) or DHT22 + BMP280
- Wind Speed: Davis 6410 Anemometer (Reed Switch) or 3D Printed Cups + Hall Sensor
- Wind Direction: 3D Printed Vane + AS5600 Magnetic Encoder or Potentiometer
- Rainfall: Tipping Bucket Rain Gauge (Reed Switch)
- Solar Radiation: BH1750 or Custom Pyranometer
- Soil Sensors: Capacitive Moisture + DS18B20 Temperature (Optional)
- Power: 5W Solar Panel + TP4056 + 18650 3.7V 3000mAh
- Enclosure: IP65 Junction Box + Radiation Shield (Stevenson Screen)
Gateway (Single):
- Controller: Raspberry Pi 4 Model B 4GB
- LoRa HAT: RAK2245 / Dragino LoRa/GPS HAT or SX1278 + SPI
- Connectivity: WiFi / Ethernet / 4G Dongle
- Storage: 32GB MicroSD + Optional SSD
- Power: 5V 3A USB-C + UPS HAT
Circuit Pinout - Sensor Node (Arduino Nano):
| Component / Sensor Pin | Arduino Nano Pin | Function / Description |
|---|---|---|
| SX1278 VCC / GND | 3.3V / GND | LoRa Power (3.3V Only!) |
| SX1278 NSS | Pin D10 | SPI Chip Select |
| SX1278 SCK | Pin D13 | SPI Clock |
| SX1278 MOSI | Pin D11 | SPI Master Out |
| SX1278 MISO | Pin D12 | SPI Master In |
| SX1278 DIO0 | Pin D2 (INT0) | RX/TX Interrupt |
| SX1278 RST | Pin D9 | Hardware Reset |
| BME280 SDA / SCL | Pin A4 / A5 | I2C Environmental |
| Anemometer | Pin D3 (INT1) | Wind Speed Pulses |
| Rain Gauge | Pin D4 | Rain Tip Pulses |
| Wind Vane (Analog) | Pin A0 | Direction Voltage |
| Solar Panel | VIN / GND | Charging Input |
Circuit Pinout - Gateway (Raspberry Pi):
| Component | Raspberry Pi Pin | Function |
|---|---|---|
| SX1278 NSS | GPIO 8 (CE0) | SPI Chip Select |
| SX1278 SCK | GPIO 11 (SCLK) | SPI Clock |
| SX1278 MOSI | GPIO 10 (MOSI) | SPI Master Out |
| SX1278 MISO | GPIO 9 (MISO) | SPI Master In |
| SX1278 DIO0 | GPIO 24 | Interrupt |
| SX1278 RST | GPIO 25 | Reset |
| GPS (Optional) | UART (TXD/RXD) | Time Sync + Location |
Firmware - Sensor Node (Arduino C++)
#include <SPI.h>
#include <LoRa.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <LowPower.h>
// LoRa Configuration
#define LORA_FREQ 868E6 // 868MHz for Asia
#define LORA_SF 12 // Spreading Factor
#define LORA_BW 125E3 // Bandwidth
#define LORA_TX_POWER 20 // dBm
// Pins
#define LORA_SS 10
#define LORA_RST 9
#define LORA_DIO0 2
#define ANEMOMETER_PIN 3
#define RAIN_PIN 4
#define VANE_PIN A0
// Sensor
Adafruit_BME280 bme;
volatile unsigned long windPulses = 0;
volatile unsigned long rainPulses = 0;
unsigned long lastWindTime = 0;
float windSpeedKmh = 0;
// Node ID (unique per node)
const uint8_t NODE_ID = 1;
unsigned long lastTx = 0;
const unsigned long TX_INTERVAL = 300000; // 5 minutes
void windISR() { windPulses++; lastWindTime = millis(); }
void rainISR() { rainPulses++; }
void setup() {
Serial.begin(9600);
// LoRa Init
LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0);
if (!LoRa.begin(LORA_FREQ)) {
Serial.println("LoRa init failed!");
while (1);
}
LoRa.setSpreadingFactor(LORA_SF);
LoRa.setSignalBandwidth(LORA_BW);
LoRa.setTxPower(LORA_TX_POWER);
// Sensors
Wire.begin();
if (!bme.begin(0x76)) {
Serial.println("BME280 not found!");
}
// Interrupts
pinMode(ANEMOMETER_PIN, INPUT_PULLUP);
pinMode(RAIN_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ANEMOMETER_PIN), windISR, FALLING);
attachInterrupt(digitalPinToInterrupt(RAIN_PIN), rainISR, FALLING);
// Sleep setup for low power
LowPower.attachInterruptWakeup(RAIN_PIN, rainISR, FALLING);
}
void loop() {
unsigned long now = millis();
// Calculate wind speed (pulses per rotation * circumference)
if (now - lastWindTime > 2000) {
windSpeedKmh = 0; // No rotation for 2s = calm
} else {
// Davis 6410: 1 pulse/rev, 2.4km/h per Hz
windSpeedKmh = (windPulses / 3.0) * 2.4; // 3 second sample
}
windPulses = 0;
// Read sensors
float temp = bme.readTemperature();
float humidity = bme.readHumidity();
float pressure = bme.readPressure() / 100.0F; // hPa
float rain_mm = rainPulses * 0.2794; // Davis 6410: 0.2794mm per tip
// Wind direction (voltage divider with known resistors)
int vaneRaw = analogRead(VANE_PIN);
float vaneVoltage = vaneRaw * (3.3 / 1023.0);
int windDir = voltageToDirection(vaneVoltage);
// Transmit if interval elapsed
if (now - lastTx >= TX_INTERVAL) {
transmitPacket(temp, humidity, pressure, windSpeedKmh, windDir, rain_mm);
lastTx = now;
rainPulses = 0; // Reset rain counter after transmission
}
// Deep sleep between readings (Arduino Pro Mini 3.3V)
// LowPower.sleep(TX_INTERVAL / 1000);
delay(1000);
}
void transmitPacket(float temp, float hum, float pres, float wind, int dir, float rain) {
String packet = String(NODE_ID) + "," +
String(temp, 1) + "," +
String(hum, 1) + "," +
String(pres, 1) + "," +
String(wind, 1) + "," +
String(dir) + "," +
String(rain, 2);
LoRa.beginPacket();
LoRa.print(packet);
LoRa.endPacket();
Serial.println("TX: " + packet);
}
int voltageToDirection(float v) {
// Calibrate with known directions
// Typical AS5600 or potentiometer voltage divider
if (v < 0.2) return 0; // N
if (v < 0.5) return 45; // NE
if (v < 0.8) return 90; // E
if (v < 1.1) return 135; // SE
if (v < 1.4) return 180; // S
if (v < 1.7) return 225; // SW
if (v < 2.0) return 270; // W
if (v < 2.3) return 315; // NW
return 360; // N
}
Gateway Software (Raspberry Pi - Python)
#!/usr/bin/env python3
import spidev
import RPi.GPIO as GPIO
import time
import json
import requests
import sqlite3
from datetime import datetime
from threading import Thread
# LoRa Configuration
LORA_FREQ = 868E6
LORA_SF = 12
LORA_BW = 125E3
# Pins (for raw SX1278 on Pi SPI)
LORA_RST = 25
LORA_DIO0 = 24
# Database
DB_PATH = "/var/lib/weather/weather.db"
API_ENDPOINT = "https://api.ghumtipasal.com/weather/ingest"
# Initialize database
def init_db():
conn = sqlite3.connect(DB_PATH)
conn.execute("""
CREATE TABLE IF NOT EXISTS readings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
temperature REAL,
humidity REAL,
pressure REAL,
wind_speed REAL,
wind_dir INTEGER,
rainfall REAL,
rssi INTEGER,
snr REAL
)
""")
conn.commit()
return conn
def save_reading(conn, data):
conn.execute("""
INSERT INTO readings (node_id, temperature, humidity, pressure,
wind_speed, wind_dir, rainfall, rssi, snr)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (data['node_id'], data['temp'], data['hum'], data['pres'],
data['wind'], data['dir'], data['rain'], data['rssi'], data['snr']))
conn.commit()
def send_to_cloud(data):
try:
response = requests.post(API_ENDPOINT, json=data, timeout=10)
return response.status_code == 200
except:
return False
def on_receive(payload, rssi, snr):
try:
parts = payload.decode().split(',')
data = {
'node_id': int(parts[0]),
'temp': float(parts[1]),
'hum': float(parts[2]),
'pres': float(parts[3]),
'wind': float(parts[4]),
'dir': int(parts[5]),
'rain': float(parts[6]),
'rssi': rssi,
'snr': snr
}
print(f"RX Node {data['node_id']}: {data['temp']}°C, {data['hum']}%, RSSI: {rssi}")
# Save locally
save_reading(db_conn, data)
# Forward to cloud
Thread(target=send_to_cloud, args=(data,)).start()
except Exception as e:
print(f"Parse error: {e}")
# Main gateway loop using SX1278 driver (e.g., python-sx127x)
# This is simplified - use proper LoRa library
if __name__ == "__main__":
db_conn = init_db()
print("Weather Gateway Started...")
# LoRa receive loop here
Network Topology
[Sensor Node 1] [Sensor Node N]
(Arduino) (Arduino)
| |
| LoRa 868MHz ~5-15km |
| |
v v
+--------------------------------------------------+
| RASPBERRY PI GATEWAY |
| - LoRa HAT (Receiver) |
| - SQLite Local Storage |
| - WiFi/Ethernet/4G Uplink |
| - MQTT Broker (Mosquitto) |
| - Grafana/InfluxDB Dashboard |
+--------------------------------------------------+
|
| Internet
v
+--------------------------------------------------+
| CLOUD / LOCAL SERVER |
| - Time-series Database (InfluxDB/TimescaleDB) |
| - Alert Engine (Thresholds, Trends) |
| - Web Dashboard (Grafana/Custom) |
| - Mobile App Notifications |
+--------------------------------------------------+
Range Optimization for Nepal Terrain
| Terrain | Expected Range | Antenna Recommendation |
|---|---|---|
| Valley Floor (LOS) | 10-15km | 3dBi Omni |
| Hills (Partial LOS) | 3-8km | 5dBi Directional Yagi |
| Dense Urban | 1-3km | 2dBi Omni + Elevated Mount |
| Deep Valley (NLOS) | 500m-2km | LoRa Repeater Node |
Power Budget (Per Sensor Node)
| Component | Current (Sleep) | Current (Active TX) | Duty Cycle |
|---|---|---|---|
| Arduino Pro Mini | 5µA | 15mA | 0.1% |
| SX1278 LoRa | 0.2µA | 120mA | 0.05% |
| BME280 | 0.1µA | 3.6µA | 100% |
| Anemometer/Rain | 0 (Passive) | - | Event |
| Total Avg | ~10µA | - | - |
Battery Life: 18650 3000mAh / 0.01mA = ~34 years (theoretical) Real-world: 6-12 months with 5W solar panel (monsoon clouds reduce charging)
Nepal Deployment Case Studies
- Mustang Apple Orchards: 12 nodes monitoring frost risk, gateway in Jomsom
- Kathmandu Valley Air Quality: 8 nodes + PM2.5 sensors, gateway at ICIMOD
- Chitwan Flood Early Warning: River level + rainfall nodes, SMS alerts to communities
- High Altitude Glacial Lakes: Solar nodes at 5000m, satellite uplink gateway
Cost Breakdown - Per Sensor Node (NPR)
| Component | Est. Price | Source |
|---|---|---|
| Arduino Pro Mini 3.3V | 800 | Ghumti Pasal |
| SX1278 LoRa Module | 1,200 | Ghumti Pasal |
| BME280 Sensor | 850 | Ghumti Pasal |
| Davis 6410 Anemometer | 4,500 | Import / Ghumti Pasal |
| Rain Gauge (Tipping Bucket) | 3,200 | Ghumti Pasal |
| Wind Vane + AS5600 | 2,800 | Ghumti Pasal |
| 5W Solar + TP4056 + 18650 | 1,500 | Ghumti Pasal |
| IP65 Enclosure + Radiation Shield | 2,500 | Ghumti Pasal / Custom |
| Total Per Node | ~17,350 |
Gateway Cost (NPR)
| Component | Est. Price |
|---|---|
| Raspberry Pi 4 4GB | 12,000 |
| RAK2245 LoRa HAT | 8,500 |
| 32GB MicroSD + SSD | 2,500 |
| 5V 3A Adapter + UPS HAT | 3,000 |
| Outdoor Enclosure + Antenna Mast | 4,000 |
| Total Gateway | ~30,000 |
Complete System (5 Nodes + 1 Gateway): ~117,000 NPR
Commercial equivalent: 500,000+ NPR for Davis/Vantage Pro2 systems
