diff --git a/README.md b/README.md index 09db369..02e3143 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ void loop() { When you set `sampleIntervalMs` to `0`, call `sampleNow()` on your monitor instance (for example, `cpuMonitor.sampleNow(sample)`) from your scheduler to drive sampling manually. If temperature is enabled, `getLastTemperature(current, average)` returns the latest reading and running mean (returns `false` when unsupported or not ready). +When your feature shuts down (task exit, OTA handoff, mode switch), call `cpuMonitor.deinit()` to release idle hooks and timer resources. ## Gotchas - CPU usage numbers are floats in percent so you can see tiny changes; `1.0` means ~1% busy, not 100%. Feel free to round (`%.0f%%`) in your logs/UI if you prefer whole numbers. @@ -82,7 +83,8 @@ If temperature is enabled, `getLastTemperature(current, average)` returns the la - Some ESP32 variants or Arduino builds omit the internal temperature sensor; in that case `temperatureC`/`temperatureAvgC` stay `NaN` and `getLastTemperature` returns `false`. ## API Reference -- `bool init(const CpuMonitorConfig &cfg = {})` / `void deinit()` – register idle hooks, create the esp_timer (when `sampleIntervalMs > 0`), and reset state. +- `bool init(const CpuMonitorConfig &cfg = {})` / `void deinit()` – register idle hooks, create the esp_timer (when `sampleIntervalMs > 0`), and reset state. `deinit()` is safe to call before `init()` and on repeated calls. +- `bool isInitialized() const` – returns `true` while the monitor owns the shared idle hooks/timer resources. - `bool isReady() const` – returns true once calibration completed and a sample is stored. - `bool getLastSample(CpuUsageSample &out) const` / `float getLastAverage() const` / `float getLastSmoothedAverage() const` – read the latest sample; average/smoothed values return `-1.0f` until ready (or when smoothing is disabled). - `bool getLastTemperature(float ¤tC, float &averageC) const` – latest temperature and running average; returns `false` if disabled, unsupported, or not yet sampled. diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..53ded0f --- /dev/null +++ b/platformio.ini @@ -0,0 +1,6 @@ +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino +monitor_speed = 115200 +build_flags = -std=gnu++17 diff --git a/src/esp_cpu_monitor/cpu_monitor.cpp b/src/esp_cpu_monitor/cpu_monitor.cpp index bb6e840..fa712fd 100644 --- a/src/esp_cpu_monitor/cpu_monitor.cpp +++ b/src/esp_cpu_monitor/cpu_monitor.cpp @@ -209,6 +209,9 @@ void ESPCpuMonitor::deinit() { calibrated_ = false; hasSample_ = false; history_.clear(); + callbacks_.clear(); + resetSmoothingState(); + resetTemperatureState(); } bool ESPCpuMonitor::isReady() const { @@ -268,7 +271,7 @@ std::vector ESPCpuMonitor::history() const { } bool ESPCpuMonitor::sampleNow(CpuUsageSample &out) { - if (s_instance != this) { + if (!isInitialized()) { ESP_LOGE(TAG, "Call init() before sampleNow()"); return false; } @@ -306,7 +309,7 @@ bool IRAM_ATTR ESPCpuMonitor::idleHookCore1() { void ESPCpuMonitor::timerCallback(void *arg) { auto *self = static_cast(arg); - if (!self) { + if (!self || !self->isInitialized()) { return; } CpuUsageSample sample{}; diff --git a/src/esp_cpu_monitor/cpu_monitor.h b/src/esp_cpu_monitor/cpu_monitor.h index 4383c30..b3867b6 100644 --- a/src/esp_cpu_monitor/cpu_monitor.h +++ b/src/esp_cpu_monitor/cpu_monitor.h @@ -124,6 +124,7 @@ class ESPCpuMonitor { bool init(const CpuMonitorConfig &cfg = {}); void deinit(); + bool isInitialized() const { return s_instance == this; } bool isReady() const; bool getLastSample(CpuUsageSample &out) const; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..0e84a05 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,4 @@ +# Host-side tests are not available because ESPCpuMonitor depends on +# ESP32 FreeRTOS idle hooks and esp_timer. Run on-device tests in +# test/test_cpu_monitor with PlatformIO/Arduino. +message(STATUS "ESPCpuMonitor: host tests are disabled.") diff --git a/test/test_cpu_monitor/test_cpu_monitor.cpp b/test/test_cpu_monitor/test_cpu_monitor.cpp new file mode 100644 index 0000000..145075f --- /dev/null +++ b/test/test_cpu_monitor/test_cpu_monitor.cpp @@ -0,0 +1,81 @@ +#include +#include +#include + +namespace { + +CpuMonitorConfig testConfig() { + CpuMonitorConfig cfg{}; + cfg.sampleIntervalMs = 0; + cfg.calibrationSamples = 1; + cfg.historySize = 4; + cfg.enablePerCore = true; + cfg.enableTemperature = false; + cfg.smoothingMode = CpuSmoothingMode::None; + return cfg; +} + +void test_deinit_is_safe_before_init() { + ESPCpuMonitor monitor; + TEST_ASSERT_FALSE(monitor.isInitialized()); + + monitor.deinit(); + TEST_ASSERT_FALSE(monitor.isInitialized()); +} + +void test_deinit_is_idempotent() { + ESPCpuMonitor monitor; + TEST_ASSERT_TRUE(monitor.init(testConfig())); + TEST_ASSERT_TRUE(monitor.isInitialized()); + + monitor.deinit(); + TEST_ASSERT_FALSE(monitor.isInitialized()); + + monitor.deinit(); + TEST_ASSERT_FALSE(monitor.isInitialized()); +} + +void test_reinit_after_deinit() { + ESPCpuMonitor monitor; + TEST_ASSERT_TRUE(monitor.init(testConfig())); + TEST_ASSERT_TRUE(monitor.isInitialized()); + + monitor.deinit(); + TEST_ASSERT_FALSE(monitor.isInitialized()); + + TEST_ASSERT_TRUE(monitor.init(testConfig())); + TEST_ASSERT_TRUE(monitor.isInitialized()); + monitor.deinit(); +} + +void test_destructor_deinits_active_instance() { + { + ESPCpuMonitor first; + TEST_ASSERT_TRUE(first.init(testConfig())); + TEST_ASSERT_TRUE(first.isInitialized()); + } + + ESPCpuMonitor second; + TEST_ASSERT_TRUE(second.init(testConfig())); + TEST_ASSERT_TRUE(second.isInitialized()); + second.deinit(); +} + +} // namespace + +void setUp() {} +void tearDown() {} + +void setup() { + 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_destructor_deinits_active_instance); + UNITY_END(); +} + +void loop() { + delay(1000); +}