#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"
// Display configuration
LiquidCrystal_I2C lcd(0x27, 16, 2); //2 rows and can fit 16 characters on each row
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 //
#define temp_alarm 18 // Hi Temp Annunciator from process control
#define humid_alarm 19 // Hi Humid Annunciator from process control
// Process Control Alarm SPs
#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 fan, Slow I for steady state)
double Kp = 100.0, Ki = 1.0, Kd = 0.5;
// REVERSE mode: Fan speeds UP when Temp goes UP
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
// --- Timing Variables ---
unsigned long lastPIDTime = 0;
const long pidInterval = 2000; // 2 seconds (matches DHT11 speed)
WiFiClient wifiClient;
PubSubClient client(wifiClient);
// WiFi credentials
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_broker = "industrial.api.ubidots.com";
const int mqtt_port = 1883; // For unencrypted connection
const char* UBIDOTS_TOKEN ="BBUS-FLBitJEEOSUACNY1IaB1SjsL2nUTGQ";
unsigned long lastReconnectAttempt = 0;
const long reconnectInterval = 5000; // Try to reconnect every 5 seconds
boolean attemptReconnect() {
Serial.println("Connecting to Ubidots MQTT...");
if (client.connect("ESP32_Client", UBIDOTS_TOKEN, "")) {
Serial.println("Connected to Ubidots");
//client.subscribe(SUB_TOPIC1, 1);
return true;
} else {
Serial.print("Failed, rc=");
Serial.print(client.state());
return false;
}
}
void setup() {
Serial.begin(115200);
pinMode(HEATER_SAFETY_PIN, OUTPUT);
pinMode(temp_alarm, OUTPUT);
pinMode(humid_alarm, OUTPUT);
pinMode(set_point_pin, INPUT);
ledcAttach(HEATER_PIN, 5000, 8);
digitalWrite(HEATER_SAFETY_PIN, HIGH);
// DHT Sensor
DHT22_PID.begin();
DHT22_Safety.begin();
delay(2000);
// Start RTC
if (!rtc.begin()) {
lcd.clear();
lcd.print("RTC Error!");
while (1);
}
// Set Time
if (rtc.lostPower()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
// LCD
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0); // (column,row)
lcd.print("Temp & Humid");
lcd.setCursor(0, 1);
lcd.print("System is Ready");
// PID
myPID.SetMode(AUTOMATIC);
myPID.SetSampleTime(pidInterval);
myPID.SetOutputLimits(0, 255); // Standard PWM limits
// WiFi and Cloud
Serial.println("Connecting to WiFi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.print("\nWiFi connected");
Serial.println(WiFi.localIP());
client.setServer(mqtt_broker, mqtt_port);
}
void loop() {
DateTime now_time = rtc.now();
unsigned long now = millis();
if (now - lastPIDTime >= pidInterval) {
lastPIDTime = now;
// 1. READ SENSORS
readSensors();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("P:T-");
lcd.print(pid_temp);
lcd.print(",H-");
lcd.print(pid_humid);
float value=0;
value = analogRead(set_point_pin);
Setpoint = map(value,0,4095,0,100);
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;
myPID.Compute();
// C. Control Fan
// 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_13-2-2026");
String payload = String("{\"temperature\":") + pid_temp + "," + String("\"heater\":") + Output + "," + String("\"current-setpoint\":") + Setpoint + "}";
client.publish(topic.c_str(), payload.c_str());
Serial.println("Published: " + payload);
}
// A. Check WiFi
if (WiFi.status() != WL_CONNECTED) {
// You could add logic here to blink an LED indicating "No WiFi"
// But we do NOTHING blocking here.
}
// B. Check MQTT
else {
if (!client.connected()) {
// Only try to reconnect once every 5 seconds
if (now - lastReconnectAttempt > reconnectInterval) {
lastReconnectAttempt = now;
// Attempt to connect. If it fails, we just continue looping PID.
if (attemptReconnect()){
lastReconnectAttempt = 0; // Reset timer on success
}
}
} else {
// C. Maintain Connection
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)) {
// check whether a value is invalid
Serial.println("ERROR: Sensor 1 (PID) failed!");
return;
}
// Serial.println("PID DHT");
// Serial.print("Temperature : "); Serial.println(pid_temp);
// Serial.print("Humidity : "); Serial.println(pid_humid);
// Serial.println("Safety DHT");
// Serial.print("Temperature : "); Serial.println(safety_temp);
// Serial.print("Humidity : "); Serial.println(safety_humid);
}