#include <LiquidCrystal_I2C.h>
#include <WiFi.h> // Defines WiFi variable
#include "time.h" // Defines configTime, getLocalTime, asctime, time_t
#include "sntp.h" // For sntp_set_sync_interval (may vary based on ESP32 core version, sometimes implicitly available)
LiquidCrystal_I2C lcd(0x27, 20, 4);
uint8_t dot[] = {0x0E, 0x0A, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00};
// --- NTP Settings ---
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 8 * 3600; // Your GMT offset (e.g., +8 hours for Singapore)
const int daylightOffset_sec = 0; // Daylight saving offset in seconds (0 if not applicable)
const unsigned long ntpSyncIntervalMs = 5 * 60 * 1000; // 5 minutes in milliseconds
// Function to get current date and time as a formatted string
String getCurrentDateTimeString() {
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
char timeString[20]; // Buffer for "%d/%m/%Y %H:%M:%S" (19 chars) + null terminator
strftime(timeString, sizeof(timeString), "%d/%m/%Y %H:%M:%S", &timeinfo);
return String(timeString);
} else {
return "Time not synced"; // Or any other placeholder
}
}
void syncTimeWithNTP() {
lcd.setCursor(0, 0);
lcd.print("Configuring time... ");
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
// Set the SNTP synchronization interval (e.g., every 5 minutes)
// This function tells the underlying SNTP service how often to resync.
sntp_set_sync_interval(ntpSyncIntervalMs);
struct tm timeinfo;
lcd.setCursor(0, 1);
lcd.print("Syncing time......."); // 20 chars wide
int attempts = 0;
// getLocalTime will wait for sync for its default timeout (or specified one)
// after configTime has started the process.
while (!getLocalTime(&timeinfo, 1000)) { // Check every 1 second, with a small timeout for getLocalTime itself
Serial.println("Waiting for NTP time sync...");
lcd.print(".");
attempts++;
if (attempts > 30) { // Give up after about 30 seconds of trying to get initial sync
lcd.setCursor(0,1);
lcd.print("Time sync FAILED! ");
Serial.println("Failed to obtain time after multiple attempts.");
return; // Exit if sync fails
}
delay(500); // Wait a bit before retrying getLocalTime check
}
Serial.println("Time synchronized successfully.");
lcd.setCursor(0, 0); // Clear "Configuring time..." and "Syncing time..." area
lcd.print("WiFi & Time OK ");
lcd.setCursor(0, 1);
lcd.print(" "); // Clear sync status line
}
void connectToWiFi() {
bool flag = true;
WiFi.begin("Wokwi-GUEST", "", 6); // Replace with your credentials if not using Wokwi
lcd.clear();
while (WiFi.status() != WL_CONNECTED) {
delay(500);
lcd.setCursor(0, 0);
lcd.print(flag ? "Connecting to WiFi++" : "Connecting to WiFi--");
flag = !flag;
Serial.print(".");
}
Serial.println("\nWiFi connected!");
lcd.setCursor(0, 0);
lcd.print("WiFi connected! ");
lcd.setCursor(0, 1);
lcd.print(WiFi.localIP());
delay(2000); // Show IP for a couple of seconds
syncTimeWithNTP(); // Configure and synchronize time
}
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
lcd.createChar(0, dot); // Use slot 0 for degree symbol
connectToWiFi();
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Disconnected!");
lcd.setCursor(0, 1);
lcd.print("Attempting reconnect");
Serial.println("WiFi Disconnected! Attempting reconnect...");
delay(2000);
connectToWiFi(); // This will re-attempt WiFi and time sync
}
// Get the formatted date and time string
String dateTimeStr = getCurrentDateTimeString();
lcd.setCursor(0, 1); // Display time on the second row
lcd.print(dateTimeStr);
// Example sensor data
float h = 80.5; // Relative humidity
float c = 27.0; // Celsius
// Display humidity
char buffer[21]; // Max 20 chars for LCD line + null
snprintf(buffer, sizeof(buffer), "Humidity: %4.1f%%", h); // Format to ensure clearing
lcd.setCursor(0, 2);
lcd.print(buffer);
// Ensure any remaining characters from previous longer strings are cleared
for (int i = strlen(buffer); i < 20; ++i) {
lcd.print(" ");
}
// Display temperature
snprintf(buffer, sizeof(buffer), "Temperature: %4.1f C", c);
lcd.setCursor(0, 3);
lcd.print(buffer);
lcd.setCursor(18,3); // Position for degree symbol if not in snprintf
lcd.write(0); // Custom degree symbol. Make sure it's "°C" if using write(0)
// Or include degree symbol directly in snprintf if your LCD supports it well
// For example: snprintf(buffer, sizeof(buffer), "Temperature: %4.1f%cC", c, (char)223); // 223 is often degree symbol
// Using the custom char is safer:
// snprintf(buffer, sizeof(buffer), "Temperature: %4.1f", c);
// lcd.print(buffer); lcd.write(0); lcd.print("C");
// Let's stick to your original way for temperature to keep it simple with the custom char
lcd.setCursor(0, 3);
lcd.print("Temperature: "); lcd.print(c, 1); lcd.write(0); lcd.print("C");
// Clear remaining part of the line
for (int i = (13 + String(c,1).length() + 2); i < 20; ++i) { // "Temperature: " is 13 chars
lcd.print(" ");
}
// Your MQTT publishing logic would go here
// Example:
// if (client.connected()) {
// client.publish("esp32/datetime", dateTimeStr.c_str());
// client.publish("esp32/humidity", String(h).c_str());
// client.publish("esp32/temperature", String(c).c_str());
// }
delay(1000); // Update LCD (and potentially MQTT) every second
}