#include <WiFi.h>
#include <PubSubClient.h>
#include <PID_v1.h>
#include <DHT.h>
// --- Ubidots & Network Settings ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// Ubidots MQTT Configuration
const char* mqtt_server = "industrial.api.ubidots.com";
#define UBIDOTS_TOKEN "BBFF-3sCuW2gt5Ghq17c50h4Mqsgp8L4OPI" // Put your BBFF-xxx token here
//#define DEVICE_LABEL "private" // The Device Label in Ubidots
#define VAR_SETPOINT "target-temp" // The Variable Label for your slider
// --- Hardware Settings ---
#define DHTPIN 4 // Connect DHT11 Data pin to GPIO 4
#define DHTTYPE DHT22
#define FAN_PIN 16 // Connect MOSFET Gate to GPIO 16
// --- Objects ---
WiFiClient espClient;
PubSubClient client(espClient);
DHT dht(DHTPIN, DHTTYPE);
// --- PID Variables ---
double Setpoint, Input, Output;
// PID Tuning (Aggressive P for fan, Slow I for steady state)
double Kp = 20.0, Ki = 5.0, Kd = 1.0;
// REVERSE mode: Fan speeds UP when Temp goes UP
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, REVERSE);
// --- Timing Variables ---
unsigned long lastPIDTime = 0;
const long pidInterval = 2000; // 2 seconds (matches DHT11 speed)
unsigned long lastReconnectAttempt = 0;
const long reconnectInterval = 5000; // Try to reconnect every 5 seconds
/* ================= Subscribe Topics ================= */
const char* SUB_TOPIC1 = "/v1.6/devices/private/target-temp/lv";
// --- MQTT Callback (Received Message Handler) ---
// This runs when Ubidots sends a new value for the "target-temp"
void callback(char* topic, byte* payload, unsigned int length) {
// 1. Create a buffer for the message
char message[length + 1];
for (int i = 0; i < length; i++) {
message[i] = (char)payload[i];
}
message[length] = '\0'; // Null-terminate string
// 2. Check which topic triggered the callback
// Topic format: /v1.6/devices/{device_label}/{variable_label}/lv
String topicStr = String(topic);
if (topicStr.indexOf(VAR_SETPOINT) > 0) {
Setpoint = atof(message); // Convert string to float
Serial.print("Updated Setpoint from Ubidots: ");
Serial.println(Setpoint);
}
}
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);
dht.begin();
// Setup WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected");
// Setup MQTT
client.setBufferSize(512);
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
// Setup PID
Setpoint = 25.0; // Default target
myPID.SetMode(AUTOMATIC);
myPID.SetSampleTime(pidInterval);
myPID.SetOutputLimits(0, 255); // Standard PWM limits
}
void loop() {
// 1. Run Control Loop (Non-blocking Timer)
unsigned long now = millis();
if (now - lastPIDTime >= pidInterval) {
lastPIDTime = now;
// A. Read Sensor
float t = dht.readTemperature();
if (isnan(t)) {
Serial.println("DHT Read Failed!");
return;
}
// B. Compute PID
Input = t;
myPID.Compute();
// C. Control Fan
// Note: analogWrite works on ESP32 Core 3.0+. Use ledcWrite for older versions.
analogWrite(FAN_PIN, (int)Output);
// D. Publish Status to Ubidots
// Ubidots expects JSON: {"variable": value, "variable2": value}
// Topic: /v1.6/devices/{device_label}
String topic = String("/v1.6/devices/private");
// Format JSON string
String payload = String("{\"temperature\":") + t + "," + String("\"fan-speed\":") + 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();
}
}
}