/*
* ESP32 wiring (consigliato):
* DIN -> GPIO23
* CS -> GPIO5
* CLK -> GPIO18
*
* Evita GPIO34..39 (input-only) e i pin di boot (0,2,12,15).
*/
#include "Arduino.h"
#if defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#include <WebServer.h>
#define WIFI_HOST_SET(host) WiFi.setHostname(host)
#else
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#define WIFI_HOST_SET(host) WiFi.hostname(host)
#endif
#include <DNSServer.h>
#include <WiFiManager.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
#include <time.h>
// ------- PROTOTIPI -------
void saveConfigCallback();
// ------- DISPLAY / MAX7219 -------
#define MAX_DIGITS 16
byte dig[MAX_DIGITS] = {0};
byte digold[MAX_DIGITS] = {0};
byte digtrans[MAX_DIGITS] = {0};
bool is12HFormat = false;
bool isPM = false;
int updCnt = 0;
int dots = 0;
long dotTime = 0;
long clkTime = 0;
int dx = 0;
int dy = 0;
byte del = 0;
int h, m, s;
int day, month, year, dayOfWeek;
int adjustedHour;
String clockHostname = "NTP-Clock";
WiFiManager wifiManager;
// Numero di moduli MAX7219 a 8x8
#define NUM_MAX 4
// --- PIN: scegli pin sicuri per ESP32 ---
#if defined(ARDUINO_ARCH_ESP32)
#define DIN_PIN 23
#define CS_PIN 5
#define CLK_PIN 18
#else
// mappatura originale ESP8266: D7=13, D6=12, D5=14
#define DIN_PIN 13
#define CS_PIN 12
#define CLK_PIN 14
#endif
#include "max7219.h"
#include "fonts.h"
long lastDataDisplayTime = 0;
bool showingTime = true;
// ============================= FONT/SCROLL =============================
int showChar(char ch, const uint8_t* data) {
int len = pgm_read_byte(data);
int i, w = pgm_read_byte(data + 1 + ch * len);
for (i = 0; i < w; i++)
scr[NUM_MAX * 8 + i] = pgm_read_byte(data + 1 + ch * len + 1 + i);
scr[NUM_MAX * 8 + i] = 0;
return w;
}
void printCharWithShift(unsigned char c, int shiftDelay) {
if (c < ' ' || c > '~' + 25) return;
c -= 32;
int w = showChar(c, font);
for (int i = 0; i < w + 1; i++) {
delay(shiftDelay);
scrollLeft();
refreshAll();
}
}
void printStringWithShift(const char* s, int shiftDelay) {
while (*s) {
printCharWithShift(*s, shiftDelay);
s++;
}
}
// ============================= ORA LOCALE (SNTP) =============================
// Timezone per Italia con cambio automatico:
// CET-1CEST,M3.5.0/2,M10.5.0/3
// - CET base UTC+1
// - CEST da ultima domenica di marzo 02:00
// - a ultima domenica di ottobre 03:00
static const char* TZ_ROME = "CET-1CEST,M3.5.0/2,M10.5.0/3";
bool syncTimeOnce() {
// avvia SNTP con TZ
#if defined(ARDUINO_ARCH_ESP32)
configTzTime(TZ_ROME, "pool.ntp.org", "time.nist.gov");
#else
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
setenv("TZ", TZ_ROME, 1);
tzset();
#endif
// aspetta che arrivi l’ora
for (int i = 0; i < 50; i++) {
time_t now = time(nullptr);
if (now > 1700000000) { // ~2023-11-14: tempo valido
return true;
}
delay(200);
}
return false;
}
void readNow() {
time_t now = time(nullptr);
struct tm tmNow;
#if defined(ARDUINO_ARCH_ESP32)
localtime_r(&now, &tmNow);
#else
tmNow = *localtime(&now);
#endif
h = tmNow.tm_hour;
m = tmNow.tm_min;
s = tmNow.tm_sec;
year = tmNow.tm_year + 1900;
month = tmNow.tm_mon + 1;
day = tmNow.tm_mday;
dayOfWeek = tmNow.tm_wday; // 0=dom
}
// ============================= INTENSITÀ =============================
void setIntensity() {
int intensityHour = h;
if (intensityHour >= 22 || intensityHour < 6) {
sendCmdAll(CMD_INTENSITY, 0);
} else if ((intensityHour >= 7 && intensityHour <= 10) || (intensityHour >= 16 && intensityHour < 18)) {
sendCmdAll(CMD_INTENSITY, 3);
} else if (intensityHour >= 11 && intensityHour <= 15) {
sendCmdAll(CMD_INTENSITY, 5);
} else if (intensityHour >= 19 && intensityHour <= 22) {
sendCmdAll(CMD_INTENSITY, 2);
}
}
// ============================= RENDER OROLOGIO =============================
void showDigit(char ch, int col, const uint8_t* data) {
if (dy < -8 || dy > 8) return;
int len = pgm_read_byte(data);
int w = pgm_read_byte(data + 1 + ch * len);
int offset = (8 - w) / 2;
col += dx + offset;
for (int i = 0; i < w; i++)
if (col + i >= 0 && col + i < 8 * NUM_MAX) {
byte v = pgm_read_byte(data + 1 + ch * len + 1 + i);
if (!dy) scr[col + i] = v;
else scr[col + i] |= dy > 0 ? v >> dy : v << -dy;
}
}
void setCol(int col, byte v) {
if (dy < -8 || dy > 8) return;
col += dx;
if (col >= 0 && col < 8 * NUM_MAX)
if (!dy) scr[col] = v;
else scr[col] |= dy > 0 ? v >> dy : v << -dy;
}
void showAnimClock() {
byte digPos[6] = { 0, 7, 17, 24, 34, 42 };
int digHt = 12;
int num = 6;
if (del == 0) {
del = digHt;
for (int i = 0; i < num; i++) digold[i] = dig[i];
dig[0] = h / 10;
dig[1] = h % 10;
dig[2] = m / 10;
dig[3] = m % 10;
dig[4] = s / 10;
dig[5] = s % 10;
for (int i = 0; i < num; i++) digtrans[i] = (dig[i] == digold[i]) ? 0 : digHt;
} else {
del--;
}
clr();
for (int i = 0; i < num; i++) {
if (digtrans[i] == 0) {
dy = 0;
showDigit(dig[i], digPos[i], dig6x8);
} else {
dy = digHt - digtrans[i];
showDigit(digold[i], digPos[i], dig6x8);
dy = -digtrans[i];
showDigit(dig[i], digPos[i], dig6x8);
digtrans[i]--;
}
}
dy = 0;
setCol(15, dots ? B01100110 : 0);
setCol(16, dots ? B01100110 : 0);
setCol(32, dots ? B01100110 : 0);
setCol(33, dots ? B01100110 : 0);
refreshAll();
delay(30);
}
void displayDate() {
byte digPos[4] = { 0, 7, 17, 24 };
int digHt = 12;
int num = 4;
del = digHt;
for (int i = 0; i < num; i++) digold[i] = dig[i];
dig[0] = day / 10;
dig[1] = day % 10;
dig[2] = month / 10;
dig[3] = month % 10;
for (int i = 0; i < num; i++) digtrans[i] = (dig[i] == digold[i]) ? 0 : digHt;
clr();
for (int i = 0; i < num; i++) {
if (digtrans[i] == 0) {
dy = 0;
showDigit(dig[i], digPos[i], dig6x8);
} else {
dy = digHt - digtrans[i];
showDigit(digold[i], digPos[i], dig6x8);
dy = -digtrans[i];
showDigit(dig[i], digPos[i], dig6x8);
digtrans[i]--;
}
}
dy = 0;
setCol(15, dots ? B00111100 : 0);
setCol(16, dots ? B00111100 : 0);
refreshAll();
delay(30);
}
// ============================= SETUP/LOOP =============================
void setup() {
Serial.begin(115200);
EEPROM.begin(sizeof(float));
wifiManager.setSaveParamsCallback(saveConfigCallback);
WiFi.mode(WIFI_STA);
WIFI_HOST_SET(clockHostname.c_str());
initMAX7219();
sendCmdAll(CMD_SHUTDOWN, 1);
sendCmdAll(CMD_INTENSITY, 0);
Serial.print("Connecting WiFi ");
if (!wifiManager.autoConnect("NTP Clock Setup")) {
Serial.println("Failed to connect and hit timeout");
delay(3000);
ESP.restart();
}
printStringWithShift("Connecting", 15);
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(".");
}
Serial.println();
Serial.print("MyIP: ");
Serial.println(WiFi.localIP());
String ipMsg = String(" MyIP: ") + WiFi.localIP().toString();
printStringWithShift(ipMsg.c_str(), 15);
delay(800);
// Sincronizza ora con TZ Europa/Roma
printStringWithShift(" Setting Time...", 15);
if (!syncTimeOnce()) {
printStringWithShift(" NTP FAIL", 20);
}
readNow(); // prima lettura
clkTime = millis();
lastDataDisplayTime = millis();
}
void saveConfigCallback() {
EEPROM.commit();
}
void loop() {
// aggiorna indicatore due punti
if (millis() - dotTime > 500) {
dotTime = millis();
dots = !dots;
}
// ogni 20s "tick" fittizio per la tua logica di refresh
if (millis() - clkTime > 20000) {
clkTime = millis();
}
// leggi tempo corrente (locale con DST auto)
readNow();
setIntensity();
// alterna ora/data
if (millis() - lastDataDisplayTime >= 30000) { // passa a data
showingTime = false;
lastDataDisplayTime = millis();
} else if (millis() - lastDataDisplayTime >= 4000 && !showingTime) { // torna all'ora dopo 4s
showingTime = true;
}
if (showingTime) {
showAnimClock();
} else {
displayDate();
}
}