Files
HorlogeImprimante3D/NTPClockEpaper/NTPClockEpaper.ino
2026-06-28 23:56:37 +02:00

376 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <GxEPD2_BW.h>
#include <GxEPD2_3C.h>
//#include <Fonts/FreeSerifBold18pt7b.h>
#include <DSEG7_Classic_Bold_58.h>
#include <Fonts/FreeMono9pt7b.h> // For IP and sync time
#include <TimeLib.h>
#include <WiFi.h>
#include <WiFiUdp.h>
// ESP32S3 pins
#define CS_PIN (10)
#define BUSY_PIN (3)
#define RES_PIN (46)
#define DC_PIN (9)
// Wi-Fi credentials (replace with your own)
const char* ssid = "Livebox-7E76";
const char* password = "oLkR9ngFR6hGSruH4y";
// NTP settings
const char* ntpServer = "fr.pool.ntp.org";
const int timeZone = 1; // Adjust for your timezone (e.g., CET = 1, CEST = 2)
WiFiUDP Udp;
unsigned long lastSyncTime = 0; // Last successful NTP sync (Unix timestamp)
// Buffer for NTP packets
static uint8_t ntpPacketBuffer[48];
// 1.54'' EPD Module
GxEPD2_BW<GxEPD2_154_D67, GxEPD2_154_D67::HEIGHT> display(GxEPD2_154_D67(CS_PIN, DC_PIN, RES_PIN, BUSY_PIN));
void setup() {
Serial.begin(115200);
display.init(115200, true, 50, false);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected. IP: " + WiFi.localIP().toString());
// Sync time with NTP
syncNTPTime();
lastSyncTime = now(); // Record sync time
// Initial full screen update
updateFullScreen();
}
// Global variables
static uint8_t lastMinute = 0;
void loop() {
uint8_t currentMinute = minute();
uint8_t currentSecond = second();
if (currentMinute != lastMinute && currentSecond == 0) {
lastMinute = currentMinute;
updateFullScreen();
}
delay(100); // Reduce CPU usage
}
// Sync time with NTP server
void syncNTPTime() {
Udp.begin(2390); // Local port for NTP
setSyncProvider(getNtpTime);
setSyncInterval(3600); // Sync every hour (optional)
}
uint8_t stratum = 0;
float rootDelay = 0;
// Fetch NTP time (callback for TimeLib)
time_t getNtpTime() {
// Send NTP request
sendNTPpacket(ntpServer);
// Wait for response (1.5s timeout)
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = Udp.parsePacket();
if (size >= 48) {
Udp.read(ntpPacketBuffer, 48); // Read packet into buffer
// --- Debug output ---
Serial.println("--- NTP Packet Details (Fixed Decoding) ---");
// --- Extract and decode all fields ---
// LI
// 0 = no warning
// 1 = last minute has 61 seconds
// 2 = last minute has 59 seconds
// 3 = unknown (clock unsynchronized)
uint8_t li = (ntpPacketBuffer[0] >> 6) & 0x03; // Leap Indicator (2 bits)
Serial.print("Leap Indicator: "); Serial.println(li);
//NTP version number, typically 4.
uint8_t vn = (ntpPacketBuffer[0] >> 3) & 0x07; // Version Number (3 bits)
Serial.print("Version: "); Serial.println(vn);
//Association mode:
//0 = reserved
//1 = symmetric active
//2 = symmetric passive
//3 = client
//4 = server
//5 = broadcast
//6 = control
//7 = private
uint8_t mode = ntpPacketBuffer[0] & 0x07; // Mode (3 bits)
Serial.print("Mode: "); Serial.println(mode);
// Stratum (byte 1)
// Indicates the distance from the reference clock.
// 0 = invalid
// 1 = primary server
// 215 = secondary
// 16 = unsynchronized
stratum = ntpPacketBuffer[1];
Serial.print("Stratum: "); Serial.println(stratum);
// Poll Interval (byte 2)
// Maximum interval between successive messages, in log₂(seconds). Typical range is 6 to 10.
// Example RAW=0 means that the poll interval is 1 second
int8_t pollIntervalRaw = ntpPacketBuffer[2];
Serial.print("Poll Interval RAW: "); Serial.println(pollIntervalRaw);
// Precision (byte 3)
// Signed log₂(seconds) of system clock precision (e.g., 18 ≈ 1 microsecond).
int8_t precisionRaw = ~(ntpPacketBuffer[3]);
Serial.print("Precision RAW: "); Serial.println(precisionRaw);
// Root Delay (bytes 4-7, 16.16 fixed-point)
// Total round-trip delay to the reference clock, in NTP short format.
// The NTP Short Format is a uint32_t with the first 16 bits being the seconds
//(i.e seconds = format >> 16) and the other 16 bits being the fraction
// of a second (i.e fraction = format & 0xFFFF). After converting I'd like the output as a double.
uint32_t rootDelayRaw = (ntpPacketBuffer[4] << 24) |
(ntpPacketBuffer[5] << 16) |
(ntpPacketBuffer[6] << 8) |
ntpPacketBuffer[7];
rootDelay = (rootDelayRaw >> 16) + ((rootDelayRaw & 0xFFFF) / 65536.0);
Serial.print("Root Delay: "); Serial.println(rootDelay, 6);
// Root Dispersion (bytes 8-11, 16.16 fixed-point)
// Total dispersion to the reference clock, in NTP short format.
uint32_t rootDispersionRaw = (ntpPacketBuffer[8] << 24) |
(ntpPacketBuffer[9] << 16) |
(ntpPacketBuffer[10] << 8) |
ntpPacketBuffer[11];
float rootDispersion = (rootDispersionRaw >> 16) + ((rootDispersionRaw & 0xFFFF) / 65536.0);
Serial.print("Root Dispersion: "); Serial.println(rootDispersion, 6);
// Reference Identifier (bytes 12-16)
// Identifies the specific server or reference clock; interpretation depends on Stratum.
// For packet stratum 0 (unspecified or invalid), this
// is a four-character ASCII [RFC1345] string, called the "kiss code",
// used for debugging and monitoring purposes. For stratum 1 (reference
// clock), this is a four-octet, left-justified, zero-padded ASCII
// string assigned to the reference clock. The authoritative list of
// Reference Identifiers is maintained by IANA; however, any string
// beginning with the ASCII character "X" is reserved for unregistered
// experimentation and development. The identifiers in Figure 12 have
// been used as ASCII identifiers:
// +------+----------------------------------------------------------+
// | ID | Clock Source |
// +------+----------------------------------------------------------+
// | GOES | Geosynchronous Orbit Environment Satellite |
// | GPS | Global Position System |
// | GAL | Galileo Positioning System |
// | PPS | Generic pulse-per-second |
// | IRIG | Inter-Range Instrumentation Group |
// | WWVB | LF Radio WWVB Ft. Collins, CO 60 kHz |
// | DCF | LF Radio DCF77 Mainflingen, DE 77.5 kHz |
// | HBG | LF Radio HBG Prangins, HB 75 kHz |
// | MSF | LF Radio MSF Anthorn, UK 60 kHz |
// | JJY | LF Radio JJY Fukushima, JP 40 kHz, Saga, JP 60 kHz |
// | LORC | MF Radio LORAN C station, 100 kHz |
// | TDF | MF Radio Allouis, FR 162 kHz |
// | CHU | HF Radio CHU Ottawa, Ontario |
// | WWV | HF Radio WWV Ft. Collins, CO |
// | WWVH | HF Radio WWVH Kauai, HI |
// | NIST | NIST telephone modem |
// | ACTS | NIST telephone modem |
// | USNO | USNO telephone modem |
// | PTB | European telephone modem |
// +------+----------------------------------------------------------+
Serial.print("Reference ID: ");
if (stratum <= 1 ) {
Serial.printf("%c.%c.%c.%c\n",
ntpPacketBuffer[12],
ntpPacketBuffer[13],
ntpPacketBuffer[14],
ntpPacketBuffer[15]);
} else if (stratum >= 2 && stratum <= 15) {
// Stratum 2-15: IPv4 address of the reference server
Serial.printf("%d.%d.%d.%d\n",
ntpPacketBuffer[12],
ntpPacketBuffer[13],
ntpPacketBuffer[14],
ntpPacketBuffer[15]);
} else if (stratum == 16) {
// Stratum 16: Unsynchronized (reserved)
Serial.println("Unsynchronized");
}
// Reference Timestamp (bytes 16-23)
// Time when the system clock was last set or corrected, in NTP timestamp format.
uint32_t referenceTimestampRaw = (ntpPacketBuffer[16] << 24) |
(ntpPacketBuffer[17] << 16) |
(ntpPacketBuffer[18] << 8) |
ntpPacketBuffer[19]; //by reading the first four bytes we ignore the decimal seconds part of the timestamp
time_t referenceTimestamp = referenceTimestampRaw - 2208988800UL + (timeZone * 3600);
Serial.print("Reference Timestamp: "); Serial.println(referenceTimestamp);
// Origin Timestamp (bytes 24-31)
// Time at the client when the request departed, in NTP timestamp format.
uint32_t originTimestampRaw = (ntpPacketBuffer[24] << 24) |
(ntpPacketBuffer[25] << 16) |
(ntpPacketBuffer[26] << 8) |
ntpPacketBuffer[27]; //by reading the first four bytes we ignore the decimal seconds part of the timestamp
time_t originTimestamp = originTimestampRaw - 2208988800UL + (timeZone * 3600);
Serial.print("Origin Timestamp: "); Serial.println(originTimestamp);
// Receive Timestamp (bytes 32-39)
// The local time, in timestamp format, when the latest NTP message arrived.
uint32_t receiveTimestampRaw = (ntpPacketBuffer[32] << 24) |
(ntpPacketBuffer[33] << 16) |
(ntpPacketBuffer[34] << 8) |
ntpPacketBuffer[35]; //by reading the first four bytes we ignore the decimal seconds part of the timestamp
time_t receiveTimestamp = receiveTimestampRaw - 2208988800UL + (timeZone * 3600);
Serial.print("Receive Timestamp: "); Serial.println(receiveTimestamp);
// Transmit timestamp (bytes 40-47)
//Time at the server when the response left, in NTP timestamp format.
uint32_t transmitTimestampRaw = (ntpPacketBuffer[40] << 24) |
(ntpPacketBuffer[41] << 16) |
(ntpPacketBuffer[42] << 8) |
ntpPacketBuffer[43]; //by reading the first four bytes we ignore the decimal seconds part of the timestamp
// Convert to Unix time (NTP time is seconds since 1900)
time_t transmitTimestamp = transmitTimestampRaw - 2208988800UL + (timeZone * 3600);
lastSyncTime = transmitTimestamp;
setTime(transmitTimestamp);
Serial.print("Transmit Timestamp: "); Serial.println(transmitTimestamp);
return transmitTimestamp;
}
}
return 0; // Failed to sync
}
// Send NTP request
void sendNTPpacket(const char* address) {
memset(ntpPacketBuffer, 0, 48);
// NTP request packet (see RFC 5905)
ntpPacketBuffer[0] = 0x1B; // LI=0, VN=3, Mode=3 (client)
Udp.beginPacket(address, 123); // NTP port
Udp.write(ntpPacketBuffer, 48);
Udp.endPacket();
}
// Update the full screen (IP, clock, last sync)
void updateFullScreen() {
display.setRotation(1);
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
// --- Top: IP Address ---
display.setFont(&FreeMono9pt7b);
display.setTextSize(1);
display.setTextColor(GxEPD_BLACK);
char ipStr[16];
sprintf(ipStr, "%d.%d.%d.%d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]);
int16_t ipX = 5;
display.setCursor(ipX, 15);
display.print(ipStr);
// Internal temperature
float internalTemp = temperatureRead();
sprintf(ipStr, "Internal: %.1f C", internalTemp);
display.setCursor(ipX, 30);
display.print(ipStr);
// --- Center: Clock (HH:MM) ---
//display.setFont(&FreeSerifBold18pt7b);
display.setFont(&DSEG7_Classic_Bold_58);
display.setTextSize(1);
char timeStr[6];
sprintf(timeStr, "%02d:%02d", hour(), minute());
int16_t tbx, tby;
uint16_t tbw, tbh;
display.getTextBounds(timeStr, 0, 0, &tbx, &tby, &tbw, &tbh);
uint16_t x = ((display.width() - tbw) / 2) - tbx;
uint16_t y = ((display.height() - tbh) / 2) - tby;
display.setCursor(x, y);
display.print(timeStr);
// --- Bottom: Last NTP Sync ---
display.setFont(&FreeMono9pt7b);
display.setTextSize(1);
char syncStr[20];
sprintf(syncStr, "Last sync: %02d:%02d", hour(lastSyncTime), minute(lastSyncTime));
//int16_t syncX = (display.width() - strlen(syncStr) * 6) / 2;
int16_t syncX = 5;
display.setCursor(syncX, display.height() - 5);
display.print(syncStr);
sprintf(syncStr, "Stratum: %02d", stratum);
//int16_t syncX = (display.width() - strlen(syncStr) * 6) / 2;
display.setCursor(syncX, display.height() - 20);
display.print(syncStr);
sprintf(syncStr, "Root Del: %.5f", rootDelay);
//int16_t syncX = (display.width() - strlen(syncStr) * 6) / 2;
display.setCursor(syncX, display.height() - 35);
display.print(syncStr);
if (stratum <= 1 ) {
// Stratum 1: 4 char code of the reference clock
sprintf(syncStr, "R: %c%c%c%c",
ntpPacketBuffer[12],
ntpPacketBuffer[13],
ntpPacketBuffer[14],
ntpPacketBuffer[15]);
} else if (stratum >= 2 && stratum <= 15) {
// Stratum 2-15: IPv4 address of the reference server
int count = (ntpPacketBuffer[12] >= 100) +
(ntpPacketBuffer[13] >= 100) +
(ntpPacketBuffer[14] >= 100) +
(ntpPacketBuffer[15] >= 100);
if(count == 4) { //At last 4 values are above 100 and below 255
sprintf(syncStr, "R:%d.%d.%d.%d",
ntpPacketBuffer[12],
ntpPacketBuffer[13],
ntpPacketBuffer[14],
ntpPacketBuffer[15]);
} else if (count == 3)
{
sprintf(syncStr, "Re:%d.%d.%d.%d",
ntpPacketBuffer[12],
ntpPacketBuffer[13],
ntpPacketBuffer[14],
ntpPacketBuffer[15]);
}else if (count == 2)
{
sprintf(syncStr, "Ref:%d.%d.%d.%d",
ntpPacketBuffer[12],
ntpPacketBuffer[13],
ntpPacketBuffer[14],
ntpPacketBuffer[15]);
}
else if (count <= 1)
{
sprintf(syncStr, "Ref :%d.%d.%d.%d",
ntpPacketBuffer[12],
ntpPacketBuffer[13],
ntpPacketBuffer[14],
ntpPacketBuffer[15]);
}
} else if (stratum == 16) {
// Stratum 16: Unsynchronized (reserved)
sprintf(syncStr, "Unsynchronized");
}
//int16_t syncX = (display.width() - strlen(syncStr) * 6) / 2;
display.setCursor(syncX, display.height() - 50);
display.print(syncStr);
} while (display.nextPage());
}