#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <time.h>
// Define sensor pins & types
#define DHTPIN 26
#define DHTTYPE DHT22
#define BUTTON_PIN 14
#define LED_PIN 19
// set WiFi & MQTT
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "broker.hivemq.com";
// MQTT topic
const char* mqtt_topic_pub = "IoT/WXP/sensor"; // send temperature
const char* mqtt_topic_time = "IoT/WXP/time"; // send time
const char* mqtt_topic_sub = "IoT/WXP/led"; // subscribe control LED On/Off
const char* mqtt_topic_alarm = "IoT/WXP/alarm"; // alarm message
const char* mqtt_topic_lwt = "IoT/WXP/status"; // device status "Last will message"
// Create client and sensor objects
WiFiClient espClient; // Create a WiFi client object, connect to WiFi network
PubSubClient client(espClient); //Create an MQTT client, use espClient for network comms
DHT dht(DHTPIN, DHTTYPE); //sensor object dht,pin/type
LiquidCrystal_I2C lcd(0x27, 20, 4); // display with I2C interface, address 0x27, 20 columns and 4 rows.
// State variables and time control
bool ledState = false; // LED status,on/off
unsigned long lastMsg = 0; // MQTT last message sent time
unsigned long lastButtonTime = 0; // last button pressed time
unsigned long lastDisplayChange = 0; // last display change
const long publishInterval = 3000; // publish data once every 3 seconds to MQTT
const long idleTimeToSleep = 60000; // 1 mins.No any action, enter sleep power mode
const long displayInterval = 3000; // Switch display 3 seconds
int displayState = 0; // 0:welcome, 1:time, 2:temp
char msg[100]; // MQTT messages, maximum length 100 bytes
// LCD display °C symbol
uint8_t dot[] = {0x0E, 0x0A, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00};
// setup WiFi
void setup_wifi() {
Serial.println("Connecting to WiFi...");
WiFi.begin(ssid, password);
// wait WiFi connect
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(250);
}
Serial.println("\nWiFi connected successfully!");
}
// handles subscribed MQTT messages
void callback(char* topic, byte* payload, unsigned int length) {
payload[length] = 0; //Ensure that the string ends with a null character
String command = String((char*)payload);
command.toUpperCase(); // Convert to UPPERCASE
Serial.print("Received command: ");
Serial.println(command);
// LED control command processing
if (command == "ON") {
digitalWrite(LED_PIN, HIGH);
ledState = true;
Serial.println("LED turned ON via MQTT command");
} else if (command == "OFF") {
digitalWrite(LED_PIN, LOW);
ledState = false;
Serial.println("LED turned OFF via MQTT command");
}
}
// reconnect MQTT
void reconnect() {
// Increase reconnection interval control to avoid frequent retries
static unsigned long lastReconnectAttempt = 0;
const long reconnectInterval = 7000; // Retry interval of 7 seconds
if (millis() - lastReconnectAttempt < reconnectInterval) {
return; // Retry time has not yet arrived
}
lastReconnectAttempt = millis();
Serial.println("Connecting to MQTT...");
// Connect to MQTT server, set will message
if (client.connect("ESP32ClientDevice", mqtt_topic_lwt, 0, true, "offline")) {
Serial.println("MQTT connected successfully");
client.publish(mqtt_topic_lwt, "online", true); // send online messages
client.subscribe(mqtt_topic_sub); // subscribed LED
// Set longer KeepAlive time and socket timeout to improve stability.
client.setKeepAlive(60);
client.setSocketTimeout(30);
} else {
Serial.print("MQTT connection failed, status=");
Serial.print(client.state());
Serial.println(", will retry ....");
}
}
// LCD welcome messages
void displayWelcome() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ESP32!");
lcd.setCursor(0, 1);
lcd.print("Hello, World!");
}
// LCD Date & Time,send to MQTT
void displayDateTime() {
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
char dtbuf[21];
// format"YYYY-MM-DD HH:MM:SS"
strftime(dtbuf, sizeof(dtbuf), "%Y-%m-%d %H:%M:%S", &timeinfo);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("DateTime:");
lcd.setCursor(0, 1);
lcd.print(dtbuf);
// send to MQTT
client.publish(mqtt_topic_time, dtbuf);
}
}
// LCD Humidity & Temperature, send to MQTT
void displayTempHumidity() {
// Check for timeout b4 reading the sensor to avoid sleep caused by long-term operation
unsigned long start = millis();
// read Humidity & Temperature
float h = dht.readHumidity();
float c = dht.readTemperature();
float f = dht.readTemperature(true);
// If the read time is too long, return directly without updating the display.
if (millis() - start > 1000) {
return;
}
// check read successful or not
if (isnan(h) || isnan(c) || isnan(f)) {
Serial.println(F("Failed to read from DHT sensor!"));
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Sensor Error!");
return;
}
// Print temperature and humidity data
Serial.printf(
"Humidity: %.1f Temperature: %.1f deg C %.1f deg F\n",
h, c, f
);
// LCD display temperature and humidity data
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Humidity: "); lcd.print(h, 1); lcd.print("%");
lcd.setCursor(0, 1);
lcd.print("Temperature: "); lcd.print(c, 2); lcd.write(0); lcd.print("C");
lcd.setCursor(0, 2);
lcd.print("Temperature: "); lcd.print(f, 1); lcd.write(0); lcd.print("F");
// send to MQTT(JSON format)
snprintf(msg, sizeof(msg), "{\"temp\": %.1f, \"hum\": %.1f}", c, h);
client.publish(mqtt_topic_pub, msg);
}
// initialize function, only 1 time
void setup() {
Serial.begin(115200);
Serial.println("Starting setup()...");
// Configure IO pins
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW); // start is off
// initialize LCD
lcd.init();
lcd.backlight();
lcd.clear();
// Create custom characters(℃)
lcd.createChar(0, dot);
// initialize sensor & WiFi
dht.begin();
setup_wifi();
// Configure MQTT client
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
// Configure NTP time sync (UTC+8 time zone)
configTime(8 * 3600, 0, "pool.ntp.org");
struct tm timeinfo;
// Waiting for time sync to complete
while (!getLocalTime(&timeinfo)) {
Serial.println("Waiting for time sync...");
delay(1000);
}
Serial.println("Time synchronized successfully");
// initialize Timer and display
lastButtonTime = millis();
lastDisplayChange = millis();
displayWelcome();
Serial.println("setup() complete, entering loop()");
}
// Main loop,continuously recycled aft system startup
void loop() {
// Chk the MQTT connection and reconnect if necessary.
if (!client.connected()) {
reconnect();
}
// Handling MQTT network events
client.loop();
unsigned long now = millis();
// Button detection (using non-blocking mode)
if (digitalRead(BUTTON_PIN) == LOW) {
delay(50);
if (digitalRead(BUTTON_PIN) == LOW) {
// change LED status
ledState = !ledState;
digitalWrite(LED_PIN, ledState);
Serial.print("Button pressed, LED toggled to: ");
Serial.println(ledState ? "ON" : "OFF");
// Press the publish button to send an alert to MQTT.
client.publish(mqtt_topic_alarm, "Button pressed!");
//Reset the timer to avoid sleep mode
lastButtonTime = now;
lastDisplayChange = now;
// Wait for button release
while (digitalRead(BUTTON_PIN) == LOW) {
delay(10);
// Continuously update the timer while waiting for the button to be released to prevent sleep mode.
now = millis();
lastButtonTime = now;
}
}
}
// Check whether there has been no operation for a long time and enter sleep mode.
if (now - lastButtonTime > idleTimeToSleep) {
Serial.println("Idle timeout reached, entering light sleep...");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Sleeping 50s...");
// Set to wake up after 50 seconds
esp_sleep_enable_timer_wakeup(50 * 1000000ULL);
Serial.println("Entering light sleep now.");
esp_light_sleep_start(); // go into light sleep mode
// Code executed after wakeup
Serial.println("Woke up from light sleep!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Woke up!");
delay(500);
// Reset status and timer
lastButtonTime = millis();
lastDisplayChange = millis();
displayState = 0;
displayWelcome();
return; // Return from the loop function and restart the loop.
}
// Display content rotation control
if (now - lastDisplayChange >= displayInterval) {
lastDisplayChange = now;
displayState = (displayState + 1) % 3; // Cycle through display states
// Update LCD content based on current display status
switch (displayState) {
case 0: displayWelcome(); break;
case 1: displayDateTime(); break;
case 2: displayTempHumidity(); break;
}
}
}