/*
ESP32-C3 Mini — WiFi NTP Clock on 32x8 WS2812B NeoPixel Matrix
India Standard Time (UTC+5:30)
Arvind Patil / AI Centre Nandurbar
Wiring:
NeoPixel DIN -> GPIO4 (330ohm resistor inline)
NeoPixel 5V -> 5V / VBUS
NeoPixel GND -> GND (common ground with ESP32-C3)
1000uF capacitor across matrix 5V/GND recommended.
DHT22 DATA -> GPIO5 (10k pull-up to 3.3V), VCC -> 3.3V, GND -> GND
Buzzer (+) -> GPIO6, Buzzer (-) -> GND
Libraries required (Library Manager):
- Adafruit NeoMatrix
- Adafruit NeoPixel
- Adafruit GFX Library
- DHT sensor library (Adafruit)
- Adafruit Unified Sensor
*/
#include <WiFi.h>
#include <time.h>
#include <Adafruit_NeoPixel.h>
#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <DHT.h>
// ---------- USER CONFIG ----------
const char* WIFI_SSID = "Wokwi-GUEST"; // Wokwi's built-in simulated WiFi network
const char* WIFI_PASSWORD = ""; // Wokwi-GUEST has no password
#define MATRIX_PIN 4
#define MATRIX_W 32
#define MATRIX_H 8
#define BRIGHTNESS 90 // raised for visibility; use a proper 5V/3A+ supply at this level,
// not the USB 500mA budget — drop back to ~40 if running off USB only
#define SCROLL_STEP_MS 140 // ms per column of scroll movement (slow, readable pace)
#define DHT_PIN 5
#define DHT_TYPE DHT22
#define BUZZER_PIN 6
// Default alarm (can later be made configurable over WiFi/web UI)
int alarmHour = 6;
int alarmMinute = 30;
bool alarmEnabled = true;
// IST = UTC + 5:30, no daylight saving
const long GMT_OFFSET_SEC = 5 * 3600 + 1800;
const int DAYLIGHT_OFFSET_SEC = 0;
const char* NTP_SERVER = "pool.ntp.org";
// ----------------------------------
DHT dht(DHT_PIN, DHT_TYPE);
float lastTemp = NAN, lastHum = NAN;
unsigned long lastSensorRead = 0;
const unsigned long SENSOR_INTERVAL_MS = 3000;
bool alarmRinging = false;
int lastAlarmMinuteFired = -1;
unsigned long lastBeepToggle = 0;
bool buzzerOn = false;
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(
MATRIX_W, MATRIX_H, MATRIX_PIN,
NEO_MATRIX_TOP + NEO_MATRIX_LEFT + NEO_MATRIX_ROWS + NEO_MATRIX_ZIGZAG,
NEO_GRB + NEO_KHZ800
);
uint16_t colorGold, colorGreen, colorCyan, colorRed;
uint16_t currentColor;
int colorCycle = 0;
int scrollX = MATRIX_W;
int secScrollX = MATRIX_W;
unsigned long lastScrollMove = 0;
unsigned long lastColonBlink = 0;
bool colonOn = true;
enum Mode { MODE_TIME, MODE_SECONDS, MODE_SCROLL_DATE, MODE_TEMP_HUM };
Mode mode = MODE_TIME;
unsigned long lastModeSwitch = 0;
const unsigned long MODE_DURATION_MS = 15000; // rotate every 15s
int tempScrollX = MATRIX_W;
void readSensor() {
if (millis() - lastSensorRead < SENSOR_INTERVAL_MS) return;
lastSensorRead = millis();
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
lastHum = h;
lastTemp = t;
}
}
void drawTempHumidity() {
matrix.fillScreen(0);
matrix.setTextColor(currentColor);
char buf[24];
if (isnan(lastTemp) || isnan(lastHum)) {
snprintf(buf, sizeof(buf), "SENSOR ERR ");
} else {
snprintf(buf, sizeof(buf), "TEMP %.1fC HUM %.0f%% ", lastTemp, lastHum);
}
matrix.setCursor(tempScrollX, 0);
matrix.print(buf);
matrix.show();
int w = strlen(buf) * 6;
if (--tempScrollX < -w) tempScrollX = MATRIX_W;
}
void checkAlarm(struct tm &t) {
if (alarmEnabled && !alarmRinging &&
t.tm_hour == alarmHour && t.tm_min == alarmMinute) {
if (lastAlarmMinuteFired != t.tm_min) {
lastAlarmMinuteFired = t.tm_min;
alarmRinging = true;
}
}
if (t.tm_min != alarmMinute) lastAlarmMinuteFired = -1;
}
void drawAlarmRinging(struct tm &t) {
unsigned long now = millis();
// toggle buzzer tone on/off every 500ms
if (now - lastBeepToggle > 500) {
lastBeepToggle = now;
buzzerOn = !buzzerOn;
if (buzzerOn) tone(BUZZER_PIN, 1500);
else noTone(BUZZER_PIN);
}
// flash "ALARM HH:MM" on the matrix
bool flashOn = (now / 300) % 2 == 0;
matrix.fillScreen(0);
if (flashOn) {
matrix.setTextColor(colorRed);
matrix.setCursor(tempScrollX, 0);
char buf[16];
snprintf(buf, sizeof(buf), "ALARM %02d:%02d ", t.tm_hour, t.tm_min);
matrix.print(buf);
if (--tempScrollX < -(int)(strlen(buf) * 6)) tempScrollX = MATRIX_W;
}
matrix.show();
}
// Call from loop(), e.g. on a button press or web request, to silence the alarm
void stopAlarm() {
alarmRinging = false;
noTone(BUZZER_PIN);
digitalWrite(BUZZER_PIN, LOW);
}
void connectWiFi() {
matrix.fillScreen(0);
matrix.setCursor(0, 0);
matrix.setTextColor(colorGold);
matrix.print("WIFI");
matrix.show();
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED && millis() - start < 20000) {
delay(300);
}
if (WiFi.status() == WL_CONNECTED) {
Serial.print("Connected. IP: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("WiFi connection failed, will retry in loop().");
}
}
void syncTime() {
configTime(GMT_OFFSET_SEC, DAYLIGHT_OFFSET_SEC, NTP_SERVER);
struct tm timeinfo;
int attempts = 0;
while (!getLocalTime(&timeinfo) && attempts < 10) {
delay(500);
attempts++;
}
}
void setup() {
Serial.begin(115200);
matrix.begin();
matrix.setTextWrap(false);
matrix.setBrightness(BRIGHTNESS);
matrix.setTextSize(1);
colorGold = matrix.Color(224, 184, 74);
colorGreen = matrix.Color(56, 255, 106);
colorCyan = matrix.Color(77, 224, 255);
colorRed = matrix.Color(255, 59, 59);
currentColor = colorGold;
connectWiFi();
if (WiFi.status() == WL_CONNECTED) {
syncTime();
}
dht.begin();
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
}
void drawClockFace(struct tm &t) {
matrix.fillScreen(0);
matrix.setTextColor(currentColor);
char buf[8];
snprintf(buf, sizeof(buf), "%02d%c%02d", t.tm_hour, colonOn ? ':' : ' ', t.tm_min);
matrix.setCursor(1, 0);
matrix.print(buf);
matrix.show();
}
void drawClockWithSeconds(struct tm &t) {
// HH:MM:SS is wider than the 32px matrix, so scroll it slowly instead of truncating
char buf[16];
snprintf(buf, sizeof(buf), "%02d:%02d:%02d ", t.tm_hour, t.tm_min, t.tm_sec);
matrix.fillScreen(0);
matrix.setTextColor(currentColor);
matrix.setCursor(secScrollX, 0);
matrix.print(buf);
matrix.show();
int textWidthPx = strlen(buf) * 6;
if (--secScrollX < -textWidthPx) secScrollX = MATRIX_W;
}
void drawScrollDate(struct tm &t) {
static const char* days[] = {"SUN","MON","TUE","WED","THU","FRI","SAT"};
static const char* mons[] = {"JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"};
char buf[40];
snprintf(buf, sizeof(buf), "%s %d %s IST ",
days[t.tm_wday], t.tm_mday, mons[t.tm_mon]);
matrix.fillScreen(0);
matrix.setTextColor(currentColor);
matrix.setCursor(scrollX, 0);
matrix.print(buf);
matrix.show();
int textWidthPx = strlen(buf) * 6;
if (--scrollX < -textWidthPx) scrollX = MATRIX_W;
}
void loop() {
// reconnect handling
if (WiFi.status() != WL_CONNECTED) {
connectWiFi();
if (WiFi.status() == WL_CONNECTED) syncTime();
}
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
delay(500);
return;
}
unsigned long now = millis();
readSensor();
checkAlarm(timeinfo);
// alarm takes over the display until stopped (e.g. by a physical button on an interrupt pin)
if (alarmRinging) {
drawAlarmRinging(timeinfo);
delay(50);
return;
}
// rotate display mode
if (now - lastModeSwitch > MODE_DURATION_MS) {
lastModeSwitch = now;
mode = (Mode)((mode + 1) % 4);
scrollX = MATRIX_W;
tempScrollX = MATRIX_W;
secScrollX = MATRIX_W;
}
// blink colon every 500ms in MODE_TIME
if (now - lastColonBlink > 500) {
lastColonBlink = now;
colonOn = !colonOn;
}
switch (mode) {
case MODE_TIME:
drawClockFace(timeinfo);
delay(60);
break;
case MODE_SECONDS:
if (now - lastScrollMove > SCROLL_STEP_MS) {
lastScrollMove = now;
drawClockWithSeconds(timeinfo);
}
break;
case MODE_SCROLL_DATE:
if (now - lastScrollMove > SCROLL_STEP_MS) {
lastScrollMove = now;
drawScrollDate(timeinfo);
}
break;
case MODE_TEMP_HUM:
if (now - lastScrollMove > SCROLL_STEP_MS) {
lastScrollMove = now;
drawTempHumidity();
}
break;
}
}
/*
NOTE: to physically silence the alarm, wire a push button to an interrupt-capable
GPIO (e.g. GPIO7) with INPUT_PULLUP, and call stopAlarm() from its ISR/flag check
at the top of loop(). Omitted here to keep the sketch focused on the core features.
*/