#include <DHT.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Wire.h>
// #include <LiquidCrystal_I2C.h>
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Cexp.h>
#define MQTT_MAX_PACKET_SIZE 512
#include <PubSubClient.h>
// ----------- Wi-Fi & ThingsBoard -----------
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASS ""
#define TB_HOST "thingsboard.cloud"
#define TB_PORT 1883
#define TB_TOKEN "2FurFe6s72hBDWblvDnR"
// Pin definitions for ESP32
#define DHT_PIN 23
#define DHT_TYPE DHT22
#define LIGHT_SENSOR_PIN 35 // GPIO36 (ADC1_CH0) (10K RESISTOR)
#define LED_NORMAL 2 // GPIO2 (built-in LED) (10K RESISTOR)
#define LED_WARNING 15 // GPIO15 (10K RESISTOR)
#define LED_CRITICAL 13 // GPIO13(10K RESISTOR)
#define BUZZER_PIN 27
#define LED_HUMIDITY 25 // use GPIO25 instead of 34 for output
// Buzzer/alert globals
unsigned long alertStartTime = 0;
unsigned long lastBeep = 0;
bool beepState = false;
bool isCritical = false;
bool isWarning = false;
// LCD using hd44780_I2Cexp (auto-detects I2C backpack)
hd44780_I2Cexp lcd;
// LiquidCrystal_I2C lcd(0x27, 16, 2);
// DHT sensor
DHT dht(DHT_PIN, DHT_TYPE);
// Thresholds for alerts
const float TEMP_HIGH = 30.0; // Critical temperature
const float TEMP_LOW = 15.0; // Low temperature warning
const float HUMIDITY_HIGH = 70.0; //High humidity warning
const float HUMIDITY_LOW = 20.0; // Low humidity warning
const int LIGHT_LOW = 1000; // Low light warning (12-bit ADC)
const float GAMMA = 0.7;
const float RL10 = 50;
// Data structure
struct SensorData {
float temperature;
float humidity;
float lightLevel;
unsigned long timestamp;
String alertStatus;
};
float avgTemp = 0, avgHumidity = 0;
int avgLight = 0;
// Global variables
SensorData currentData;
SensorData dataBuffer[5]; // Simple aggregation buffer
int bufferIndex = 0;
unsigned long lastReading = 0;
unsigned long lastDisplay = 0;
// Buffer for non-blocking telemetry publishing
String telemetryBuffer = "";
bool alertActive = false;
// ----------- Wi-Fi + MQTT clients -----------
WiFiClient espClient;
PubSubClient mqttClient(espClient);
const char* TB_TELEMETRY_TOPIC = "v1/devices/me/telemetry";
const char* TB_ATTRIBUTES_TOPIC = "v1/devices/me/attributes";
bool sentConnectAttributes = false;
// ----------- Timing control -----------
unsigned long previousTelemetrySend = 0;
unsigned long previousTelemetrySend2 = 0;
void setup() {
Serial.begin(115200);
// Initialize DHT
dht.begin();
delay(2000);
// Initialize I2C LCD
// lcd.init();
lcd.begin(16, 2);
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("ESP32 Monitor");
lcd.setCursor(0, 1);
lcd.print("Starting...");
// Initialize pins
pinMode(LED_NORMAL, OUTPUT);
pinMode(LED_WARNING, OUTPUT);
pinMode(LED_CRITICAL, OUTPUT);
pinMode(LED_HUMIDITY, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
// Configure ADC for ESP32
analogReadResolution(12); // ESP32 has 12-bit ADC
analogSetAttenuation(ADC_11db); // For 0-3.3V range
// Initial LED state
digitalWrite(LED_NORMAL, HIGH);
mqttClient.setServer(TB_HOST, TB_PORT);
Serial.println("=== ESP32 Smart Environment Monitor Started ===");
Serial.println("Temperature | Humidity | Light | Status");
Serial.println("--------------------------------------------");
delay(2000);
lcd.clear();
}
void loop() {
ensureWifi();
ensureMqtt();
mqttClient.loop();
yield();// allow Wi-Fi background tasks
// Read sensors every 5 seconds
if (millis() - lastReading >= 2000) {
readSensors();
processEdgeLogic();
aggregateData();
// Prepare payload, don’t send yet
StaticJsonDocument<256> doc;
doc["temperature"] = currentData.temperature;
doc["humidity"] = currentData.humidity;
doc["lightLevel"] = currentData.lightLevel;
doc["alertLevel"] = currentData.alertStatus;
doc["uptime"] = millis()/1000UL;
telemetryBuffer = "";
serializeJson(doc, telemetryBuffer);
lastReading = millis();
}
// Update display every 1 second
if (millis() - lastDisplay >= 1000) {
updateDisplay();
printToSerial();
lastDisplay = millis();
}
// if (millis() - previousTelemetrySend2 >= 3000) {
// previousTelemetrySend2 = millis();
// publishTelemetryNow();
// }
// Publish asynchronously every 2s if buffer is filled
if (millis() - previousTelemetrySend2 >= 2000 && telemetryBuffer.length() > 0) {
mqttClient.publish(TB_TELEMETRY_TOPIC, telemetryBuffer.c_str());
telemetryBuffer = ""; // clear after sending
previousTelemetrySend2 = millis();
}
if (millis() - previousTelemetrySend >= 10000) {
previousTelemetrySend = millis();
// publishTelemetryNow();
publishAggregate(avgTemp, avgHumidity, avgLight);
}
// Handle alert buzzer
if (alertActive) {
handleAlertSound();
}
delay(100);
}
void readSensors() {
yield(); // allow Wi-Fi background tasks
// Read DHT22 (Temperature & Humidity)
currentData.temperature = dht.readTemperature();
currentData.humidity = dht.readHumidity();
// Read light sensor (potentiometer simulates light sensor in Wokwi)
int analogValue = analogRead(LIGHT_SENSOR_PIN);
float voltage = analogValue / 4096. * 5;
float resistance = 2000 * voltage / (1 - voltage / 5);
float lux = pow(RL10 * 1e3 * pow(10, GAMMA) / resistance, (1 / GAMMA));
currentData.lightLevel = lux;
currentData.timestamp = millis();
// Validate sensor readings
if (isnan(currentData.temperature) || isnan(currentData.humidity)) {
Serial.println("ERROR: Failed to read from DHT sensor!");
currentData.temperature = 0;
currentData.humidity = 0;
}
// Validate light sensor (check for reasonable range)
if (currentData.lightLevel < 0 || currentData.lightLevel > 4095) {
Serial.println("ERROR: Invalid light sensor reading!");
currentData.lightLevel = 0;
}
}
void processEdgeLogic() {
// Reset LEDs
digitalWrite(LED_NORMAL, LOW);
digitalWrite(LED_WARNING, LOW);
digitalWrite(LED_CRITICAL, LOW);
digitalWrite(LED_HUMIDITY, LOW);
isCritical = false; //newly added for buzzer
isWarning = false; //newly added for buzzer
const bool tempHigh = (currentData.temperature > TEMP_HIGH);
const bool tempLow = (currentData.temperature < TEMP_LOW);
const bool humHigh = (currentData.humidity > HUMIDITY_HIGH);
const bool lightLow = (currentData.lightLevel < LIGHT_LOW);
const bool humLow = (currentData.humidity < HUMIDITY_LOW);
// Decide status once, in priority order: CRITICALS first, then WARNINGS, else NORMAL
String status = "NORMAL";
// int ledPin = LED_NORMAL;
// Criticals (any combination that includes a critical dimension)
if (tempHigh && humHigh && lightLow) {
status = "CRITICAL_TEMP_HUM_HIGH_LIGHT_LOW";
digitalWrite(LED_CRITICAL, HIGH);
isCritical = true;
} else if (tempHigh && humHigh) {
status = "CRITICAL_TEMP_HUM_HIGH";
digitalWrite(LED_CRITICAL, HIGH);
isCritical = true;
} else if (tempHigh && lightLow) {
status = "CRITICAL_TEMP_HIGH_LIGHT_LOW";
digitalWrite(LED_CRITICAL, HIGH);
isCritical = true;
} else if (humHigh && lightLow) {
status = "CRITICAL_HUM_HIGH_LIGHT_LOW";
digitalWrite(LED_CRITICAL, HIGH);
isCritical = true;
} else if (tempHigh) {
status = "CRITICAL_TEMP_HIGH";
digitalWrite(LED_CRITICAL, HIGH);
isCritical = true;
} else if (lightLow) {
status = "CRITICAL_LIGHT_LOW";
digitalWrite(LED_CRITICAL, HIGH);
isCritical = true;
}
else if (humHigh) {
status = "WARNING_HUMIDITY_HIGH";
digitalWrite(LED_CRITICAL, HIGH);
isCritical = true;
}
// Warnings (non-critical)
else if (tempLow) {
status = "WARNING_TEMP_LOW";
digitalWrite(LED_WARNING, HIGH);
isWarning = true;
}
else if (humLow){
status = "WARNING_HUM_LOW";
digitalWrite(LED_WARNING, HIGH);
isWarning = true;
}
else{
status = "NORMAL";
digitalWrite(LED_NORMAL, HIGH);
}
currentData.alertStatus = status;
// Alert activation window for buzzer (critical only)
if (isCritical && !alertActive) {
alertActive = true;
alertStartTime = millis();
// reset beep cycle
lastBeep = 0;
beepState = false;
} else if (!isCritical && alertActive) {
// Clear buzzer if no longer critical
alertActive = false;
noTone(BUZZER_PIN);
}
}
// void triggerAlert(int ledPin) {
// // Turn off all LEDs
// digitalWrite(LED_NORMAL, LOW);
// digitalWrite(LED_WARNING, LOW);
// digitalWrite(LED_CRITICAL, LOW);
// // Turn on appropriate LED
// digitalWrite(ledPin, HIGH);
// }
void handleAlertSound(){
if (!alertActive) {
noTone(BUZZER_PIN); // Ensure buzzer is off
return;
}
if (millis() - lastBeep >= 500) { // Beep every 500ms
beepState = !beepState;
if (beepState) {
tone(BUZZER_PIN, 1000, 200); // 1kHz for 200ms
} else {
noTone(BUZZER_PIN); // Silence between beeps
}
lastBeep = millis();
}
}
void aggregateData() {
// Store data in buffer for aggregation
dataBuffer[bufferIndex] = currentData;
bufferIndex = (bufferIndex + 1) % 5; // Circular buffer
// Calculate averages when buffer is full
if (bufferIndex == 0) {
avgTemp = 0;
avgHumidity = 0;
avgLight = 0;
for (int i = 0; i < 5; i++) {
avgTemp += dataBuffer[i].temperature;
avgHumidity += dataBuffer[i].humidity;
avgLight += dataBuffer[i].lightLevel;
}
avgTemp /= 5;
avgHumidity /= 5;
avgLight /= 5;
// Publish aggregated data only here
// publishAggregate(avgTemp, avgHumidity, avgLight);
// Print aggregated data
Serial.println("\n=== EDGE AGGREGATED DATA (5 readings) ===");
Serial.print("Avg Temp: "); Serial.print(avgTemp, 1); Serial.print("°C | ");
Serial.print("Avg Humidity: "); Serial.print(avgHumidity, 1); Serial.print("% | ");
Serial.print("Avg Light: "); Serial.println(avgLight);
Serial.println("=========================================\n");
}
}
void updateDisplay() {
lcd.clear();
// Line 1: Temperature and Humidity
lcd.setCursor(0, 0);
lcd.print("T:");
lcd.print(currentData.temperature, 1);
lcd.print("C H:");
lcd.print(currentData.humidity, 0);
lcd.print("%");
// Line 2: Light and Status
lcd.setCursor(0, 1);
lcd.print("L:");
lcd.print(currentData.lightLevel);
// Show WiFi status
// lcd.setCursor(8, 1);
// if (wifiConnected) {
// lcd.print("WiFi:OK");
// } else {
// lcd.print("WiFi:NO");
// }
// Show alert indicator
if (currentData.alertStatus != "NORMAL") {
lcd.setCursor(15, 1);
lcd.print("!");
}
}
void printToSerial() {
// Print current readings to Serial Monitor
Serial.print(currentData.temperature, 1);
Serial.print("°C | ");
Serial.print(currentData.humidity, 1);
Serial.print("% | ");
Serial.print(currentData.lightLevel);
Serial.print(" | ");
Serial.println(currentData.alertStatus);
// Print JSON format (simulating HTTP transmission)
if (currentData.alertStatus != "NORMAL" || (millis() % 30000 < 5000)) {
printJSONData();
}
}
void printJSONData() {
Serial.println("\n--- MQTT JSON Payload ---");
// Create JSON document
StaticJsonDocument<300> doc;
doc["deviceId"] = "ESP32_ENV_001";
doc["timestamp"] = currentData.timestamp;
doc["location"] = "room_1";
JsonObject data = doc.createNestedObject("data");
data["temperature"] = round(currentData.temperature * 100) / 100.0; // 2 decimal places
data["humidity"] = round(currentData.humidity * 100) / 100.0;
data["lightLevel"] = currentData.lightLevel;
data["alertLevel"] = currentData.alertStatus;
// Serialize and print
String jsonString;
serializeJson(doc, jsonString);
Serial.println(jsonString);
Serial.println("-------------------------\n");
}
// -------------------- THINGSBOARD TELEMETRY --------------------
void publishTelemetryNow() {
if (!mqttClient.connected()) return;
char payload[512];
snprintf(payload, sizeof(payload),
"{\"temperature\":%.2f,\"humidity\":%.2f,"
"\"lightLevel\":%d,\"alertLevel\":\"%s\",\"uptime\":%lu}",
currentData.temperature, currentData.humidity,
currentData.lightLevel, currentData.alertStatus.c_str(),
millis()/1000UL);
mqttClient.publish(TB_TELEMETRY_TOPIC, payload);
}
void publishAggregate(float t, float h, long lAvg) {
if (!mqttClient.connected()) return;
char payload[160];
snprintf(payload, sizeof(payload),
"{\"avgTemp\":%.2f,\"avgHumidity\":%.2f,\"avgLight\":%ld}",
t, h, lAvg);
mqttClient.publish(TB_TELEMETRY_TOPIC, payload);
}
void publishConnectAttributes() {
if (!mqttClient.connected() || sentConnectAttributes) return;
char attr[256];
snprintf(attr, sizeof(attr),
"{\"fw\":\"edge-v1\",\"tempHigh\":%.1f,\"tempLow\":%.1f,"
"\"humHigh\":%.1f,\"lightThresh\":%d}",
30.0, 15.0, 70.0, 1000);
mqttClient.publish(TB_ATTRIBUTES_TOPIC, attr);
sentConnectAttributes = true;
}
// =====================================================================
// -------------------- CONNECTIVITY HELPERS --------------------
void ensureWifi() {
if (WiFi.status() == WL_CONNECTED) return;
WiFi.begin(WIFI_SSID, WIFI_PASS);
delay(500);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
}
void ensureMqtt() {
if (WiFi.status() != WL_CONNECTED) return;
if (mqttClient.connected()) return;
String clientId = String("esp32-") + String((uint32_t)ESP.getEfuseMac(), HEX);
if (mqttClient.connect(clientId.c_str(), TB_TOKEN, NULL)) {
publishConnectAttributes();
}
}