0% found this document useful (0 votes)
4 views16 pages

Weather Station

The document outlines the implementation of a weather station using NodeMCU V3, incorporating BMP280 and DHT11 sensors for temperature, pressure, and humidity readings. It features an LCD display, WiFi connectivity, and integration with ThingSpeak for data logging. The code includes functions for configuration management, sensor data retrieval, and a web server for user interaction.
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)
4 views16 pages

Weather Station

The document outlines the implementation of a weather station using NodeMCU V3, incorporating BMP280 and DHT11 sensors for temperature, pressure, and humidity readings. It features an LCD display, WiFi connectivity, and integration with ThingSpeak for data logging. The code includes functions for configuration management, sensor data retrieval, and a web server for user interaction.
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
You are on page 1/ 16

* Weather Station

* By Shihab Fatin Abdul, improved by burnblazter

* Board : NodeMCU V3

* Input : Sensor BMP280

* Sensor DHT11

* Output : LCD 16x2

* Thingspeak

* Status LED on D4

* Connections:

* DHT11 -- Pin D5

* BMP280 SCL -- Pin D1

* SDA -- Pin D2

* LCD SCL -- Pin D1

* SDA -- Pin D2

* LED -- Pin D4

****************************************/

#include <ESP8266WiFi.h>

#include <ESP8266WebServer.h>

#include <DNSServer.h>

#include <WiFiManager.h>

#include <Wire.h>

#include <Adafruit_Sensor.h>

#include <Adafruit_BMP280.h>

#include <LiquidCrystal_I2C.h>

#include <DHT.h>

#include <ArduinoJson.h>

#include <EEPROM.h>

#define SEALEVELPRESSURE_HPA (1013.25)


#define DHTPIN D5

#define DHTTYPE DHT11

#define LED_PIN D4

#define AP_NAME "WeatherStat"

#define CONFIG_SIZE 512

struct Config {

char ssid[32];

char password[32];

char apiKey[32];

int updateInterval;

bool ledEnabled;

};

Config config;

DHT dht(DHTPIN, DHTTYPE);

Adafruit_BMP280 bmp;

LiquidCrystal_I2C lcd(0x27, 16, 2);

WiFiClient client;

ESP8266WebServer server(80);

WiFiManager wifiManager;

unsigned long lastUpdateTime = 0;

bool sensorBMP280_OK = false;

bool sensorDHT11_OK = false;

const char* thingspeak_server = "api.thingspeak.com";

int lcdPage = 0;

unsigned long lcdPageChangeTime = 0;

const unsigned long pageChangeInterval = 15000; // 15 seconds

unsigned long ledOffTime = 0;


bool ledState = false;

// HTML for the configuration portal

const char PORTAL_HTML[] PROGMEM = R"rawliteral(

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width, initial-scale=1">

<title>Weather Station Configuration</title>

<style>

body {font-family: Arial, Helvetica, sans-serif; margin: 0; padding: 20px; background-color: #f0f8ff;}

.container {max-width: 500px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);}

