Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 &currentC, float &averageC) const` – latest temperature and running average; returns `false` if disabled, unsupported, or not yet sampled.
Expand Down
6 changes: 6 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
build_flags = -std=gnu++17
7 changes: 5 additions & 2 deletions src/esp_cpu_monitor/cpu_monitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ void ESPCpuMonitor::deinit() {
calibrated_ = false;
hasSample_ = false;
history_.clear();
callbacks_.clear();
resetSmoothingState();
resetTemperatureState();
}

bool ESPCpuMonitor::isReady() const {
Expand Down Expand Up @@ -268,7 +271,7 @@ std::vector<CpuUsageSample> ESPCpuMonitor::history() const {
}

bool ESPCpuMonitor::sampleNow(CpuUsageSample &out) {
if (s_instance != this) {
if (!isInitialized()) {
ESP_LOGE(TAG, "Call init() before sampleNow()");
return false;
}
Expand Down Expand Up @@ -306,7 +309,7 @@ bool IRAM_ATTR ESPCpuMonitor::idleHookCore1() {

void ESPCpuMonitor::timerCallback(void *arg) {
auto *self = static_cast<ESPCpuMonitor *>(arg);
if (!self) {
if (!self || !self->isInitialized()) {
return;
}
CpuUsageSample sample{};
Expand Down
1 change: 1 addition & 0 deletions src/esp_cpu_monitor/cpu_monitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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.")
81 changes: 81 additions & 0 deletions test/test_cpu_monitor/test_cpu_monitor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include <Arduino.h>
#include <ESPCpuMonitor.h>
#include <unity.h>

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);
}