#include <DHT.h> // ESP32 to communicate with your DHT11 or DHT22 sensor to read temperature and humidity levels
#include <Wire.h> // I2C communication
#include <LiquidCrystal_I2C.h> // LCD real-time data via I2C
#include <PID_v1.h> // calculates exactly how much power to give the heater to keep the temperature perfectly steady at your setpoint
#include <WiFi.h> // ESP32's internal radio to connect to a network.
#include <PubSubClient.h> // MQTT client library and formats sensor data into the specific messages that Ubidots understands and sends them to the industrial.api.ubidots.com broker
#include <RTClib.h> // To work with Real-Time Clock (RTC) module
// Display configuration
LiquidCrystal_I2C lcd(0x27, 16, 2); //2 rows and can fit 16 characters on each row
//LiquidCrystal_I2C lcd(0x27, 20, 4); // (address, columns, rows)
RTC_DS3231 rtc;
#define DHTTYPE DHT22 // Type of sensor model we are using
// I/O Pin Assignments
#define DHT22_1PIN 4 // PID Control Sensor 1
#define DHT22_2PIN 2 // Safety Protection Sensor 2
#define set_point_pin 34 // Manual SP adjustment with potentiometer
#define HEATER_PIN 16 // Connect MOSFET Gate to GPIO 16 for heating element
#define HEATER_SAFETY_PIN 17 // Connect MOSFET Gate to GPIO 17 for safety protection
#define temp_alarm 18 // Hi Temp Annunciator from process control
#define humid_alarm 19 // Hi Humid Annunciator from process control
// Process Control Alarm SPs. SOL for operator intervention
#define temp_h 30 // Hi Temp SP
#define humid_h 65 // Hi Humid SP
// Safety Protection set points
#define safety_temp_hh 35
#define safety_humid_hh 70
#define safety_temp_ll 15
#define safety_humid_ll 50
DHT DHT22_PID(DHT22_1PIN, DHTTYPE); // For PID Control
DHT DHT22_Safety(DHT22_2PIN, DHTTYPE); // For Process Safety Protection
// --- VARIABLES ---
float pid_temp, pid_humid; // Sensor 1 readings
float safety_temp, safety_humid; // Sensor 2 readings
// --- PID Variables ---
double Setpoint, Input, Output; //
// PID Tuning (Aggressive P for HEATER, Slow I for steady state, D for fastest rising time to prevent overshoot/undershoot)
double Kp = 10.0, Ki = 0.1, Kd = 1.0;
// Direct mode: HEATER speeds UP when Temp goes UP
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
// --- Timing Variables ---
unsigned long lastPIDTime = 0; // Store last PID calculation time
const long pidInterval = 2000; // 2 seconds (matches DHT22 speed)
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 use log in
unsigned long lastReconnectAttempt = 0; // Track last MQTT reconnect time
const long reconnectInterval = 5000; // Try to reconnect every 5 seconds
boolean attemptReconnect() {
Serial.println("Connecting to Ubidots MQTT..."); // Print connection attempt message
// Try to connect to MQTT broker
// Parameters: (clientID, username, password)
// clientID = "ESP32_Client" (unique identifier for this device)
// username = UBIDOTS_TOKEN (your API token as username)
// password = "" (empty string for Ubidots)
if (client.connect("ESP32_Client", UBIDOTS_TOKEN, "")) {
Serial.println("Connected to Ubidots"); // Successfully connected
//client.subscribe(SUB_TOPIC1, 1); // Commented out subscription
return true; // Return success
} else { // Connection failed
Serial.print("Failed, rc=");
Serial.print(client.state()); // Print error code
return false; // Return failure
}
}
void setup() {
Serial.begin(115200); // Initialize serial for debugging
pinMode(HEATER_SAFETY_PIN, OUTPUT); // Safety protection control
pinMode(temp_alarm, OUTPUT); // Temperature warning LED/buzzer
pinMode(humid_alarm, OUTPUT); // Humidity warning LED/buzzer
// Configure input pins
pinMode(set_point_pin, INPUT); // Manual setpoint adjustment 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)
// Start RTC
if (!rtc.begin()) { // If RTC fails to start, show error on LCD
lcd.clear(); // Remove any text on screen
lcd.print("RTC Error!"); // Show error message
// Stop everything here (infinite loop)
while (1); // Program freezes here forever
}
// Set Time and Check if RTC lost power (battery dead or first time use)
if (rtc.lostPower()) { //Set RTC to the date/time when this code was compiled
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));//__DATE__ = today's date (when you uploaded the code) 7 __TIME__ = current time (when you uploaded the code)
}
// 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
myPID.SetMode(AUTOMATIC); // Turn PID on
myPID.SetSampleTime(pidInterval); // Compute every 1000ms
myPID.SetOutputLimits(0, 255); // Standard PWM limits (Analog Write)
// WiFi and Cloud
Serial.println("Connecting to WiFi...");
WiFi.begin(ssid, password); // Connect to WiFi
while (WiFi.status() != WL_CONNECTED) { // Check connection status
delay(1000);
Serial.print(".");
}
Serial.print("\nWiFi connected");
Serial.println(WiFi.localIP()); // Get IP address
client.setServer(mqtt_broker, mqtt_port); //Configures the MQTT broker (server) to connect to
// The address of your MQTT server (e.g., "broker.hivemq.com") & The port number for MQTT (usually 1883 for non-encrypted)
}
void loop() {
DateTime now_time = rtc.now(); //Gets current time from RTC module
unsigned long now = millis(); //Gets Arduino uptime in milliseconds
if (now - lastPIDTime >= pidInterval) { //Checks if 1000ms has passed since last PID run
lastPIDTime = now; //Updates timer for next interval
// 1. READ SENSORS
readSensors();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("P:T1-");
lcd.print(pid_temp);
lcd.print(",H1-");
lcd.print(pid_humid);
float value=0;
value = analogRead(set_point_pin);
Setpoint = map(value,0,4095,0,100);
// lcd.setCursor(0, 2);
// lcd.print("P:T2-");
// lcd.print(safety_temp);
// lcd.print(",H2-");
// lcd.print(safety_humid);
lcd.setCursor(0, 1);
lcd.print("Set_point:");
lcd.print(Setpoint);
if(pid_temp > temp_h){digitalWrite(temp_alarm, HIGH);}
else {digitalWrite(temp_alarm,LOW);}
if(pid_humid > humid_h){digitalWrite(humid_alarm, HIGH);}
else {digitalWrite(humid_alarm,LOW);}
if(now_time.minute()>=29 && now_time.minute()<=50) {
if(safety_temp > safety_temp_hh || safety_humid < safety_humid_ll)
digitalWrite(HEATER_SAFETY_PIN,LOW);
else {
digitalWrite(HEATER_SAFETY_PIN, HIGH);
Input = pid_temp; //Feeds current temperature reading into PID as the process variable
myPID.Compute(); //Runs PID math using Input vs Setpoint to calculate new Output value
// C. Control HEATER
// Note: analogWrite works on ESP32 Core 3.0+. Use ledcWrite for older versions.
ledcWrite(HEATER_PIN, (int)Output);
Serial.print("PID Output : ");
Serial.println((int)Output);
}
}
else digitalWrite(HEATER_SAFETY_PIN,LOW);
// Upload to Ubidots Cloud
String topic = String("/v1.6/devices/iot_16-2-2026"); //Creates Ubidots API path for your specific device
String payload = String("{\"temperature-1\":") + pid_temp +
"," + String("\"humidity-1\":") + pid_humid +
"," + String("\"heater temperature\":") + Output +
"," + String("\"current-setpoint\":") + Setpoint +
"," + String("\"temperature-2\":") + safety_temp +
"," + String("\"humidity-2\":") + safety_humid + "}";//Builds JSON data packet with temperature, heater output, and target setpoint
client.publish(topic.c_str(), payload.c_str());//Sends the data to Ubidots cloud via MQTT
Serial.println("Published: " + payload);//Prints confirmation in Serial Monitor for debugging
}
// A. Check WiFi
if (WiFi.status() != WL_CONNECTED) {
// Checks if WiFi is disconnected.
}
// B. Check MQTT
else {
if (!client.connected()) {
// Checks if MQTT client is disconnected (even though WiFi is on).
if (now - lastReconnectAttempt > reconnectInterval) { //Checks if 5 seconds have passed since last reconnect attempt
lastReconnectAttempt = now; //Updates timer to current time
if (attemptReconnect()){ //Calls function to try reconnecting to MQTT broker
lastReconnectAttempt = 0; // Resets timer if reconnection successful
}
}
} else {
// If MQTT connected, client.loop() maintains connection and processes messages
client.loop();
}
}
}
// --- 1. READ SENSORS ---
void readSensors() {
// Read Sensor 1 (PID control)
pid_temp = DHT22_PID.readTemperature();
pid_humid = DHT22_PID.readHumidity();
// Read Sensor 2 (Safety protection)
safety_temp = DHT22_Safety.readTemperature();
safety_humid = DHT22_Safety.readHumidity();
// Check for sensor errors
if (isnan(pid_temp) || isnan(pid_humid) || isnan(safety_temp) || isnan(safety_humid)) { //Checks if ANY sensor reading is "Not a Number" (invalid/error)
// check whether a value is invalid
Serial.println("ERROR: Sensor 1 (PID) failed!"); //Prints error message to Serial Monitor
return;
}
Serial.println("PID DHT"); //Prints header "PID DHT" to identify first sensor
Serial.print("Temperature : "); Serial.println(pid_temp);//Prints "Temperature : " label without new line, then prints actual temperature value with new line
Serial.print("Humidity : "); Serial.println(pid_humid);//Prints "Humidity : " label then humidity value
Serial.println("Safety DHT");//Prints header "Safety DHT" to identify second sensor
Serial.print("Temperature : "); Serial.println(safety_temp);//Prints temperature label then safety sensor temperature
Serial.print("Humidity : "); Serial.println(safety_humid);//Prints humidity label then safety sensor humidity
}