376 lines
15 KiB
C++
376 lines
15 KiB
C++
#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
|
||
// 2–15 = 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());
|
||
} |