h1 {color: #0077cc; text-align: center;}

.form-group {margin-bottom: 15px;}

label {display: block; margin-bottom: 5px; font-weight: bold; color: #333;}

input[type=text], input[type=password], input[type=number] {width: 100%; padding: 10px; border:


1px solid #ddd; border-radius: 4px; box-sizing: border-box;}

.btn {background-color: #0077cc; color: white; padding: 10px 15px; border: none; border-radius: 4px;
cursor: pointer; width: 100%;}

.btn:hover {background-color: #005fa3;}

.status {padding: 15px; background-color: #f8f9fa; border-radius: 4px; margin-bottom: 20px;}

.status-item {margin-bottom: 10px; display: flex; justify-content: space-between;}

.status-label {font-weight: bold;}

.status-value {padding: 2px 8px; border-radius: 4px;}

.ok {background-color: #d4edda; color: #155724;}

.error {background-color: #f8d7da; color: #721c24;}

.switch {position: relative; display: inline-block; width: 60px; height: 34px;}

.switch input {opacity: 0; width: 0; height: 0;}


.slider {position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc;
transition: .4s; border-radius: 34px;}

.slider:before {position: absolute; content: ""; height: 26px; width: 26px; left: 4px; bottom: 4px;
background-color: white; transition: .4s; border-radius: 50%;}

input:checked + .slider {background-color: #0077cc;}

input:checked + .slider:before {transform: translateX(26px);}

</style>

</head>

<body>

<div class="container">

<h1>Weather Station</h1>

<div class="status">

<div class="status-item">

<span class="status-label">BMP280 Sensor:</span>

<span class="status-value %BMP_STATUS_CLASS%">%BMP_STATUS%</span>

</div>

<div class="status-item">

<span class="status-label">DHT11 Sensor:</span>

<span class="status-value %DHT_STATUS_CLASS%">%DHT_STATUS%</span>

</div>

<div class="status-item">

<span class="status-label">WiFi Connection:</span>

<span class="status-value %WIFI_STATUS_CLASS%">%WIFI_STATUS%</span>

</div>

<div class="status-item">

<span class="status-label">ThingSpeak:</span>

<span class="status-value %TS_STATUS_CLASS%">%TS_STATUS%</span>

</div>
</div>

<form action="/save" method="post">

<div class="form-group">

<label for="ssid">WiFi SSID:</label>

<input type="text" id="ssid" name="ssid" value="%SSID%">

</div>

<div class="form-group">

<label for="password">WiFi Password:</label>

<input type="password" id="password" name="password" value="%PASSWORD%">

</div>

<div class="form-group">

<label for="apiKey">ThingSpeak API Key:</label>

<input type="text" id="apiKey" name="apiKey" value="%API_KEY%">

</div>

<div class="form-group">

<label for="interval">Update Interval (seconds):</label>

<input type="number" id="interval" name="interval" min="20" value="%INTERVAL%">

</div>

<div class="form-group">

<label>LED Indicator:</label>

<label class="switch">

<input type="checkbox" id="ledEnabled" name="ledEnabled" %LED_CHECKED%>

<span class="slider"></span>

</label>
</div>

<button type="submit" class="btn">Save Configuration</button>

</form>

</div>

</body>

</html>

)rawliteral";

// Function to load configuration from EEPROM

void loadConfig() {

EEPROM.begin(CONFIG_SIZE);

EEPROM.get(0, config);

EEPROM.end();

// Set defaults if values appear invalid

if (config.updateInterval < 20 || config.updateInterval > 3600) {

config.updateInterval = 30; // Default 30 seconds

// Function to save configuration to EEPROM

void saveConfig() {

EEPROM.begin(CONFIG_SIZE);

EEPROM.put(0, config);

EEPROM.commit();

EEPROM.end();

}
// LED functions

void blinkLED(int times, int delayMs) {

for (int i = 0; i < times; i++) {

digitalWrite(LED_PIN, HIGH);

delay(delayMs);

digitalWrite(LED_PIN, LOW);

if (i < times - 1) delay(delayMs);

void flashLED() {

if (config.ledEnabled) {

digitalWrite(LED_PIN, HIGH);

ledState = true;

ledOffTime = millis() + 200; // LED will be on for 200ms

// Update LCD display

void updateLCD() {

unsigned long currentTime = millis();

if (currentTime - lcdPageChangeTime >= pageChangeInterval) {

lcdPage = (lcdPage + 1) % 2;

lcdPageChangeTime = currentTime;

lcd.clear();
if (lcdPage == 0) {

float temperature = bmp.readTemperature();

float pressure = bmp.readPressure() / 100.0F;

lcd.setCursor(0, 0);

lcd.print("T:");

lcd.print(temperature, 1);

lcd.print("C ");

lcd.setCursor(8, 0);

lcd.print("P:");

lcd.print(pressure, 0);

lcd.print("hPa");

lcd.setCursor(0, 1);

lcd.print("H:");

lcd.print(dht.readHumidity(), 0);

lcd.print("%");

lcd.setCursor(8, 1);

lcd.print("WiFi:");

lcd.print(WiFi.RSSI());

} else {

float altitude = bmp.readAltitude(SEALEVELPRESSURE_HPA);

lcd.setCursor(0, 0);

lcd.print("Alt:");

lcd.print(altitude, 1);

lcd.print("m");
lcd.setCursor(0, 1);

lcd.print("");

lcd.print(WiFi.localIP().toString());

// Update sensor data to ThingSpeak

void updateThingSpeak() {

float temperature = bmp.readTemperature();

float pressure = bmp.readPressure() / 100.0F;

float altitude = bmp.readAltitude(SEALEVELPRESSURE_HPA);

float humidity = dht.readHumidity();

if (client.connect(thingspeak_server, 80)) {

flashLED(); // Flash LED when sending data

String postStr = config.apiKey;

postStr += "&field1=";

postStr += String(temperature);

postStr += "&field2=";

postStr += String(pressure);

postStr += "&field3=";

postStr += String(altitude);

postStr += "&field4=";

postStr += String(humidity);

postStr += "\r\n\r\n";

client.print("POST /update HTTP/1.1\n");

client.print("Host: api.thingspeak.com\n");
client.print("Connection: close\n");

client.print("X-THINGSPEAKAPIKEY: " + String(config.apiKey) + "\n");

client.print("Content-Type: application/x-www-form-urlencoded\n");

client.print("Content-Length: ");

client.print(postStr.length());

client.print("\r\n\r\n");

client.print(postStr);

client.stop();

lastUpdateTime = millis();

// Web server handlers

String processor(const String& var) {

if (var == "BMP_STATUS") return sensorBMP280_OK ? "Connected" : "Error";

if (var == "BMP_STATUS_CLASS") return sensorBMP280_OK ? "ok" : "error";

if (var == "DHT_STATUS") return sensorDHT11_OK ? "Connected" : "Error";

if (var == "DHT_STATUS_CLASS") return sensorDHT11_OK ? "ok" : "error";

if (var == "WIFI_STATUS") return WiFi.status() == WL_CONNECTED ? "Connected" : "Not Connected";

if (var == "WIFI_STATUS_CLASS") return WiFi.status() == WL_CONNECTED ? "ok" : "error";

if (var == "TS_STATUS") return strlen(config.apiKey) > 0 ? "Configured" : "Not Configured";

if (var == "TS_STATUS_CLASS") return strlen(config.apiKey) > 0 ? "ok" : "error";

if (var == "SSID") return String(config.ssid);

if (var == "PASSWORD") return String(config.password);

if (var == "API_KEY") return String(config.apiKey);

if (var == "INTERVAL") return String(config.updateInterval);

if (var == "LED_CHECKED") return config.ledEnabled ? "checked" : "";

return String();
}

String processTemplate(const String& templateStr) {

String result = templateStr;

result.replace("%BMP_STATUS%", sensorBMP280_OK ? "Connected" : "Error");

result.replace("%BMP_STATUS_CLASS%", sensorBMP280_OK ? "ok" : "error");

result.replace("%DHT_STATUS%", sensorDHT11_OK ? "Connected" : "Error");

result.replace("%DHT_STATUS_CLASS%", sensorDHT11_OK ? "ok" : "error");

result.replace("%WIFI_STATUS%", WiFi.status() == WL_CONNECTED ? "Connected" : "Not Connected");

result.replace("%WIFI_STATUS_CLASS%", WiFi.status() == WL_CONNECTED ? "ok" : "error");

result.replace("%TS_STATUS%", strlen(config.apiKey) > 0 ? "Configured" : "Not Configured");

result.replace("%TS_STATUS_CLASS%", strlen(config.apiKey) > 0 ? "ok" : "error");

result.replace("%SSID%", String(config.ssid));

result.replace("%PASSWORD%", String(config.password));

result.replace("%API_KEY%", String(config.apiKey));

result.replace("%INTERVAL%", String(config.updateInterval));

result.replace("%LED_CHECKED%", config.ledEnabled ? "checked" : "");

return result;

// Handle root URLt

void handleRoot() {

String html = FPSTR(PORTAL_HTML);

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

// Handle configuration save


void handleSave() {

if (server.hasArg("ssid")) {

server.arg("ssid").toCharArray(config.ssid, sizeof(config.ssid));

if (server.hasArg("password")) {

server.arg("password").toCharArray(config.password, sizeof(config.password));

if (server.hasArg("apiKey")) {

server.arg("apiKey").toCharArray(config.apiKey, sizeof(config.apiKey));

if (server.hasArg("interval")) {

config.updateInterval = server.arg("interval").toInt();

if (config.updateInterval < 20) config.updateInterval = 20;

config.ledEnabled = server.hasArg("ledEnabled");

saveConfig();

server.sendHeader("Location", "/");

server.send(303);

// Only attempt to reconnect to WiFi if we're in STA mode

if (WiFi.getMode() == WIFI_STA) {

WiFi.disconnect();

delay(1000);
WiFi.begin(config.ssid, config.password);

// Setup function

void setup() {

pinMode(LED_PIN, OUTPUT);

digitalWrite(LED_PIN, LOW);

// Initialize LCD

lcd.init();

lcd.backlight();

lcd.clear();

lcd.setCursor(0, 0);

lcd.print("IoT Weather Station");

lcd.setCursor(0, 1);

lcd.print("Initializing...");

// Initialize EEPROM and load configuration

EEPROM.begin(CONFIG_SIZE);

loadConfig();

// Initialize sensors

delay(100);

sensorBMP280_OK = bmp.begin(0x76);

if (!sensorBMP280_OK) {

lcd.clear();

lcd.setCursor(0, 0);

lcd.print("BMP280 Error!");
blinkLED(3, 300);

dht.begin();

delay(2000); // Give DHT time to stabilize

float h = dht.readHumidity();

sensorDHT11_OK = !isnan(h);

if (!sensorDHT11_OK) {

lcd.clear();

lcd.setCursor(0, 0);

lcd.print("DHT11 Error!");

blinkLED(3, 300);

// WiFi setup - first try to connect with saved credentials

lcd.clear();

lcd.setCursor(0, 0);

lcd.print("Connecting WiFi");

WiFi.mode(WIFI_STA);

WiFi.begin(config.ssid, config.password);

int timeout = 0;

while (WiFi.status() != WL_CONNECTED && timeout < 20) {

delay(500);

lcd.setCursor(timeout % 16, 1);

lcd.print(".");

timeout++;

}
// If WiFi connection fails, start AP mode with captive portal

if (WiFi.status() != WL_CONNECTED) {

WiFi.mode(WIFI_AP);

WiFi.softAP(AP_NAME);

lcd.clear();

lcd.setCursor(0, 0);

lcd.print("AP Mode Enabled");

lcd.setCursor(0, 1);

lcd.print("SSID: ");

lcd.print(AP_NAME);

blinkLED(5, 200); // Indicate AP mode with blinks

} else {

lcd.clear();

lcd.setCursor(0, 0);

lcd.print("WiFi Connected!");

lcd.setCursor(0, 1);

lcd.print(WiFi.localIP().toString());

delay(2000);

// Setup web server

server.on("/", handleRoot);

server.on("/save", HTTP_POST, handleSave);

server.begin();

}
// Main loop

void loop() {

server.handleClient();

// Turn off LED if timer expired

if (ledState && millis() > ledOffTime) {

digitalWrite(LED_PIN, LOW);

ledState = false;

// Update LCD every second

static unsigned long lastLcdUpdate = 0;

if (millis() - lastLcdUpdate > 1000) {

updateLCD();

lastLcdUpdate = millis();

// Update ThingSpeak if connected to WiFi and interval has passed

if (WiFi.status() == WL_CONNECTED &&

strlen(config.apiKey) > 0 &&

millis() - lastUpdateTime > (config.updateInterval * 1000)) {

updateThingSpeak();

You might also like