diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bff97b..70228b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,15 +25,28 @@ jobs: fail-fast: false matrix: config: - - name: NodeMCU (ESP8266) + # TestNTPClient + - name: TestNTPClient (ESP8266) fqbn: esp8266:esp8266:nodemcuv2 - sketch: examples/NodeMCU/NodeMCU.ino + sketch: examples/TestNTPClient/TestNTPClient.ino platforms: | - name: esp8266:esp8266 source-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json - - name: TestNodeMCU (ESP8266) + - name: TestNTPClient (ESP32) + fqbn: esp32:esp32:esp32 + 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 + # examples + - name: NodeMCU (ESP8266) fqbn: esp8266:esp8266:nodemcuv2 - sketch: examples/TestNodeMCU/TestNodeMCU.ino + sketch: examples/NodeMCU/NodeMCU.ino platforms: | - name: esp8266:esp8266 source-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json diff --git a/examples/TestNodeMCU/TestNodeMCU.ino b/examples/TestNTPClient/TestNTPClient.ino similarity index 53% rename from examples/TestNodeMCU/TestNodeMCU.ino rename to examples/TestNTPClient/TestNTPClient.ino index de4125b..098b090 100644 --- a/examples/TestNodeMCU/TestNodeMCU.ino +++ b/examples/TestNTPClient/TestNTPClient.ino @@ -1,13 +1,30 @@ /* - 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 + #include "esp_task_wdt.h" +#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 = ""; @@ -21,57 +38,57 @@ 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 --"); - WiFiUDP udp; + 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 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; { @@ -84,132 +101,161 @@ 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 --"); - WiFiUDP udp; + 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 --"); - WiFiUDP udp; + 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() --"); - WiFiUDP udp; + 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 --"); - WiFiUDP udp; + 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() { - Serial.println("\n-- stale time preserved when resync fails (wait ~70 s) --"); - WiFiUDP udp; +#if defined(ESP8266) || defined(ESP32) + 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(); } +#else delay(65000); +#endif 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(F("\n-- stale time: skipped" + " (WiFiEsp does not support WiFi.disconnect() on AVR) --")); +#endif } // ── entry points ───────────────────────────────────────────────────────────── +#define RUN(n, fn) do { \ + Serial.println(F("\n>>> [" #n "/8] " #fn)); \ + int _p = g_passed, _f = g_failed; \ + fn(); \ + 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) + esp_serial.begin(9600); + WiFi.init(&esp_serial); +#endif 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(); - - Serial.println("\n=== Results ==="); - Serial.print(g_passed); Serial.println(" passed"); - Serial.print(g_failed); Serial.println(" failed"); + 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(F("\n=== Results ===")); + Serial.print(g_passed); Serial.println(F(" passed")); + Serial.print(g_failed); Serial.println(F(" failed")); } +#undef RUN + void loop() {}