#include <DHT.h> // DHT sensor library
#include <Wire.h> // I2C communication
#include <LiquidCrystal_I2C.h> // I2C LCD control
#include <PID_v1.h> // PID control algorithm
#include <WiFi.h> // WiFi connectivity
#include <PubSubClient.h> // MQTT for cloud comms
// Display configuration
LiquidCrystal_I2C lcd(0x27, 16, 2); // LCD: address 0x27, 16 cols, 2 rows
//LiquidCrystal_I2C lcd(0x27, 20, 4); // LCD: address 0x27, 20 cols, 4 rows
#define DHTTYPE DHT22 // Sensor type: DHT22
// I/O Pin Assignments
#define DHT22_1PIN 4 // Sensor 1 (PID control) on GPIO4
#define DHT22_2PIN 2 // Sensor 2 (safety) on GPIO2
#define set_point_pin 34 // Potentiometer for temp adjustment
#define HEATER_PIN 16 // MOSFET gate for heater control
#define HEATER_SAFETY_PIN 17 // MOSFET for safety cutoff
#define temp_alarm 18 // LED/buzzer for high temp
#define humid_alarm 19 // LED/buzzer for high humidity
// Process Control Alarm Setpoints
#define temp_h 30 // High temp alarm threshold (°C)
#define humid_h 65 // High humidity alarm threshold (%)
// Safety Protection set points
#define safety_temp_hh 35 // Safety high temp cutoff
#define safety_humid_hh 70 // Safety high humidity cutoff
#define safety_temp_ll 15 // Safety low temp cutoff
#define safety_humid_ll 50 // Safety low humidity cutoff
// Create DHT sensor objects
DHT DHT22_PID(DHT22_1PIN, DHTTYPE); // Primary sensor for control
DHT DHT22_Safety(DHT22_2PIN, DHTTYPE); // Backup sensor for safety
// --- VARIABLES ---
float pid_temp, pid_humid; // Sensor 1 readings
float safety_temp, safety_humid; // Sensor 2 readings
// --- PID Variables ---
double Setpoint, Input, Output; // Target, current, output value
// PID tuning constants
double Kp = 10.0, Ki = 0.1, Kd = 1.0; // Proportional, Integral, Derivative
// Direct mode: HEATER speeds UP when Temp goes UP
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); // PID object
// --- Timing Variables ---
unsigned long lastPIDTime = 0; // Last PID calculation time
const long pidInterval = 2000; // Run PID every 2 seconds
WiFiClient wifiClient; // Create WiFi client object
PubSubClient client(wifiClient); // Create MQTT client using WiFi
// WiFi credentials
const char* ssid = "Wokwi-GUEST"; // Your WiFi network name
const char* password = ""; // Your WiFi password (empty for Wokwi)
const char* mqtt_broker = "industrial.api.ubidots.com"; // Ubidots server
const int mqtt_port = 1883; // For unencrypted connection(std MQTT port)
const char* UBIDOTS_TOKEN ="BBUS-FLBitJEEOSUACNY1IaB1SjsL2nUTGQ"; // User ID from ubidits
unsigned long lastReconnectAttempt = 0; // Track last MQTT reconnect time
const long reconnectInterval = 5000; // Try to reconnect every 5 seconds
// Function prototypes (declare before use)
void readSensors(); // Reads both DHT sensors
void checkWiFiAndMQTT(); // Manages cloud connection
boolean attemptReconnect(); // Tries to reconnect to MQTT
// ========== SETUP FUNCTION (runs once at start)==========
void setup() {
Serial.begin(115200); // Start serial monitor at 115200 baud
// Configure pins as inputs/outputs
pinMode(HEATER_SAFETY_PIN, OUTPUT); // Safety protection control
pinMode(temp_alarm, OUTPUT); // Temperature warning LED/buzzer
pinMode(humid_alarm, OUTPUT); // Humidity warning LED/buzzer
pinMode(set_point_pin, INPUT); // Manual setpoint adjustment for potentiometer at PIN 34
ledcAttach(HEATER_PIN, 5000, 8); // Setup PWM for heater (5kHz, 8-bit = 0-255) so called limitation
digitalWrite(HEATER_SAFETY_PIN, HIGH); // Enable heater safety circuit
// Initialize both DHT22 sensors
DHT22_PID.begin(); // Primary sensor for PID control
DHT22_Safety.begin(); // Backup sensor for safety monitoring
delay(2000); // Wait for sensors to stabilize (DHT22 requires 2s warm-up)
// LCD dispaly setup
lcd.init(); // Turn on the LCD so it can show text
lcd.backlight(); // Light up the screen so you can see it
lcd.setCursor(0, 0); // (column, row) - starts at 0
lcd.print("Temp & Humid"); // Display what this system does
lcd.setCursor(0, 1); // Move cursor to second line (row 1, column 0)
lcd.print("System is Ready"); // Show second line on LCD and tell user system is working
//lcd.setCursor(0, 0); // (column, row) - starts at 0
//lcd.print("Temp-1 & Humid-1"); // Display what this system does
//lcd.setCursor(0, 1); Move cursor to second line (row 1, column 0)
//lcd.print("System is Ready"); // Show second line on LCD and tell user system is working
//lcd.setCursor(0, 3);
//lcd.print("Temp-2 & Humid-2");
//lcd.setCursor(0, 4);
//lcd.print("TIME");
// PID set up
myPID.SetMode(AUTOMATIC); // Turn PID on
myPID.SetSampleTime(pidInterval); // Compute every 1000ms
myPID.SetOutputLimits(0, 255); // Standard PWM limits (Analog Write)
// WiFi and Cloud set up
Serial.println("Connecting to WiFi..."); // Status message
WiFi.begin(ssid, password); // Start WiFi connection
while (WiFi.status() != WL_CONNECTED) { // Wait for connection
delay(1000); // Wait 1 sec
Serial.print("."); // Print dot for each attempt
}
Serial.print("\nWiFi connected, IP:"); // Success message
Serial.println(WiFi.localIP()); // Show assigned IP
client.setServer(mqtt_broker, mqtt_port); //Set MQTT broker
// The address of your MQTT server (e.g., "broker.hivemq.com") & The port number for MQTT (usually 1883 for non-encrypted)
}
// ========== LOOP FUNCTION ==========
void loop() {
unsigned long now = millis(); // Get current time in ms
float heating_percentage = 0; //Stores heater power as percentage (0-100%)
// Handle PID control and display (runs every 2 seconds)
if (now - lastPIDTime >= pidInterval) { //Checks if 1000ms has passed since last PID run
lastPIDTime = now; //Updates timer for next interval
// 1. READ SENSORS
readSensors(); //Get temp/humid from both sensors
lcd.clear(); // Clear screen
lcd.setCursor(0, 0); // Top row
lcd.print("P:T1-"); // Label for sensor1 temp
lcd.print(pid_temp,1); // Show temp value
lcd.print(",H1-"); // Label for sensor1 humidity
lcd.print(pid_humid,1); // Show humidity value
// Read setpoint from potentiometer
float value = analogRead(set_point_pin); // 0-4095 ADC reading
Setpoint = map(value, 0, 4095, 0, 100); // Convert to 0-100°C
// lcd.setCursor(0, 2);
// lcd.print("P:T2-");
// lcd.print(safety_temp,1);
// lcd.print(",H2-");
// lcd.print(safety_humid,1);
lcd.setCursor(0, 1); // Bottom row
lcd.print("Set_point:"); // Label
lcd.print(Setpoint,1); // Show target temperature
// Process alarms (check if thresholds exceeded)
if(pid_temp > temp_h) { digitalWrite(temp_alarm, HIGH); } // Turn on temp alarm
else { digitalWrite(temp_alarm, LOW); } // Turn off temp alarm
if(pid_humid > humid_h) { digitalWrite(humid_alarm, HIGH); } // Turn on humid alarm
else { digitalWrite(humid_alarm, LOW); } // Turn off humid alarm
// Safety protection (emergency shutdown)
if(safety_temp > safety_temp_hh || safety_humid < safety_humid_ll) {
digitalWrite(HEATER_SAFETY_PIN, LOW); // Kill heater (LOW = off)
} else {
digitalWrite(HEATER_SAFETY_PIN, HIGH); // Enable heater
Input = pid_temp; // Feed current temp to PID
myPID.Compute(); // Calculate new output value
ledcWrite(HEATER_PIN, (int)Output); // Apply PWM to heater
// Calculate percentage (Output is 0-255)
heating_percentage = (Output / 255.0) * 100.0;
// Show BOTH raw and percentage in serial monitor
Serial.print("PID Raw: ");
Serial.print((int)Output); // Shows raw value (e.g., 169)
Serial.print(", Percentage: ");
Serial.print(heating_percentage, 1); // Shows with 1 decimal (e.g., 66.3)
Serial.println("%"); // Adds the % symbol
}
} else {
digitalWrite(HEATER_SAFETY_PIN, LOW); // Keep heater off between cycles
}
// 2. Check WiFi and MQTT connection
checkWiFiAndMQTT(); // Ensure cloud connection
// 3. Upload to Ubidots Cloud
String topic = String("/v1.6/devices/iot_22-2-2026"); // Ubidots device path
// Build JSON payload for Ubidots
String payload = String("{\"temperature-1\":") +
(isnan(pid_temp) ? "\"nan\"" : String(pid_temp, 1)) + // Temp1 or "nan"
"," + String("\"humidity-1\":") +
(isnan(pid_humid) ? "\"nan\"" : String(pid_humid, 1)) + // Humid1 or "nan"
"," + String("\"heater_percent\":") +
(isnan(Output) ? "\"nan\"" : String(heating_percentage, 1)) + // Heater % or "nan"
"," + String("\"current-setpoint\":") +
(isnan(Setpoint) ? "\"nan\"" : String(Setpoint, 1)) + // Setpoint or "nan"
"," + String("\"temperature-2\":") +
(isnan(safety_temp) ? "\"nan\"" : String(safety_temp, 1)) + // Temp2 or "nan"
"," + String("\"humidity-2\":") +
(isnan(safety_humid) ? "\"nan\"" : String(safety_humid, 1)) + "}"; // Humid2 or "nan"
client.publish(topic.c_str(), payload.c_str()); // Send to cloud
Serial.println("Published: " + payload); // Confirm in serial monitor
delay(5000); // Wait 5 seconds before next publication
}
// ========== FUNCTION TO CHECK WIFI AND MQTT CONNECTION==========
void checkWiFiAndMQTT() {
unsigned long now = millis(); // Current time
// A. Check WiFi connection
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi disconnected!"); // Alert
WiFi.reconnect(); // Try to reconnect
}
// B. Check MQTT connection (if WiFi is OK)
else {
if (!client.connected()) { // MQTT disconnected?
// Time to try reconnecting?
if (now - lastReconnectAttempt > reconnectInterval) {
lastReconnectAttempt = now; // Update timer
if (attemptReconnect()) { // Try to connect
lastReconnectAttempt = 0; // Reset timer on success
}
}
} else {
client.loop(); // Maintain MQTT connection
}
}
}
// ========== MQTT RECONNECT FUNCTION ==========
boolean attemptReconnect() {
Serial.println("Connecting to Ubidots MQTT..."); // Status
// Connect using token as username, empty password
if (client.connect("ESP32_Client", UBIDOTS_TOKEN, "")) {
Serial.println("Connected to Ubidots"); // Success
return true; // Report success
} else {
Serial.print("Failed, rc="); // Show error code
Serial.println(client.state());
return false; // Report failure
}
}
// ========== READ SENSORS FUNCTION ==========
void readSensors() {
// Read Sensor 1 (PID control)
pid_temp = DHT22_PID.readTemperature(); // Get temp in °C
pid_humid = DHT22_PID.readHumidity(); // Get humidity in %
// Read Sensor 2 (Safety protection)
safety_temp = DHT22_Safety.readTemperature(); // Get temp in °C
safety_humid = DHT22_Safety.readHumidity(); // Get humidity in %
// Check for sensor errors (NaN = Not a Number)
if (isnan(pid_temp) || isnan(pid_humid) || isnan(safety_temp) || isnan(safety_humid)) {
Serial.println("ERROR: Sensor reading failed!"); // Alert
return; // Exit function
}
// Print readings to serial monitor
Serial.println("PID DHT"); // Label for sensor1
Serial.print("Temperature : "); Serial.println(pid_temp,1);
Serial.print("Humidity : "); Serial.println(pid_humid,1);
Serial.println("Safety DHT"); // Label for sensor2
Serial.print("Temperature : "); Serial.println(safety_temp,1);
Serial.print("Humidity : "); Serial.println(safety_humid,1);
}