/*
* LED matrix Clock with DHT22 Temperature and Humidity
* Modified from original by Uncle Da
* Added date, AM/PM, temperature and humidity display
* Last Update: April 10, 2025
*/
#include <WiFi.h> //ESP32
//#include <ESP8266WiFi.h> //ESP8266
#include <MD_Parola.h> //For MAX7219 Matrix LED
#include <MD_MAX72xx.h> //For MAX7219 SPI LED Driver
#include <SPI.h> //For SPI Communication
#include <time.h> //For Time Based Application
#include "Font.h"
#include <DHT.h> //For DHT22 Sensor
//********** User Config Setting ******************************/
const char *ssid = "Wokwi-GUEST"; //Change to your WiFi SSID
const char *password = ""; //Password
const char *NTPServer = "pool.ntp.org";
const int gmtOffset_sec = 28800; //8*3600 (GMT+8)
const int daylightOffset_sec = 0; //Daylight savings
//=====MAX7219================================
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW //Change to FC16_HW if needed
#define MAX_DEVICES 4
#define CLK_PIN 18
#define DATA_PIN 23
#define CS_PIN 5
MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
#define SPEED_TIME 60 //Lower number = faster
#define PAUSE_TIME 1000 //Display pause time in milliseconds
//=====DHT22 Sensor Setup===================
#define DHTPIN 15 // Digital pin connected to the DHT sensor
#define DHTTYPE DHT22 // DHT 22 (AM2302)
DHT dht(DHTPIN, DHTTYPE);
float temperature = 0.0;
float humidity = 0.0;
unsigned long lastDHTReadTime = 0;
#define DHT_READ_INTERVAL 2000 // Read DHT22 every 2 seconds
//=====Display Variables=====================
uint16_t h, m, s;
char szTime[9]; // HH:MM\0
char szDate[11]; // MM/DD/YY\0
char szAMPM[3]; // AM/PM
char szTemp[7]; // XX.X°C
char szHumid[7]; // XX.X%
char szSecond[4]; // SS
bool isPM = false;
// Display mode enum to track what we're showing
enum DisplayMode {
SHOW_TIME,
SHOW_DATE,
SHOW_TEMP,
SHOW_HUMID,
DISPLAY_MODES_COUNT // Number of display modes
};
DisplayMode currentMode = SHOW_TIME;
unsigned long lastModeChangeTime = 0;
#define MODE_CHANGE_INTERVAL 5000 // Change display mode every 5 seconds
//=============================================
void getSecond(char *szSecond)
{
sprintf(szSecond, "%02d", s);
}
//=============================================
void updateTimeAndDate()
{
time_t now = time(nullptr);
struct tm* p_tm = localtime(&now);
h = p_tm->tm_hour;
m = p_tm->tm_min;
s = p_tm->tm_sec;
// Format time
if (h >= 12) {
isPM = true;
strcpy(szAMPM, "PM");
if (h > 12) h -= 12; // Convert to 12-hour format
} else {
isPM = false;
strcpy(szAMPM, "AM");
if (h == 0) h = 12; // 0 hour = 12 AM
}
sprintf(szTime, "%2d:%02d", h, m); // HH:MM in 12-hour format
// Format date (MM/DD/YY)
sprintf(szDate, "%02d/%02d/%02d",
p_tm->tm_mon + 1, // Month (0-11, so add 1)
p_tm->tm_mday, // Day of month
p_tm->tm_year % 100); // Year (since 1900, so get last 2 digits)
Serial.printf("%04d-%02d-%02d %02d:%02d:%02d %s\n",
p_tm->tm_year + 1900, p_tm->tm_mon + 1, p_tm->tm_mday,
h, m, s, szAMPM);
// Check if we need to change the display mode
if (millis() - lastModeChangeTime >= MODE_CHANGE_INTERVAL) {
currentMode = static_cast<DisplayMode>((currentMode + 1) % DISPLAY_MODES_COUNT);
lastModeChangeTime = millis();
P.displayReset(1); // Reset zone 1 display
}
}
//=============================================
void readDHT()
{
// Read DHT sensor only every DHT_READ_INTERVAL milliseconds
if (millis() - lastDHTReadTime >= DHT_READ_INTERVAL) {
humidity = dht.readHumidity();
temperature = dht.readTemperature();
// Check if readings are valid
if (isnan(humidity) || isnan(temperature)) {
Serial.println("Failed to read from DHT sensor!");
strcpy(szTemp, "Error");
strcpy(szHumid, "Error");
} else {
sprintf(szTemp, "%2.1fC", temperature);
sprintf(szHumid, "%2.1f%%", humidity);
Serial.printf("Temperature: %2.1f°C, Humidity: %2.1f%%\n", temperature, humidity);
}
lastDHTReadTime = millis();
}
}
// ============SmartConfig/Auto Reconnect/WeChat Scan Config====================
void SmartConfig()
{
WiFi.mode(WIFI_STA);
Serial.println("\r\nWait for Smartconfig...");
WiFi.beginSmartConfig();
while (1)
{
Serial.print(".");
delay(500); // wait for a second
if (WiFi.smartConfigDone())
{
Serial.println("SmartConfig Success");
Serial.printf("SSID:%s\r\n", WiFi.SSID().c_str());
Serial.printf("PSW:%s\r\n", WiFi.psk().c_str());
break;
}
}
}
bool AutoConfig() //Auto reconnect on power-up
{
WiFi.begin();
for (int i = 0; i < 20; i++)
{
int wstatus = WiFi.status();
if (wstatus == WL_CONNECTED)
{
Serial.println("WIFI SmartConfig Success");
Serial.printf("SSID:%s", WiFi.SSID().c_str());
Serial.printf(", PSW:%s\r\n", WiFi.psk().c_str());
Serial.print("LocalIP:");
Serial.print(WiFi.localIP());
Serial.print(" ,GateIP:");
Serial.println(WiFi.gatewayIP());
return true;
}
else
{
Serial.print("WIFI AutoConfig Waiting......");
Serial.println(wstatus);
delay(1000);
}
}
Serial.println("WIFI AutoConfig Failed!" );
return false;
}
//===================================================
void setup()
{
Serial.begin(115200);
delay(50);
// Initialize DHT sensor
dht.begin();
// Initialize LED Matrix
P.begin(2); // Split into 2 zones
P.setInvert(false); // false = normal display, true = inverted
// Initialize WiFi
Serial.println();
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to ");
Serial.println(ssid);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
delay(1000);
// Get time from NTP
getTimeNTP();
if (!AutoConfig()) {
SmartConfig();
}
// Set up display zones
P.setZone(0, 0, 0); // Zone 0: rightmost 8x8 matrix for seconds
P.setZone(1, 1, 3); // Zone 1: matrices 1-3 for everything else
P.setFont(0, GF3x5p); // Font for seconds (smaller)
P.setFont(1, GF4x7p); // Font for main display
P.displayZoneText(0, szSecond, PA_RIGHT, SPEED_TIME, 0, PA_PRINT, PA_NO_EFFECT);
P.displayZoneText(1, szTime, PA_CENTER, SPEED_TIME, 0, PA_PRINT, PA_NO_EFFECT);
// Initial readings
readDHT();
updateTimeAndDate();
}
//=============================================
void loop() {
P.displayAnimate();
updateTimeAndDate();
readDHT();
getSecond(szSecond);
// Reset seconds display
P.displayReset(0);
// Update main display based on current mode
char displayBuffer[20] = {0};
switch (currentMode) {
case SHOW_TIME:
sprintf(displayBuffer, "%s %s", szTime, szAMPM);
break;
case SHOW_DATE:
strcpy(displayBuffer, szDate);
break;
case SHOW_TEMP:
strcpy(displayBuffer, szTemp);
break;
case SHOW_HUMID:
strcpy(displayBuffer, szHumid);
break;
default:
strcpy(displayBuffer, szTime);
break;
}
P.displayZoneText(1, displayBuffer, PA_CENTER, SPEED_TIME, 0, PA_PRINT, PA_NO_EFFECT);
P.displayReset(1);
// Adjust brightness based on time of day
int hour24 = isPM ? (h + 12) : h; // Convert back to 24-hour format
if (hour24 >= 0 && hour24 < 7) { // Night mode (midnight to 7am)
P.setIntensity(0, 1); // Zone 0 brightness = 1
P.setIntensity(1, 1); // Zone 1 brightness = 1
} else {
P.setIntensity(0, 3); // Zone 0 brightness = 3
P.setIntensity(1, 3); // Zone 1 brightness = 3
}
// Auto restart at 00:20:00
if (hour24 == 0 && m == 20 && s == 0) {
P.displayClear();
delay(1000);
ESP.restart();
}
}
//=============================================
void getTimeNTP() // NTP time server
{
configTime(gmtOffset_sec, daylightOffset_sec, NTPServer);
while(!time(nullptr)) {
delay(500);
Serial.print(".");
}
Serial.println("NTP Time Update Complete");
}