diff --git a/README.md b/README.md index c5bc23b..4fe59ce 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ ESPDate is a tiny C++17 helper for ESP32 projects that makes working with dates - **Optional NTP bootstrap**: call `init` with `ESPDateConfig` containing both `timeZone` and `ntpServer` to set TZ and start SNTP after Arduino/WiFi is ready. - **NTP sync callback + manual re-sync**: register `setNtpSyncCallback(...)` with a function, lambda, or `std::bind`, call `syncNTP()` anytime to trigger an immediate refresh, and optionally override SNTP interval via `ntpSyncIntervalMs` / `setNtpSyncIntervalMs(...)`. - **Optional PSRAM-backed config/state buffers**: `ESPDateConfig::usePSRAMBuffers` routes ESPDate-owned text state (timezone/NTP/scoped TZ restore buffers) through `ESPBufferManager` with automatic fallback. -- **Explicit lifecycle cleanup**: `deinit()` unregisters ESPDate-owned SNTP callback hooks; the destructor calls it automatically. +- **Explicit lifecycle cleanup**: `deinit()` unregisters ESPDate-owned SNTP callback hooks, clears runtime config buffers, and is safe to call repeatedly; the destructor calls it automatically. +- **Init-state introspection**: `isInitialized()` reports whether `init(...)` has been called without a matching `deinit()`. - **Last sync tracking**: `hasLastNtpSync()` / `lastNtpSync()` expose the latest SNTP sync timestamp kept inside `ESPDate`. - **Last sync string helpers**: `lastNtpSyncStringLocal/Utc` provide direct formatting helpers for `lastNtpSync`. - **Local breakdown helpers**: `nowLocal()` / `toLocal()` surface the broken-out local time (with UTC offset) for quick DST/debug checks; feed sunrise/sunset results into `toLocal` to read them in local time. @@ -118,6 +119,20 @@ void setup() { std::string utcString = date.nowUtcString(); Serial.printf("UTC now (string): %s\n", utcString.c_str()); } + +void loop() { + // Example teardown path (mode switch / OTA / feature shutdown). + static bool released = false; + if (!released && millis() > 60000UL) { + if (date.isInitialized()) { + date.deinit(); + } + if (solar.isInitialized()) { + solar.deinit(); + } + released = true; + } +} ``` ### Working With Local Time (UI) vs UTC (storage/logic) @@ -186,6 +201,7 @@ public: ~ESPDate(); void init(const ESPDateConfig &config); void deinit(); + bool isInitialized() const; void setNtpSyncCallback(NtpSyncCallback callback); template void setNtpSyncCallback(Callable&& callback); // capturing lambda/std::bind/functor diff --git a/examples/basic_date/basic_date.ino b/examples/basic_date/basic_date.ino index 42a373a..ae04068 100644 --- a/examples/basic_date/basic_date.ino +++ b/examples/basic_date/basic_date.ino @@ -3,6 +3,7 @@ #include ESPDate date; +bool releasedDateResources = false; class SyncObserver { public: @@ -95,5 +96,10 @@ void setup() { } void loop() { - // Intentionally empty. + // Demonstrate explicit teardown in long-running sketches. + if (!releasedDateResources && millis() > 60000UL && date.isInitialized()) { + date.deinit(); + releasedDateResources = true; + Serial.println("ESPDate deinitialized."); + } } diff --git a/src/esp_date/date.cpp b/src/esp_date/date.cpp index f5a0d45..ce43733 100644 --- a/src/esp_date/date.cpp +++ b/src/esp_date/date.cpp @@ -191,9 +191,17 @@ ESPDate::~ESPDate() { void ESPDate::deinit() { ntpSyncCallback_ = nullptr; ntpSyncCallbackCallable_ = NtpSyncCallable{}; - usePSRAMBuffers_ = false; hasLastNtpSync_ = false; lastNtpSync_ = DateTime{}; + hasLocation_ = false; + latitude_ = 0.0f; + longitude_ = 0.0f; + ntpSyncIntervalMs_ = 0; + const bool usePSRAM = usePSRAMBuffers_; + timeZone_ = DateString(DateAllocator(usePSRAM)); + ntpServer_ = DateString(DateAllocator(usePSRAM)); + usePSRAMBuffers_ = false; + initialized_ = false; if (activeNtpSyncOwner_ == this) { activeNtpSyncOwner_ = nullptr; @@ -229,6 +237,7 @@ void ESPDate::init(const ESPDateConfig& config) { setenv("TZ", timeZone_.c_str(), 1); tzset(); } + initialized_ = true; } void ESPDate::setNtpSyncCallback(NtpSyncCallback callback) { diff --git a/src/esp_date/date.h b/src/esp_date/date.h index 59d21ac..0d52de1 100644 --- a/src/esp_date/date.h +++ b/src/esp_date/date.h @@ -79,6 +79,9 @@ class ESPDate { ~ESPDate(); void init(const ESPDateConfig& config); void deinit(); + bool isInitialized() const { + return initialized_; + } // Optional SNTP sync notification. Pass nullptr to clear. void setNtpSyncCallback(NtpSyncCallback callback); // Accepts capturing lambdas / std::bind / functors. @@ -298,4 +301,5 @@ class ESPDate { static NtpSyncCallable activeNtpSyncCallbackCallable_; static ESPDate* activeNtpSyncOwner_; bool hasLocation_ = false; + bool initialized_ = false; }; diff --git a/test/test_esp_date/test_esp_date.cpp b/test/test_esp_date/test_esp_date.cpp index 0b5dca9..81b9a2d 100644 --- a/test/test_esp_date/test_esp_date.cpp +++ b/test/test_esp_date/test_esp_date.cpp @@ -11,6 +11,42 @@ ESPDate date; static const float kBudapestLat = 47.4979f; static const float kBudapestLon = 19.0402f; +static void test_deinit_is_safe_before_init() { + ESPDate monitor; + TEST_ASSERT_FALSE(monitor.isInitialized()); + + monitor.deinit(); + TEST_ASSERT_FALSE(monitor.isInitialized()); +} + +static void test_deinit_is_idempotent() { + ESPDate monitor; + monitor.init(ESPDateConfig{0.0f, 0.0f, "UTC0", nullptr}); + TEST_ASSERT_TRUE(monitor.isInitialized()); + + monitor.deinit(); + TEST_ASSERT_FALSE(monitor.isInitialized()); + + monitor.deinit(); + TEST_ASSERT_FALSE(monitor.isInitialized()); +} + +static void test_reinit_after_deinit() { + ESPDate monitor; + monitor.init(ESPDateConfig{0.0f, 0.0f, "UTC0", nullptr}); + TEST_ASSERT_TRUE(monitor.isInitialized()); + + monitor.deinit(); + TEST_ASSERT_FALSE(monitor.isInitialized()); + + monitor.init(ESPDateConfig{kBudapestLat, kBudapestLon, "CET-1CEST,M3.5.0/2,M10.5.0/3", nullptr}); + TEST_ASSERT_TRUE(monitor.isInitialized()); + TEST_ASSERT_TRUE(monitor.sunrise(monitor.fromUtc(2024, 6, 1)).ok); + + monitor.deinit(); + TEST_ASSERT_FALSE(monitor.isInitialized()); +} + static void test_add_days_and_differences() { DateTime base = date.fromUnixSeconds(1704067200); // 2024-01-01T00:00:00Z DateTime plus = date.addDays(base, 1); @@ -331,6 +367,9 @@ void setup() { tzset(); delay(2000); UNITY_BEGIN(); + RUN_TEST(test_deinit_is_safe_before_init); + RUN_TEST(test_deinit_is_idempotent); + RUN_TEST(test_reinit_after_deinit); RUN_TEST(test_add_days_and_differences); RUN_TEST(test_add_months_clamps_day_in_leap_year); RUN_TEST(test_start_and_end_of_day_utc);