From c40e03f9ef936242f4bb595e1e073cac6f12542a Mon Sep 17 00:00:00 2001 From: aharshac Date: Sun, 10 May 2026 15:22:09 +0530 Subject: [PATCH 1/4] feat: add comprehensive multi-platform test suite Supports ESP8266, ESP32, and Arduino Uno+ESP-01 platforms --- .../TestNTPClient.ino} | 49 ++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) rename examples/{TestNodeMCU/TestNodeMCU.ino => TestNTPClient/TestNTPClient.ino} (89%) diff --git a/examples/TestNodeMCU/TestNodeMCU.ino b/examples/TestNTPClient/TestNTPClient.ino similarity index 89% rename from examples/TestNodeMCU/TestNodeMCU.ino rename to examples/TestNTPClient/TestNTPClient.ino index de4125b..b5b0ab8 100644 --- a/examples/TestNodeMCU/TestNodeMCU.ino +++ b/examples/TestNTPClient/TestNTPClient.ino @@ -1,13 +1,29 @@ /* - EasyNTPClient test suite — NodeMCU (ESP8266) + EasyNTPClient test suite — ESP8266, ESP32, Uno+ESP-01 Open Serial Monitor at 115200 baud after flashing. Fill in WIFI_SSID and WIFI_PASSWORD before flashing. */ -#include -#include +#if defined(ESP8266) + #include + #include +#elif defined(ESP32) + #include + #include +#else + #include + #include + #include +#endif #include +#if defined(ESP8266) || defined(ESP32) + using NTPUdp = WiFiUDP; +#else + static SoftwareSerial esp_serial(3, 2); // D3→ESP-01 TX, D2→ESP-01 RX + using NTPUdp = WiFiEspUDP; +#endif + const char* WIFI_SSID = ""; const char* WIFI_PASSWORD = ""; @@ -56,7 +72,7 @@ void test_constants() { void test_basic_sync() { Serial.println("\n-- basic sync --"); - WiFiUDP udp; + NTPUdp udp; EasyNTPClient client(udp, "pool.ntp.org"); unsigned long t = client.getUnixTime(); @@ -69,9 +85,9 @@ void test_basic_sync() { void test_client_reuse() { Serial.println("\n-- socket reuse across client instances --"); - // Both clients share the same WiFiUDP object. Without Fix B+C the second + // Both clients share the same UDP object. Without Fix B+C the second // begin() is skipped (static flag) and the second sync fails. - WiFiUDP udp; + NTPUdp udp; unsigned long t1 = 0, t2 = 0; { @@ -93,7 +109,7 @@ void test_client_reuse() { void test_offset_immediate() { Serial.println("\n-- offset change takes effect without resync --"); - WiFiUDP udp; + NTPUdp udp; EasyNTPClient client(udp, "pool.ntp.org"); // Sync once with offset = 0; capture base time. @@ -118,7 +134,7 @@ void test_offset_immediate() { void test_was_updated() { Serial.println("\n-- wasUpdated() flag --"); - WiFiUDP udp; + NTPUdp udp; EasyNTPClient client(udp, "pool.ntp.org"); check(!client.wasUpdated(), "wasUpdated() is false before first sync"); @@ -131,7 +147,7 @@ void test_was_updated() { void test_set_ntp_server() { Serial.println("\n-- setNTPServer() / getNTPServer() --"); - WiFiUDP udp; + NTPUdp udp; EasyNTPClient client(udp, "pool.ntp.org"); check(strcmp(client.getNTPServer(), "pool.ntp.org") == 0, @@ -149,7 +165,7 @@ void test_set_ntp_server() { void test_set_update_interval() { Serial.println("\n-- setUpdateInterval() / getUpdateInterval() / 4-arg constructor --"); - WiFiUDP udp; + NTPUdp udp; EasyNTPClient client(udp, "pool.ntp.org"); client.setUpdateInterval(30); @@ -165,8 +181,9 @@ void test_set_update_interval() { // ── stale time preservation ────────────────────────────────────────────────── void test_stale_time() { +#if defined(ESP8266) || defined(ESP32) Serial.println("\n-- stale time preserved when resync fails (wait ~70 s) --"); - WiFiUDP udp; + NTPUdp udp; EasyNTPClient client(udp, "pool.ntp.org"); // Populate mServerTime with a good sync. @@ -187,6 +204,10 @@ void test_stale_time() { // Reconnect before the next test group. wifi_connect(); +#else + Serial.println("\n-- stale time: skipped" + " (WiFiEsp does not support WiFi.disconnect() on AVR) --"); +#endif } // ── entry points ───────────────────────────────────────────────────────────── @@ -196,6 +217,12 @@ void setup() { delay(100); Serial.println("\n=== EasyNTPClient test suite ==="); +#if !defined(ESP8266) && !defined(ESP32) + Serial.println("Initializing WiFiEsp with SoftwareSerial on pins D3 (RX) and D2 (TX)..."); + esp_serial.begin(9600); + WiFi.init(&esp_serial); +#endif + wifi_connect(); test_constants(); From 0727917358cf5e58d2be8aaafd57fb351548aee0 Mon Sep 17 00:00:00 2001 From: aharshac Date: Sun, 10 May 2026 15:37:11 +0530 Subject: [PATCH 2/4] chore: add TestNTPClient to CI matrix - Build for ESP8266, ESP32, and Uno+ESP-01 --- .github/workflows/ci.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bff97b..af4dfad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,12 +31,23 @@ jobs: platforms: | - name: esp8266:esp8266 source-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json - - name: TestNodeMCU (ESP8266) + - name: TestNTPClient (ESP8266) fqbn: esp8266:esp8266:nodemcuv2 - sketch: examples/TestNodeMCU/TestNodeMCU.ino + sketch: examples/TestNTPClient/TestNTPClient.ino platforms: | - name: esp8266:esp8266 source-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json + - name: TestNTPClient (ESP32) + fqbn: esp32:esp32:esp32dev + sketch: examples/TestNTPClient/TestNTPClient.ino + platforms: | + - name: esp32:esp32 + source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + - name: TestNTPClient (Uno+ESP-01) + fqbn: arduino:avr:uno + sketch: examples/TestNTPClient/TestNTPClient.ino + platforms: | + - name: arduino:avr - name: Arduino UNO fqbn: arduino:avr:uno sketch: examples/ArduinoEspWifiShield/ArduinoEspWifiShield.ino From cd5f7a0d314f7d9f6144262023513bdd0d95cd96 Mon Sep 17 00:00:00 2001 From: aharshac Date: Sun, 10 May 2026 15:48:06 +0530 Subject: [PATCH 3/4] test: add per-test numbering, fix ESP32 watchdog --- .github/workflows/ci.yml | 16 +++++++----- examples/TestNTPClient/TestNTPClient.ino | 33 ++++++++++++++++++------ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af4dfad..70228b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,12 +25,7 @@ jobs: fail-fast: false matrix: config: - - name: NodeMCU (ESP8266) - fqbn: esp8266:esp8266:nodemcuv2 - sketch: examples/NodeMCU/NodeMCU.ino - platforms: | - - name: esp8266:esp8266 - source-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json + # TestNTPClient - name: TestNTPClient (ESP8266) fqbn: esp8266:esp8266:nodemcuv2 sketch: examples/TestNTPClient/TestNTPClient.ino @@ -38,7 +33,7 @@ jobs: - name: esp8266:esp8266 source-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json - name: TestNTPClient (ESP32) - fqbn: esp32:esp32:esp32dev + fqbn: esp32:esp32:esp32 sketch: examples/TestNTPClient/TestNTPClient.ino platforms: | - name: esp32:esp32 @@ -48,6 +43,13 @@ jobs: sketch: examples/TestNTPClient/TestNTPClient.ino platforms: | - name: arduino:avr + # examples + - name: NodeMCU (ESP8266) + fqbn: esp8266:esp8266:nodemcuv2 + sketch: examples/NodeMCU/NodeMCU.ino + platforms: | + - name: esp8266:esp8266 + source-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json - name: Arduino UNO fqbn: arduino:avr:uno sketch: examples/ArduinoEspWifiShield/ArduinoEspWifiShield.ino diff --git a/examples/TestNTPClient/TestNTPClient.ino b/examples/TestNTPClient/TestNTPClient.ino index b5b0ab8..3d3adc3 100644 --- a/examples/TestNTPClient/TestNTPClient.ino +++ b/examples/TestNTPClient/TestNTPClient.ino @@ -10,6 +10,7 @@ #elif defined(ESP32) #include #include + #include "esp_task_wdt.h" #else #include #include @@ -195,7 +196,12 @@ void test_stale_time() { // preserved mServerTime + millis() drift. WiFi.disconnect(); Serial.println(" WiFi disconnected. Waiting 65 s..."); +#if defined(ESP32) + // vTaskDelay() does not reset the TWDT; feed it once per second. + for (int i = 0; i < 65; i++) { delay(1000); esp_task_wdt_reset(); } +#else delay(65000); +#endif unsigned long stale = client.getUnixTime(); long drift = (long)stale - (long)good; @@ -212,6 +218,15 @@ void test_stale_time() { // ── entry points ───────────────────────────────────────────────────────────── +#define RUN(n, fn) do { \ + Serial.println("\n>>> [" #n "/8] " #fn); \ + int _p = g_passed, _f = g_failed; \ + fn(); \ + Serial.print("<<< [" #n "/8] "); \ + Serial.print(g_passed - _p); Serial.print(" passed, "); \ + Serial.print(g_failed - _f); Serial.println(" failed"); \ +} while (0) + void setup() { Serial.begin(115200); delay(100); @@ -225,18 +240,20 @@ void setup() { wifi_connect(); - test_constants(); - test_basic_sync(); - test_client_reuse(); - test_offset_immediate(); - test_was_updated(); - test_set_ntp_server(); - test_set_update_interval(); - test_stale_time(); + RUN(1, test_constants); + RUN(2, test_basic_sync); + RUN(3, test_client_reuse); + RUN(4, test_offset_immediate); + RUN(5, test_was_updated); + RUN(6, test_set_ntp_server); + RUN(7, test_set_update_interval); + RUN(8, test_stale_time); Serial.println("\n=== Results ==="); Serial.print(g_passed); Serial.println(" passed"); Serial.print(g_failed); Serial.println(" failed"); } +#undef RUN + void loop() {} From d52066bdeb87ef7c550352b868e81727312d96ee Mon Sep 17 00:00:00 2001 From: aharshac Date: Sun, 10 May 2026 16:06:32 +0530 Subject: [PATCH 4/4] fix: optimize memory by using F() macro for string literals --- examples/TestNTPClient/TestNTPClient.ino | 106 ++++++++++++----------- 1 file changed, 54 insertions(+), 52 deletions(-) diff --git a/examples/TestNTPClient/TestNTPClient.ino b/examples/TestNTPClient/TestNTPClient.ino index 3d3adc3..098b090 100644 --- a/examples/TestNTPClient/TestNTPClient.ino +++ b/examples/TestNTPClient/TestNTPClient.ino @@ -38,53 +38,53 @@ static const unsigned long MAX_UNIX_2030 = 1893456000UL; int g_passed = 0; int g_failed = 0; -void check(bool condition, const char* label) { - if (condition) { Serial.print("[PASS] "); g_passed++; } - else { Serial.print("[FAIL] "); g_failed++; } +void check(bool condition, const __FlashStringHelper* label) { + if (condition) { Serial.print(F("[PASS] ")); g_passed++; } + else { Serial.print(F("[FAIL] ")); g_failed++; } Serial.println(label); } // ── WiFi ───────────────────────────────────────────────────────────────────── void wifi_connect() { - Serial.print("Connecting to WiFi"); + Serial.print(F("Connecting to WiFi")); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); - while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } - Serial.println(" connected."); + while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print(F(".")); } + Serial.println(F(" connected.")); } // ── NTP constants ──────────────────────────────────────────────────────────── void test_constants() { - Serial.println("\n-- NTP constants --"); - check(NTP_PACKET_SIZE == 48, "NTP_PACKET_SIZE == 48"); - check(NTP_TX_TIMESTAMP_OFFSET == 40, "NTP_TX_TIMESTAMP_OFFSET == 40"); - check(NTP_SERVER_PORT == 123, "NTP_SERVER_PORT == 123"); - check(NTP_REQUEST_PORT == 1123, "NTP_REQUEST_PORT == 1123"); - check(NTP_HEADER_LI == 0b11000000, "NTP_HEADER_LI == 0xC0"); - check(NTP_HEADER_VN == 0b00100000, "NTP_HEADER_VN == 0x20"); - check(NTP_HEADER_MODE == 0b00000011, "NTP_HEADER_MODE == 0x03"); + Serial.println(F("\n-- NTP constants --")); + check(NTP_PACKET_SIZE == 48, F("NTP_PACKET_SIZE == 48")); + check(NTP_TX_TIMESTAMP_OFFSET == 40, F("NTP_TX_TIMESTAMP_OFFSET == 40")); + check(NTP_SERVER_PORT == 123, F("NTP_SERVER_PORT == 123")); + check(NTP_REQUEST_PORT == 1123, F("NTP_REQUEST_PORT == 1123")); + check(NTP_HEADER_LI == 0b11000000, F("NTP_HEADER_LI == 0xC0")); + check(NTP_HEADER_VN == 0b00100000, F("NTP_HEADER_VN == 0x20")); + check(NTP_HEADER_MODE == 0b00000011, F("NTP_HEADER_MODE == 0x03")); // Combined byte 0 must equal the first byte of the old magic constant 0xEC0600E3. check((NTP_HEADER_LI | NTP_HEADER_VN | NTP_HEADER_MODE) == 0xE3, - "header byte 0 matches original magic constant 0xE3"); + F("header byte 0 matches original magic constant 0xE3")); } // ── basic sync ─────────────────────────────────────────────────────────────── void test_basic_sync() { - Serial.println("\n-- basic sync --"); + Serial.println(F("\n-- basic sync --")); NTPUdp udp; EasyNTPClient client(udp, "pool.ntp.org"); unsigned long t = client.getUnixTime(); - check(t > MIN_UNIX_2024, "time is after 2024-01-01"); - check(t < MAX_UNIX_2030, "time is before 2030-01-01"); + check(t > MIN_UNIX_2024, F("time is after 2024-01-01")); + check(t < MAX_UNIX_2030, F("time is before 2030-01-01")); } // ── socket reuse ───────────────────────────────────────────────────────────── void test_client_reuse() { - Serial.println("\n-- socket reuse across client instances --"); + Serial.println(F("\n-- socket reuse across client instances --")); // Both clients share the same UDP object. Without Fix B+C the second // begin() is skipped (static flag) and the second sync fails. @@ -101,101 +101,103 @@ void test_client_reuse() { t2 = c2.getUnixTime(); // Fix C re-runs begin(); Fix B already closed it cleanly } - check(t1 > MIN_UNIX_2024, "first client syncs successfully"); - check(t2 > MIN_UNIX_2024, "second client syncs on same UDP object"); - check(t2 >= t1 && (t2 - t1) < 5, "timestamps consistent between clients"); + check(t1 > MIN_UNIX_2024, F("first client syncs successfully")); + check(t2 > MIN_UNIX_2024, F("second client syncs on same UDP object")); + check(t2 >= t1 && (t2 - t1) < 5, F("timestamps consistent between clients")); } // ── offset decoupling ──────────────────────────────────────────────────────── void test_offset_immediate() { - Serial.println("\n-- offset change takes effect without resync --"); + Serial.println(F("\n-- offset change takes effect without resync --")); NTPUdp udp; EasyNTPClient client(udp, "pool.ntp.org"); // Sync once with offset = 0; capture base time. client.setTimeOffset(0); unsigned long base = client.getUnixTime(); - check(base > MIN_UNIX_2024, "initial sync with offset=0 succeeds"); + check(base > MIN_UNIX_2024, F("initial sync with offset=0 succeeds")); // Change offset without waiting for a resync (mUpdateInterval not elapsed). client.setTimeOffset(3600); unsigned long adjusted = client.getUnixTime(); long delta = (long)adjusted - (long)base; - check(delta >= 3598 && delta <= 3602, "+3600 s offset reflected on next call"); + check(delta >= 3598 && delta <= 3602, F("+3600 s offset reflected on next call")); // Reverse to zero. client.setTimeOffset(0); unsigned long restored = client.getUnixTime(); delta = (long)restored - (long)base; - check(delta >= 0 && delta <= 2, "offset=0 reflected immediately after reversal"); + check(delta >= 0 && delta <= 2, F("offset=0 reflected immediately after reversal")); } // ── wasUpdated flag ────────────────────────────────────────────────────────── void test_was_updated() { - Serial.println("\n-- wasUpdated() flag --"); + Serial.println(F("\n-- wasUpdated() flag --")); NTPUdp udp; EasyNTPClient client(udp, "pool.ntp.org"); - check(!client.wasUpdated(), "wasUpdated() is false before first sync"); + check(!client.wasUpdated(), F("wasUpdated() is false before first sync")); client.getUnixTime(); - check(client.wasUpdated(), "wasUpdated() is true after successful sync"); + check(client.wasUpdated(), F("wasUpdated() is true after successful sync")); } // ── NTP server get/set ─────────────────────────────────────────────────────── void test_set_ntp_server() { - Serial.println("\n-- setNTPServer() / getNTPServer() --"); + Serial.println(F("\n-- setNTPServer() / getNTPServer() --")); NTPUdp udp; EasyNTPClient client(udp, "pool.ntp.org"); check(strcmp(client.getNTPServer(), "pool.ntp.org") == 0, - "getNTPServer() returns initial pool"); + F("getNTPServer() returns initial pool")); client.setNTPServer("time.cloudflare.com"); check(strcmp(client.getNTPServer(), "time.cloudflare.com") == 0, - "getNTPServer() reflects setNTPServer()"); + F("getNTPServer() reflects setNTPServer()")); unsigned long t = client.getUnixTime(); - check(t > MIN_UNIX_2024, "syncs from server set via setNTPServer()"); + check(t > MIN_UNIX_2024, F("syncs from server set via setNTPServer()")); } // ── update interval get/set ─────────────────────────────────────────────────── void test_set_update_interval() { - Serial.println("\n-- setUpdateInterval() / getUpdateInterval() / 4-arg constructor --"); + Serial.println(F("\n-- setUpdateInterval() / getUpdateInterval() / 4-arg constructor --")); NTPUdp udp; EasyNTPClient client(udp, "pool.ntp.org"); client.setUpdateInterval(30); - check(client.getUpdateInterval() == 30, "getUpdateInterval() reflects setUpdateInterval(30 s)"); + check(client.getUpdateInterval() == 30, + F("getUpdateInterval() reflects setUpdateInterval(30 s)")); EasyNTPClient client2(udp, "pool.ntp.org", 0, 120); - check(client2.getUpdateInterval() == 120, "4-arg constructor sets update interval to 120 s"); + check(client2.getUpdateInterval() == 120, + F("4-arg constructor sets update interval to 120 s")); unsigned long t = client2.getUnixTime(); - check(t > MIN_UNIX_2024, "syncs with interval set via 4-arg constructor"); + check(t > MIN_UNIX_2024, F("syncs with interval set via 4-arg constructor")); } // ── stale time preservation ────────────────────────────────────────────────── void test_stale_time() { #if defined(ESP8266) || defined(ESP32) - Serial.println("\n-- stale time preserved when resync fails (wait ~70 s) --"); + Serial.println(F("\n-- stale time preserved when resync fails (wait ~70 s) --")); NTPUdp udp; EasyNTPClient client(udp, "pool.ntp.org"); // Populate mServerTime with a good sync. unsigned long good = client.getUnixTime(); - check(good > MIN_UNIX_2024, "initial sync succeeds before WiFi drop"); + check(good > MIN_UNIX_2024, F("initial sync succeeds before WiFi drop")); // Drop WiFi and wait for mUpdateInterval (60 s default) to expire so the // next getUnixTime() call attempts a resync, fails, and should return the // preserved mServerTime + millis() drift. WiFi.disconnect(); - Serial.println(" WiFi disconnected. Waiting 65 s..."); + Serial.println(F(" WiFi disconnected. Waiting 65 s...")); #if defined(ESP32) // vTaskDelay() does not reset the TWDT; feed it once per second. for (int i = 0; i < 65; i++) { delay(1000); esp_task_wdt_reset(); } @@ -206,34 +208,34 @@ void test_stale_time() { unsigned long stale = client.getUnixTime(); long drift = (long)stale - (long)good; // Allow 63-72 s: 65 s delay plus up to 7 s for sync timeout polling. - check(drift >= 63 && drift <= 72, "stale time preserved and advancing during no-WiFi"); + check(drift >= 63 && drift <= 72, + F("stale time preserved and advancing during no-WiFi")); // Reconnect before the next test group. wifi_connect(); #else - Serial.println("\n-- stale time: skipped" - " (WiFiEsp does not support WiFi.disconnect() on AVR) --"); + Serial.println(F("\n-- stale time: skipped" + " (WiFiEsp does not support WiFi.disconnect() on AVR) --")); #endif } // ── entry points ───────────────────────────────────────────────────────────── #define RUN(n, fn) do { \ - Serial.println("\n>>> [" #n "/8] " #fn); \ + Serial.println(F("\n>>> [" #n "/8] " #fn)); \ int _p = g_passed, _f = g_failed; \ fn(); \ - Serial.print("<<< [" #n "/8] "); \ - Serial.print(g_passed - _p); Serial.print(" passed, "); \ - Serial.print(g_failed - _f); Serial.println(" failed"); \ + Serial.print(F("<<< [" #n "/8] ")); \ + Serial.print(g_passed - _p); Serial.print(F(" passed, ")); \ + Serial.print(g_failed - _f); Serial.println(F(" failed")); \ } while (0) void setup() { Serial.begin(115200); delay(100); - Serial.println("\n=== EasyNTPClient test suite ==="); + Serial.println(F("\n=== EasyNTPClient test suite ===")); #if !defined(ESP8266) && !defined(ESP32) - Serial.println("Initializing WiFiEsp with SoftwareSerial on pins D3 (RX) and D2 (TX)..."); esp_serial.begin(9600); WiFi.init(&esp_serial); #endif @@ -249,9 +251,9 @@ void setup() { RUN(7, test_set_update_interval); RUN(8, test_stale_time); - Serial.println("\n=== Results ==="); - Serial.print(g_passed); Serial.println(" passed"); - Serial.print(g_failed); Serial.println(" failed"); + Serial.println(F("\n=== Results ===")); + Serial.print(g_passed); Serial.println(F(" passed")); + Serial.print(g_failed); Serial.println(F(" failed")); } #undef RUN