// ESP32 + Wokwi + MQTT (PubSubClient) + DHT22 + Buttons + LWT
#include <WiFi.h>
#include <PubSubClient.h>
#include "DHT.h"
// Readable Aliases
#define ON HIGH
#define OFF LOW
// Pin Assignments
const int led1 = 18; // BLUE LED
const int led2 = 13; // RED LED
const int btn1 = 12; // RED Button (wired to GND) -> INPUT_PULLUP
const int btn2 = 14; // BLUE Button (wired to GND) -> INPUT_PULLUP
const int potPin = 34; // Analog input
const int dhtPin = 4; // DHT22 data pin
// WiFi (Wokwi virtual AP)
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const uint8_t wifi_channel = 6; // IMPORTANT for Wokwi
// MQTT Broker
const char* mqtt_server = "test.mosquitto.org";
const uint16_t mqtt_port = 1883;
// MQTT Topics
const char* topicTemp = "Brownies/Azriel/dht22/temperature";
const char* topicHum = "Brownies/Azriel/dht22/humidity";
const char* topicPot = "Brownies/Azriel/potentiometer";
const char* topicStatus = "Brownies/Azriel/sensor/status"; // presence topic
const char* topicLed1Control = "Brownies/Azriel/led1/control"; // BLUE
const char* topicLed2Control = "Brownies/Azriel/led2/control"; // RED
// LWT configuration
const char* deviceId = "ESP32-Taufiq"; // unique per device
const char* willMsgOffline = "offline";
const char* msgOnline = "online";
const int willQos = 1;
const bool willRetain = true;
WiFiClient espClient;
PubSubClient client(espClient);
DHT dht(dhtPin, DHT22);
unsigned long lastPublish = 0;
// Variables to remember App states
bool appLed1State = false; // BLUE LED
bool appLed2State = false; // RED LED
// --- MQTT message callback ---
void callback(char* topic, byte* payload, unsigned int length) {
String message;
message.reserve(length);
for (unsigned int i = 0; i < length; i++) message += (char)payload[i];
Serial.printf("MQTT [%s]: %s\n", topic, message.c_str());
if (String(topic) == topicLed1Control) {
appLed1State = (message == "ON");
} else if (String(topic) == topicLed2Control) {
appLed2State = (message == "ON");
}
}
// --- Robust Wi‑Fi connect (Wokwi) ---
void setup_wifi() {
Serial.println();
Serial.printf("Connecting to %s\n", ssid);
WiFi.mode(WIFI_STA);
WiFi.persistent(false);
WiFi.setSleep(false);
// In Wokwi, specifying channel 6 avoids long scans and “infinite dots”
WiFi.begin(ssid, password, wifi_channel);
const uint32_t t0 = millis();
const uint32_t timeoutMs = 12000; // 12s timeout
while (WiFi.status() != WL_CONNECTED && (millis() - t0) < timeoutMs) {
delay(250);
Serial.print(".");
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
Serial.print("WiFi connected, IP: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("WiFi connect timed out. In Wokwi, ensure the Wi‑Fi icon is enabled.");
}
}
// --- MQTT reconnect with LWT ---
void reconnectMqtt() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection... ");
// Connect with Last Will
// connect(clientID, willTopic, willQos, willRetain, willMessage)
bool ok = client.connect(deviceId, topicStatus, willQos, willRetain, willMsgOffline);
if (ok) {
Serial.println("connected");
// Publish presence = "online" (retained)
client.publish(topicStatus, msgOnline, true);
// Subscribe to control topics
client.subscribe(topicLed1Control);
client.subscribe(topicLed2Control);
} else {
Serial.printf("failed, rc=%d. Retrying in 3s\n", client.state());
delay(3000);
}
}
}
void setup() {
Serial.begin(115200);
delay(100);
pinMode(led1, OUTPUT);
pinMode(led2, OUTPUT);
// Buttons wired to GND -> use INPUT_PULLUP (pressed = LOW)
pinMode(btn1, INPUT_PULLUP);
pinMode(btn2, INPUT_PULLUP);
dht.begin();
setup_wifi();
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
// If Wi‑Fi dropped, try to rejoin quickly
setup_wifi();
}
if (!client.connected()) {
reconnectMqtt();
}
client.loop(); // keep MQTT traffic flowing
// --- LED Logic ---
// BLUE LED on pin 18: controlled by app OR physical button 12
digitalWrite(led1, (appLed1State || digitalRead(btn1) == LOW) ? ON : OFF);
// RED LED on pin 13: controlled by app OR physical button 14
digitalWrite(led2, (appLed2State || digitalRead(btn2) == LOW) ? ON : OFF);
// --- SENSOR DATA PUBLISHING (every 2 seconds) ---
unsigned long now = millis();
if (now - lastPublish > 2000) {
lastPublish = now;
float t = dht.readTemperature();
float h = dht.readHumidity();
int potValue = analogRead(potPin);
// Only publish valid DHT readings
if (!isnan(t) && !isnan(h)) {
client.publish(topicTemp, String(t).c_str());
client.publish(topicHum, String(h).c_str());
} else {
Serial.println("DHT read failed (NaN). Skipping publish.");
}
client.publish(topicPot, String(potValue).c_str());
Serial.print("Sent -> Temp: "); Serial.print(t);
Serial.print(" | Hum: "); Serial.print(h);
Serial.print(" | Pot: "); Serial.println(potValue);
}
}