From 17dd13e42250c99ecb89f8983e206b33b64a404d Mon Sep 17 00:00:00 2001 From: zekageri Date: Tue, 10 Mar 2026 13:11:37 +0100 Subject: [PATCH] chore: align formatter baseline with esptoolkit-template --- .clang-format | 11 + .editorconfig | 11 + .gitignore | 1 - .vscode/bin/clang-format | 19 + .vscode/extensions.json | 9 + .vscode/settings.json | 30 + .vscode/tasks.json | 12 + README.md | 7 + examples/Basic/Basic.ino | 121 +-- examples/PauseResume/PauseResume.ino | 87 +- scripts/format_cpp.sh | 24 + src/esp_timer/timer.cpp | 1196 +++++++++++++++----------- src/esp_timer/timer.h | 325 ++++--- src/esp_timer/timer_allocator.h | 108 ++- test/test_basic/test_main.cpp | 143 +-- 15 files changed, 1217 insertions(+), 887 deletions(-) create mode 100644 .clang-format create mode 100644 .editorconfig create mode 100755 .vscode/bin/clang-format create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100755 scripts/format_cpp.sh diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8450693 --- /dev/null +++ b/.clang-format @@ -0,0 +1,11 @@ +BasedOnStyle: LLVM +ColumnLimit: 100 +BinPackArguments: false +BinPackParameters: false +AllowAllArgumentsOnNextLine: false +AlignAfterOpenBracket: BlockIndent +UseTab: ForIndentation +IndentWidth: 4 +TabWidth: 4 +ContinuationIndentWidth: 4 +AllowShortFunctionsOnASingleLine: None diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d89c76d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +[*.{c,cc,cpp,h,hpp,ino}] +indent_style = tab +indent_size = tab +tab_width = 4 diff --git a/.gitignore b/.gitignore index 78f49b6..6346d5c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ .venv build/ build_prev_runner/ -.vscode \ No newline at end of file diff --git a/.vscode/bin/clang-format b/.vscode/bin/clang-format new file mode 100755 index 0000000..0df371f --- /dev/null +++ b/.vscode/bin/clang-format @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if command -v clang-format >/dev/null 2>&1; then + exec clang-format "$@" +fi + +_home_dir="${HOME:-}" +if [ -n "$_home_dir" ]; then + _candidate="$(ls -1d "$_home_dir"/.vscode/extensions/ms-vscode.cpptools-*-linux-x64/LLVM/bin/clang-format 2>/dev/null | tail -n 1 || true)" + if [ -n "$_candidate" ] && [ -x "$_candidate" ]; then + exec "$_candidate" "$@" + fi +fi + +echo "clang-format executable not found." >&2 +echo "Install clang-format system-wide or install/update ms-vscode.cpptools." >&2 +exit 127 diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..f814711 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "pioarduino.pioarduino-ide", + "xaver.clang-format" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..24368c8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,30 @@ +{ + "files.associations": { + "*.ino": "cpp" + }, + "editor.defaultFormatter": "xaver.clang-format", + "C_Cpp.formatting": "Disabled", + "clang-format.style": "file", + "clang-format.executable": "${workspaceRoot}/.vscode/bin/clang-format", + "[cpp]": { + "editor.defaultFormatter": "xaver.clang-format", + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "editor.formatOnSave": true + }, + "[c]": { + "editor.defaultFormatter": "xaver.clang-format", + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "editor.formatOnSave": true + }, + "[arduino]": { + "editor.defaultFormatter": "xaver.clang-format", + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "editor.formatOnSave": true + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..20e66d5 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,12 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Format Firmware Sources", + "type": "shell", + "command": "bash ${workspaceFolder}/scripts/format_cpp.sh", + "group": "build", + "problemMatcher": [] + } + ] +} diff --git a/README.md b/README.md index 83d98b6..c1ecaee 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,13 @@ Stack sizes are expressed in bytes. ## Tests Unity-based smoke tests live in `test/test_basic`. Drop the folder into your PlatformIO workspace (or add your own `platformio.ini` at the repo root) and run `pio test -e esp32dev` against an ESP32 dev kit. The test harness is Arduino friendly and exercises every timer type. +## Formatting Baseline + +This repository follows the firmware formatting baseline from `esptoolkit-template`: +- `.clang-format` is the source of truth for C/C++/INO layout. +- `.editorconfig` enforces tabs (`tab_width = 4`), LF endings, and final newline. +- Format all tracked firmware sources with `bash scripts/format_cpp.sh`. + ## License MIT — see [LICENSE.md](LICENSE.md). diff --git a/examples/Basic/Basic.ino b/examples/Basic/Basic.ino index c73c6f2..db6fe8f 100644 --- a/examples/Basic/Basic.ino +++ b/examples/Basic/Basic.ino @@ -2,77 +2,88 @@ #include #include -ESPTimer timer; // Create your own instance +ESPTimer timer; // Create your own instance volatile bool shouldDeinit = false; void setup() { - Serial.begin(115200); - delay(500); + Serial.begin(115200); + delay(500); - ESPTimerConfig cfg; // defaults are fine - timer.init(cfg); + ESPTimerConfig cfg; // defaults are fine + timer.init(cfg); - // Triggers once - uint32_t t1 = timer.setTimeout([]() { - Serial.println("1.5 sec is timed out!"); - }, 1500); + // Triggers once + uint32_t t1 = timer.setTimeout([]() { Serial.println("1.5 sec is timed out!"); }, 1500); - // Retriggers every 1500ms - uint32_t t2 = timer.setInterval([]() { - Serial.println("1.5 sec is triggered!"); - }, 1500); + // Retriggers every 1500ms + uint32_t t2 = timer.setInterval([]() { Serial.println("1.5 sec is triggered!"); }, 1500); - // Called every sec for 10000 ms - uint32_t t3 = timer.setSecCounter([](int secLeft) { - Serial.printf("%d sec left so far\n", secLeft); - }, 10000); + // Called every sec for 10000 ms + uint32_t t3 = timer.setSecCounter( + [](int secLeft) { Serial.printf("%d sec left so far\n", secLeft); }, + 10000 + ); - // Called every ms for 10000 ms - uint32_t t4 = timer.setMsCounter([](uint32_t msLeft) { - if (msLeft % 1000 == 0) { - Serial.printf("%lu ms left so far\n", static_cast(msLeft)); - } - }, 10000); + // Called every ms for 10000 ms + uint32_t t4 = timer.setMsCounter( + [](uint32_t msLeft) { + if (msLeft % 1000 == 0) { + Serial.printf("%lu ms left so far\n", static_cast(msLeft)); + } + }, + 10000 + ); - // Called every min for 10000 ms - uint32_t t5 = timer.setMinCounter([](int minLeft) { - Serial.printf("%d min left so far\n", minLeft); - }, 10000); + // Called every min for 10000 ms + uint32_t t5 = timer.setMinCounter( + [](int minLeft) { Serial.printf("%d min left so far\n", minLeft); }, + 10000 + ); - // Example: pause then resume interval, then clear - timer.setTimeout([t2]() { - bool paused = timer.pauseInterval(t2); // pause only - Serial.printf("Interval paused: %s\n", paused ? "ok" : "fail"); - }, 5000); + // Example: pause then resume interval, then clear + timer.setTimeout( + [t2]() { + bool paused = timer.pauseInterval(t2); // pause only + Serial.printf("Interval paused: %s\n", paused ? "ok" : "fail"); + }, + 5000 + ); - timer.setTimeout([t2]() { - bool resumed = timer.resumeInterval(t2); - Serial.printf("Interval resumed: %s\n", resumed ? "ok" : "fail"); - }, 8000); + timer.setTimeout( + [t2]() { + bool resumed = timer.resumeInterval(t2); + Serial.printf("Interval resumed: %s\n", resumed ? "ok" : "fail"); + }, + 8000 + ); - timer.setTimeout([t2]() { - bool cleared = timer.clearInterval(t2); - Serial.printf("Interval cleared: %s\n", cleared ? "ok" : "fail"); - }, 12000); + timer.setTimeout( + [t2]() { + bool cleared = timer.clearInterval(t2); + Serial.printf("Interval cleared: %s\n", cleared ? "ok" : "fail"); + }, + 12000 + ); - // Check status example - timer.setTimeout([t1]() { - auto s = timer.getStatus(t1); - Serial.printf("Timeout status: %d\n", static_cast(s)); - }, 2000); + // Check status example + timer.setTimeout( + [t1]() { + auto s = timer.getStatus(t1); + Serial.printf("Timeout status: %d\n", static_cast(s)); + }, + 2000 + ); - // Request teardown from loop context (not from timer worker tasks). - timer.setTimeout([]() { - shouldDeinit = true; - }, 15000); + // Request teardown from loop context (not from timer worker tasks). + timer.setTimeout([]() { shouldDeinit = true; }, 15000); } void loop() { - if (shouldDeinit && timer.isInitialized()) { - timer.deinit(); - shouldDeinit = false; - Serial.println("Timer runtime deinitialized"); - } + if (shouldDeinit && timer.isInitialized()) { + timer.deinit(); + shouldDeinit = false; + Serial.println("Timer runtime deinitialized"); + } - // App code does other things; timers run in background FreeRTOS tasks + // App code does other things; timers run in background FreeRTOS tasks } diff --git a/examples/PauseResume/PauseResume.ino b/examples/PauseResume/PauseResume.ino index f2ad3da..aa2d25a 100644 --- a/examples/PauseResume/PauseResume.ino +++ b/examples/PauseResume/PauseResume.ino @@ -8,48 +8,53 @@ volatile bool shouldDeinit = false; uint32_t intervalId; void setup() { - Serial.begin(115200); - delay(500); - - timer.init(); - - intervalId = timer.setInterval([]() { - Serial.println("Tick from interval"); - }, 1000); - - // Pause after 3s - timer.setTimeout([]() { - bool ok = timer.pauseInterval(intervalId); - Serial.printf("Paused interval: %s\n", ok ? "ok" : "fail"); - auto s = timer.getStatus(intervalId); - Serial.printf("Status now: %d (Paused=2)\n", (int)s); - }, 3000); - - // Resume after 6s - timer.setTimeout([]() { - bool ok = timer.resumeInterval(intervalId); - Serial.printf("Resumed interval: %s\n", ok ? "ok" : "fail"); - auto s = timer.getStatus(intervalId); - Serial.printf("Status now: %d (Running=1)\n", (int)s); - }, 6000); - - // Clear after 10s - timer.setTimeout([]() { - bool ok = timer.clearInterval(intervalId); - Serial.printf("Cleared interval: %s\n", ok ? "ok" : "fail"); - auto s = timer.getStatus(intervalId); - Serial.printf("Status now: %d (Invalid=0 if removed)\n", (int)s); - }, 10000); - - timer.setTimeout([]() { - shouldDeinit = true; - }, 12000); + Serial.begin(115200); + delay(500); + + timer.init(); + + intervalId = timer.setInterval([]() { Serial.println("Tick from interval"); }, 1000); + + // Pause after 3s + timer.setTimeout( + []() { + bool ok = timer.pauseInterval(intervalId); + Serial.printf("Paused interval: %s\n", ok ? "ok" : "fail"); + auto s = timer.getStatus(intervalId); + Serial.printf("Status now: %d (Paused=2)\n", (int)s); + }, + 3000 + ); + + // Resume after 6s + timer.setTimeout( + []() { + bool ok = timer.resumeInterval(intervalId); + Serial.printf("Resumed interval: %s\n", ok ? "ok" : "fail"); + auto s = timer.getStatus(intervalId); + Serial.printf("Status now: %d (Running=1)\n", (int)s); + }, + 6000 + ); + + // Clear after 10s + timer.setTimeout( + []() { + bool ok = timer.clearInterval(intervalId); + Serial.printf("Cleared interval: %s\n", ok ? "ok" : "fail"); + auto s = timer.getStatus(intervalId); + Serial.printf("Status now: %d (Invalid=0 if removed)\n", (int)s); + }, + 10000 + ); + + timer.setTimeout([]() { shouldDeinit = true; }, 12000); } void loop() { - if (shouldDeinit && timer.isInitialized()) { - timer.deinit(); - shouldDeinit = false; - Serial.println("ESPTimer deinitialized"); - } + if (shouldDeinit && timer.isInitialized()) { + timer.deinit(); + shouldDeinit = false; + Serial.println("ESPTimer deinitialized"); + } } diff --git a/scripts/format_cpp.sh b/scripts/format_cpp.sh new file mode 100755 index 0000000..7d17b04 --- /dev/null +++ b/scripts/format_cpp.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -euo pipefail + +_repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +_clang_format="${_repo_root}/.vscode/bin/clang-format" + +if [ ! -x "${_clang_format}" ]; then + echo "clang-format wrapper not found: ${_clang_format}" >&2 + exit 1 +fi + +mapfile -d '' _format_files < <( + git -C "${_repo_root}" ls-files -z -- '*.c' '*.cc' '*.cpp' '*.h' '*.hpp' '*.ino' +) + +if [ "${#_format_files[@]}" -eq 0 ]; then + echo "No tracked C/C++/INO files found to format." + exit 0 +fi + +"${_clang_format}" -i --style=file "${_format_files[@]}" + +echo "Formatted ${#_format_files[@]} files." diff --git a/src/esp_timer/timer.cpp b/src/esp_timer/timer.cpp index bb5568a..aa3d385 100644 --- a/src/esp_timer/timer.cpp +++ b/src/esp_timer/timer.cpp @@ -4,581 +4,777 @@ #include ESPTimer::~ESPTimer() { - deinit(); + deinit(); } void ESPTimer::deinit() { - if (!initialized_) return; - - running_.store(false, std::memory_order_release); - - auto waitForTaskExit = [](TaskHandle_t& handle) { - if (!handle) { - return; - } - TickType_t start = xTaskGetTickCount(); - while (handle && (xTaskGetTickCount() - start) <= pdMS_TO_TICKS(500)) { - vTaskDelay(pdMS_TO_TICKS(10)); - } - if (handle) { - vTaskDelete(handle); - handle = nullptr; - } - }; - - waitForTaskExit(hTimeout_); - waitForTaskExit(hInterval_); - waitForTaskExit(hSec_); - waitForTaskExit(hMs_); - waitForTaskExit(hMin_); - - auto releaseStorage = [](auto& vec) { - using VecType = std::decay_t; - VecType empty{vec.get_allocator()}; - vec.swap(empty); - }; - - releaseStorage(timeouts_); - releaseStorage(intervals_); - releaseStorage(secs_); - releaseStorage(mss_); - releaseStorage(mins_); - - if (mutex_) { - vSemaphoreDelete(mutex_); - mutex_ = nullptr; - } - initialized_ = false; + if (!initialized_) + return; + + running_.store(false, std::memory_order_release); + + auto waitForTaskExit = [](TaskHandle_t &handle) { + if (!handle) { + return; + } + TickType_t start = xTaskGetTickCount(); + while (handle && (xTaskGetTickCount() - start) <= pdMS_TO_TICKS(500)) { + vTaskDelay(pdMS_TO_TICKS(10)); + } + if (handle) { + vTaskDelete(handle); + handle = nullptr; + } + }; + + waitForTaskExit(hTimeout_); + waitForTaskExit(hInterval_); + waitForTaskExit(hSec_); + waitForTaskExit(hMs_); + waitForTaskExit(hMin_); + + auto releaseStorage = [](auto &vec) { + using VecType = std::decay_t; + VecType empty{vec.get_allocator()}; + vec.swap(empty); + }; + + releaseStorage(timeouts_); + releaseStorage(intervals_); + releaseStorage(secs_); + releaseStorage(mss_); + releaseStorage(mins_); + + if (mutex_) { + vSemaphoreDelete(mutex_); + mutex_ = nullptr; + } + initialized_ = false; } // Internal lock utilities void ESPTimer::lock() { - if (mutex_) { - xSemaphoreTake(mutex_, portMAX_DELAY); - } + if (mutex_) { + xSemaphoreTake(mutex_, portMAX_DELAY); + } } void ESPTimer::unlock() { - if (mutex_) { - xSemaphoreGive(mutex_); - } + if (mutex_) { + xSemaphoreGive(mutex_); + } } uint32_t ESPTimer::nextId() { - // Simple wrap-safe increment (0 is reserved as invalid) - uint32_t id = nextId_++; - if (nextId_ == 0) nextId_ = 1; - return id; -} - -void ESPTimer::init(const ESPTimerConfig& cfg) { - if (initialized_) return; - cfg_ = cfg; - usePSRAMBuffers_ = cfg_.usePSRAMBuffers; - - TimerVector timeoutStorage{TimerAllocator(usePSRAMBuffers_)}; - timeouts_.swap(timeoutStorage); - TimerVector intervalStorage{TimerAllocator(usePSRAMBuffers_)}; - intervals_.swap(intervalStorage); - TimerVector secStorage{TimerAllocator(usePSRAMBuffers_)}; - secs_.swap(secStorage); - TimerVector msStorage{TimerAllocator(usePSRAMBuffers_)}; - mss_.swap(msStorage); - TimerVector minStorage{TimerAllocator(usePSRAMBuffers_)}; - mins_.swap(minStorage); - - if (!mutex_) { - mutex_ = xSemaphoreCreateMutex(); - } - - running_.store(true, std::memory_order_release); - auto createTask = [&](TaskFunction_t fn, - const char* name, - uint16_t stack, - UBaseType_t prio, - int8_t core, - TaskHandle_t& handle) { - const BaseType_t coreId = core < 0 ? tskNO_AFFINITY : static_cast(core); - handle = nullptr; - const BaseType_t created = xTaskCreatePinnedToCore( - fn, name ? name : "ESPTimerTask", stack, this, prio, &handle, coreId); - if (created != pdPASS) { - handle = nullptr; - } - }; - - createTask(&ESPTimer::timeoutTaskTrampoline, "ESPTmrTimeout", cfg_.stackSizeTimeout, cfg_.priorityTimeout, cfg_.coreTimeout, hTimeout_); - createTask(&ESPTimer::intervalTaskTrampoline, "ESPTmrInterval", cfg_.stackSizeInterval, cfg_.priorityInterval, cfg_.coreInterval, hInterval_); - createTask(&ESPTimer::secTaskTrampoline, "ESPTmrSec", cfg_.stackSizeSec, cfg_.prioritySec, cfg_.coreSec, hSec_); - createTask(&ESPTimer::msTaskTrampoline, "ESPTmrMs", cfg_.stackSizeMs, cfg_.priorityMs, cfg_.coreMs, hMs_); - createTask(&ESPTimer::minTaskTrampoline, "ESPTmrMin", cfg_.stackSizeMin, cfg_.priorityMin, cfg_.coreMin, hMin_); - - initialized_ = true; + // Simple wrap-safe increment (0 is reserved as invalid) + uint32_t id = nextId_++; + if (nextId_ == 0) + nextId_ = 1; + return id; +} + +void ESPTimer::init(const ESPTimerConfig &cfg) { + if (initialized_) + return; + cfg_ = cfg; + usePSRAMBuffers_ = cfg_.usePSRAMBuffers; + + TimerVector timeoutStorage{TimerAllocator(usePSRAMBuffers_)}; + timeouts_.swap(timeoutStorage); + TimerVector intervalStorage{TimerAllocator(usePSRAMBuffers_)}; + intervals_.swap(intervalStorage); + TimerVector secStorage{TimerAllocator(usePSRAMBuffers_)}; + secs_.swap(secStorage); + TimerVector msStorage{TimerAllocator(usePSRAMBuffers_)}; + mss_.swap(msStorage); + TimerVector minStorage{TimerAllocator(usePSRAMBuffers_)}; + mins_.swap(minStorage); + + if (!mutex_) { + mutex_ = xSemaphoreCreateMutex(); + } + + running_.store(true, std::memory_order_release); + auto createTask = [&](TaskFunction_t fn, + const char *name, + uint16_t stack, + UBaseType_t prio, + int8_t core, + TaskHandle_t &handle) { + const BaseType_t coreId = core < 0 ? tskNO_AFFINITY : static_cast(core); + handle = nullptr; + const BaseType_t created = xTaskCreatePinnedToCore( + fn, + name ? name : "ESPTimerTask", + stack, + this, + prio, + &handle, + coreId + ); + if (created != pdPASS) { + handle = nullptr; + } + }; + + createTask( + &ESPTimer::timeoutTaskTrampoline, + "ESPTmrTimeout", + cfg_.stackSizeTimeout, + cfg_.priorityTimeout, + cfg_.coreTimeout, + hTimeout_ + ); + createTask( + &ESPTimer::intervalTaskTrampoline, + "ESPTmrInterval", + cfg_.stackSizeInterval, + cfg_.priorityInterval, + cfg_.coreInterval, + hInterval_ + ); + createTask( + &ESPTimer::secTaskTrampoline, + "ESPTmrSec", + cfg_.stackSizeSec, + cfg_.prioritySec, + cfg_.coreSec, + hSec_ + ); + createTask( + &ESPTimer::msTaskTrampoline, + "ESPTmrMs", + cfg_.stackSizeMs, + cfg_.priorityMs, + cfg_.coreMs, + hMs_ + ); + createTask( + &ESPTimer::minTaskTrampoline, + "ESPTmrMin", + cfg_.stackSizeMin, + cfg_.priorityMin, + cfg_.coreMin, + hMin_ + ); + + initialized_ = true; } // Public API: creators uint32_t ESPTimer::setTimeout(std::function cb, uint32_t delayMs) { - TimeoutItem item; - item.type = Type::Timeout; - item.id = nextId(); - item.cb = std::move(cb); - item.createdMs = millis(); - item.dueAtMs = item.createdMs + delayMs; - item.status = ESPTimerStatus::Running; - - lock(); - timeouts_.push_back(std::move(item)); - uint32_t id = timeouts_.back().id; - unlock(); - return id; + TimeoutItem item; + item.type = Type::Timeout; + item.id = nextId(); + item.cb = std::move(cb); + item.createdMs = millis(); + item.dueAtMs = item.createdMs + delayMs; + item.status = ESPTimerStatus::Running; + + lock(); + timeouts_.push_back(std::move(item)); + uint32_t id = timeouts_.back().id; + unlock(); + return id; } uint32_t ESPTimer::setInterval(std::function cb, uint32_t periodMs) { - IntervalItem item; - item.type = Type::Interval; - item.id = nextId(); - item.cb = std::move(cb); - item.createdMs = millis(); - item.periodMs = periodMs; - item.lastFireMs = item.createdMs; - item.status = ESPTimerStatus::Running; - - lock(); - intervals_.push_back(std::move(item)); - uint32_t id = intervals_.back().id; - unlock(); - return id; + IntervalItem item; + item.type = Type::Interval; + item.id = nextId(); + item.cb = std::move(cb); + item.createdMs = millis(); + item.periodMs = periodMs; + item.lastFireMs = item.createdMs; + item.status = ESPTimerStatus::Running; + + lock(); + intervals_.push_back(std::move(item)); + uint32_t id = intervals_.back().id; + unlock(); + return id; } uint32_t ESPTimer::setSecCounter(std::function cb, uint32_t totalMs) { - SecItem item; - item.type = Type::Sec; - item.id = nextId(); - item.cb = std::move(cb); - item.createdMs = millis(); - item.endAtMs = item.createdMs + totalMs; - item.lastTickMs = item.createdMs; - item.status = ESPTimerStatus::Running; - - lock(); - secs_.push_back(std::move(item)); - uint32_t id = secs_.back().id; - unlock(); - return id; + SecItem item; + item.type = Type::Sec; + item.id = nextId(); + item.cb = std::move(cb); + item.createdMs = millis(); + item.endAtMs = item.createdMs + totalMs; + item.lastTickMs = item.createdMs; + item.status = ESPTimerStatus::Running; + + lock(); + secs_.push_back(std::move(item)); + uint32_t id = secs_.back().id; + unlock(); + return id; } uint32_t ESPTimer::setMsCounter(std::function cb, uint32_t totalMs) { - MsItem item; - item.type = Type::Ms; - item.id = nextId(); - item.cb = std::move(cb); - item.createdMs = millis(); - item.endAtMs = item.createdMs + totalMs; - item.lastTickMs = item.createdMs; - item.status = ESPTimerStatus::Running; - - lock(); - mss_.push_back(std::move(item)); - uint32_t id = mss_.back().id; - unlock(); - return id; + MsItem item; + item.type = Type::Ms; + item.id = nextId(); + item.cb = std::move(cb); + item.createdMs = millis(); + item.endAtMs = item.createdMs + totalMs; + item.lastTickMs = item.createdMs; + item.status = ESPTimerStatus::Running; + + lock(); + mss_.push_back(std::move(item)); + uint32_t id = mss_.back().id; + unlock(); + return id; } uint32_t ESPTimer::setMinCounter(std::function cb, uint32_t totalMs) { - MinItem item; - item.type = Type::Min; - item.id = nextId(); - item.cb = std::move(cb); - item.createdMs = millis(); - item.endAtMs = item.createdMs + totalMs; - item.lastTickMs = item.createdMs; - item.status = ESPTimerStatus::Running; - - lock(); - mins_.push_back(std::move(item)); - uint32_t id = mins_.back().id; - unlock(); - return id; + MinItem item; + item.type = Type::Min; + item.id = nextId(); + item.cb = std::move(cb); + item.createdMs = millis(); + item.endAtMs = item.createdMs + totalMs; + item.lastTickMs = item.createdMs; + item.status = ESPTimerStatus::Running; + + lock(); + mins_.push_back(std::move(item)); + uint32_t id = mins_.back().id; + unlock(); + return id; } // Public API: pause/clear/status ESPTimerStatus ESPTimer::togglePause(Type type, uint32_t id) { - ESPTimerStatus newStatus = ESPTimerStatus::Invalid; - auto toggle = [&](auto& vec) { - for (auto& it : vec) { - if (it.id == id) { - if (it.status == ESPTimerStatus::Running) { - it.status = ESPTimerStatus::Paused; - newStatus = ESPTimerStatus::Paused; - } else if (it.status == ESPTimerStatus::Paused) { - it.status = ESPTimerStatus::Running; - // Shift last tick to avoid immediate burst after long pause - if constexpr (std::is_same::value) { - it.lastFireMs = millis(); - } else if constexpr (std::is_same::value || - std::is_same::value || - std::is_same::value) { - it.lastTickMs = millis(); - } - newStatus = ESPTimerStatus::Running; - } - return; - } - } - }; - - lock(); - switch (type) { - case Type::Timeout: toggle(timeouts_); break; - case Type::Interval: toggle(intervals_); break; - case Type::Sec: toggle(secs_); break; - case Type::Ms: toggle(mss_); break; - case Type::Min: toggle(mins_); break; - } - unlock(); - return newStatus; + ESPTimerStatus newStatus = ESPTimerStatus::Invalid; + auto toggle = [&](auto &vec) { + for (auto &it : vec) { + if (it.id == id) { + if (it.status == ESPTimerStatus::Running) { + it.status = ESPTimerStatus::Paused; + newStatus = ESPTimerStatus::Paused; + } else if (it.status == ESPTimerStatus::Paused) { + it.status = ESPTimerStatus::Running; + // Shift last tick to avoid immediate burst after long pause + if constexpr (std::is_same::value) { + it.lastFireMs = millis(); + } else if constexpr (std::is_same::value || + std::is_same::value || + std::is_same::value) { + it.lastTickMs = millis(); + } + newStatus = ESPTimerStatus::Running; + } + return; + } + } + }; + + lock(); + switch (type) { + case Type::Timeout: + toggle(timeouts_); + break; + case Type::Interval: + toggle(intervals_); + break; + case Type::Sec: + toggle(secs_); + break; + case Type::Ms: + toggle(mss_); + break; + case Type::Min: + toggle(mins_); + break; + } + unlock(); + return newStatus; } bool ESPTimer::pauseItem(Type type, uint32_t id) { - bool changed = false; - auto pause_fn = [&](auto& vec) { - for (auto& it : vec) { - if (it.id == id) { - if (it.status == ESPTimerStatus::Running) { - it.status = ESPTimerStatus::Paused; - changed = true; - } - return; - } - } - }; - - lock(); - switch (type) { - case Type::Timeout: pause_fn(timeouts_); break; - case Type::Interval: pause_fn(intervals_); break; - case Type::Sec: pause_fn(secs_); break; - case Type::Ms: pause_fn(mss_); break; - case Type::Min: pause_fn(mins_); break; - } - unlock(); - return changed; + bool changed = false; + auto pause_fn = [&](auto &vec) { + for (auto &it : vec) { + if (it.id == id) { + if (it.status == ESPTimerStatus::Running) { + it.status = ESPTimerStatus::Paused; + changed = true; + } + return; + } + } + }; + + lock(); + switch (type) { + case Type::Timeout: + pause_fn(timeouts_); + break; + case Type::Interval: + pause_fn(intervals_); + break; + case Type::Sec: + pause_fn(secs_); + break; + case Type::Ms: + pause_fn(mss_); + break; + case Type::Min: + pause_fn(mins_); + break; + } + unlock(); + return changed; } bool ESPTimer::resumeItem(Type type, uint32_t id) { - bool changed = false; - auto resume_fn = [&](auto& vec) { - for (auto& it : vec) { - if (it.id == id) { - if (it.status == ESPTimerStatus::Paused) { - it.status = ESPTimerStatus::Running; - // Shift last firing/tick reference to now to avoid burst - if constexpr (std::is_same::value) { - it.lastFireMs = millis(); - } else if constexpr (std::is_same::value || - std::is_same::value || - std::is_same::value) { - it.lastTickMs = millis(); - } - changed = true; - } - return; - } - } - }; - - lock(); - switch (type) { - case Type::Timeout: resume_fn(timeouts_); break; - case Type::Interval: resume_fn(intervals_); break; - case Type::Sec: resume_fn(secs_); break; - case Type::Ms: resume_fn(mss_); break; - case Type::Min: resume_fn(mins_); break; - } - unlock(); - return changed; + bool changed = false; + auto resume_fn = [&](auto &vec) { + for (auto &it : vec) { + if (it.id == id) { + if (it.status == ESPTimerStatus::Paused) { + it.status = ESPTimerStatus::Running; + // Shift last firing/tick reference to now to avoid burst + if constexpr (std::is_same::value) { + it.lastFireMs = millis(); + } else if constexpr (std::is_same::value || + std::is_same::value || + std::is_same::value) { + it.lastTickMs = millis(); + } + changed = true; + } + return; + } + } + }; + + lock(); + switch (type) { + case Type::Timeout: + resume_fn(timeouts_); + break; + case Type::Interval: + resume_fn(intervals_); + break; + case Type::Sec: + resume_fn(secs_); + break; + case Type::Ms: + resume_fn(mss_); + break; + case Type::Min: + resume_fn(mins_); + break; + } + unlock(); + return changed; } bool ESPTimer::clearItem(Type type, uint32_t id) { - bool removed = false; - auto remove_by_id = [&](auto& vec) { - auto it = std::remove_if(vec.begin(), vec.end(), [&](auto& n) { - if (n.id == id) { - removed = true; - return true; - } - return false; - }); - if (it != vec.end()) vec.erase(it, vec.end()); - }; - - lock(); - switch (type) { - case Type::Timeout: remove_by_id(timeouts_); break; - case Type::Interval: remove_by_id(intervals_); break; - case Type::Sec: remove_by_id(secs_); break; - case Type::Ms: remove_by_id(mss_); break; - case Type::Min: remove_by_id(mins_); break; - } - unlock(); - return removed; + bool removed = false; + auto remove_by_id = [&](auto &vec) { + auto it = std::remove_if(vec.begin(), vec.end(), [&](auto &n) { + if (n.id == id) { + removed = true; + return true; + } + return false; + }); + if (it != vec.end()) + vec.erase(it, vec.end()); + }; + + lock(); + switch (type) { + case Type::Timeout: + remove_by_id(timeouts_); + break; + case Type::Interval: + remove_by_id(intervals_); + break; + case Type::Sec: + remove_by_id(secs_); + break; + case Type::Ms: + remove_by_id(mss_); + break; + case Type::Min: + remove_by_id(mins_); + break; + } + unlock(); + return removed; } ESPTimerStatus ESPTimer::getItemStatus(Type type, uint32_t id) { - ESPTimerStatus status = ESPTimerStatus::Invalid; - auto find_by_id = [&](auto& vec) { - for (auto& n : vec) { - if (n.id == id) { - status = n.status; - return; - } - } - }; - - lock(); - switch (type) { - case Type::Timeout: find_by_id(timeouts_); break; - case Type::Interval: find_by_id(intervals_); break; - case Type::Sec: find_by_id(secs_); break; - case Type::Ms: find_by_id(mss_); break; - case Type::Min: find_by_id(mins_); break; - } - unlock(); - return status; -} - -bool ESPTimer::pauseTimer(uint32_t id) { return pauseItem(Type::Timeout, id); } -bool ESPTimer::pauseInterval(uint32_t id) { return pauseItem(Type::Interval, id); } -bool ESPTimer::pauseSecCounter(uint32_t id) { return pauseItem(Type::Sec, id); } -bool ESPTimer::pauseMsCounter(uint32_t id) { return pauseItem(Type::Ms, id); } -bool ESPTimer::pauseMinCounter(uint32_t id) { return pauseItem(Type::Min, id); } - -bool ESPTimer::resumeTimer(uint32_t id) { return resumeItem(Type::Timeout, id); } -bool ESPTimer::resumeInterval(uint32_t id) { return resumeItem(Type::Interval, id); } -bool ESPTimer::resumeSecCounter(uint32_t id) { return resumeItem(Type::Sec, id); } -bool ESPTimer::resumeMsCounter(uint32_t id) { return resumeItem(Type::Ms, id); } -bool ESPTimer::resumeMinCounter(uint32_t id) { return resumeItem(Type::Min, id); } + ESPTimerStatus status = ESPTimerStatus::Invalid; + auto find_by_id = [&](auto &vec) { + for (auto &n : vec) { + if (n.id == id) { + status = n.status; + return; + } + } + }; + + lock(); + switch (type) { + case Type::Timeout: + find_by_id(timeouts_); + break; + case Type::Interval: + find_by_id(intervals_); + break; + case Type::Sec: + find_by_id(secs_); + break; + case Type::Ms: + find_by_id(mss_); + break; + case Type::Min: + find_by_id(mins_); + break; + } + unlock(); + return status; +} + +bool ESPTimer::pauseTimer(uint32_t id) { + return pauseItem(Type::Timeout, id); +} +bool ESPTimer::pauseInterval(uint32_t id) { + return pauseItem(Type::Interval, id); +} +bool ESPTimer::pauseSecCounter(uint32_t id) { + return pauseItem(Type::Sec, id); +} +bool ESPTimer::pauseMsCounter(uint32_t id) { + return pauseItem(Type::Ms, id); +} +bool ESPTimer::pauseMinCounter(uint32_t id) { + return pauseItem(Type::Min, id); +} + +bool ESPTimer::resumeTimer(uint32_t id) { + return resumeItem(Type::Timeout, id); +} +bool ESPTimer::resumeInterval(uint32_t id) { + return resumeItem(Type::Interval, id); +} +bool ESPTimer::resumeSecCounter(uint32_t id) { + return resumeItem(Type::Sec, id); +} +bool ESPTimer::resumeMsCounter(uint32_t id) { + return resumeItem(Type::Ms, id); +} +bool ESPTimer::resumeMinCounter(uint32_t id) { + return resumeItem(Type::Min, id); +} bool ESPTimer::toggleRunStatusTimer(uint32_t id) { - return togglePause(Type::Timeout, id) == ESPTimerStatus::Running; + return togglePause(Type::Timeout, id) == ESPTimerStatus::Running; } bool ESPTimer::toggleRunStatusInterval(uint32_t id) { - return togglePause(Type::Interval, id) == ESPTimerStatus::Running; + return togglePause(Type::Interval, id) == ESPTimerStatus::Running; } bool ESPTimer::toggleRunStatusSecCounter(uint32_t id) { - return togglePause(Type::Sec, id) == ESPTimerStatus::Running; + return togglePause(Type::Sec, id) == ESPTimerStatus::Running; } bool ESPTimer::toggleRunStatusMsCounter(uint32_t id) { - return togglePause(Type::Ms, id) == ESPTimerStatus::Running; + return togglePause(Type::Ms, id) == ESPTimerStatus::Running; } bool ESPTimer::toggleRunStatusMinCounter(uint32_t id) { - return togglePause(Type::Min, id) == ESPTimerStatus::Running; + return togglePause(Type::Min, id) == ESPTimerStatus::Running; } -bool ESPTimer::clearTimeout(uint32_t id) { return clearItem(Type::Timeout, id); } -bool ESPTimer::clearInterval(uint32_t id) { return clearItem(Type::Interval, id); } -bool ESPTimer::clearSecCounter(uint32_t id) { return clearItem(Type::Sec, id); } -bool ESPTimer::clearMsCounter(uint32_t id) { return clearItem(Type::Ms, id); } -bool ESPTimer::clearMinCounter(uint32_t id) { return clearItem(Type::Min, id); } +bool ESPTimer::clearTimeout(uint32_t id) { + return clearItem(Type::Timeout, id); +} +bool ESPTimer::clearInterval(uint32_t id) { + return clearItem(Type::Interval, id); +} +bool ESPTimer::clearSecCounter(uint32_t id) { + return clearItem(Type::Sec, id); +} +bool ESPTimer::clearMsCounter(uint32_t id) { + return clearItem(Type::Ms, id); +} +bool ESPTimer::clearMinCounter(uint32_t id) { + return clearItem(Type::Min, id); +} ESPTimerStatus ESPTimer::getStatus(uint32_t id) { - // Search across all types; first hit wins - ESPTimerStatus s; - s = getItemStatus(Type::Timeout, id); if (s != ESPTimerStatus::Invalid) return s; - s = getItemStatus(Type::Interval, id); if (s != ESPTimerStatus::Invalid) return s; - s = getItemStatus(Type::Sec, id); if (s != ESPTimerStatus::Invalid) return s; - s = getItemStatus(Type::Ms, id); if (s != ESPTimerStatus::Invalid) return s; - s = getItemStatus(Type::Min, id); if (s != ESPTimerStatus::Invalid) return s; - return ESPTimerStatus::Invalid; + // Search across all types; first hit wins + ESPTimerStatus s; + s = getItemStatus(Type::Timeout, id); + if (s != ESPTimerStatus::Invalid) + return s; + s = getItemStatus(Type::Interval, id); + if (s != ESPTimerStatus::Invalid) + return s; + s = getItemStatus(Type::Sec, id); + if (s != ESPTimerStatus::Invalid) + return s; + s = getItemStatus(Type::Ms, id); + if (s != ESPTimerStatus::Invalid) + return s; + s = getItemStatus(Type::Min, id); + if (s != ESPTimerStatus::Invalid) + return s; + return ESPTimerStatus::Invalid; } // Task trampolines -void ESPTimer::timeoutTaskTrampoline(void* arg) { static_cast(arg)->timeoutTask(); } -void ESPTimer::intervalTaskTrampoline(void* arg) { static_cast(arg)->intervalTask(); } -void ESPTimer::secTaskTrampoline(void* arg) { static_cast(arg)->secTask(); } -void ESPTimer::msTaskTrampoline(void* arg) { static_cast(arg)->msTask(); } -void ESPTimer::minTaskTrampoline(void* arg) { static_cast(arg)->minTask(); } +void ESPTimer::timeoutTaskTrampoline(void *arg) { + static_cast(arg)->timeoutTask(); +} +void ESPTimer::intervalTaskTrampoline(void *arg) { + static_cast(arg)->intervalTask(); +} +void ESPTimer::secTaskTrampoline(void *arg) { + static_cast(arg)->secTask(); +} +void ESPTimer::msTaskTrampoline(void *arg) { + static_cast(arg)->msTask(); +} +void ESPTimer::minTaskTrampoline(void *arg) { + static_cast(arg)->minTask(); +} // Task loops void ESPTimer::timeoutTask() { - while (running_.load(std::memory_order_acquire)) { - const uint32_t now = millis(); - TimerVector> toCall{TimerAllocator>(usePSRAMBuffers_)}; - - lock(); - toCall.reserve(timeouts_.size()); - // Collect callbacks due and remove completed - auto it = timeouts_.begin(); - while (it != timeouts_.end()) { - if (it->status == ESPTimerStatus::Running && now >= it->dueAtMs) { - toCall.push_back(it->cb); - it = timeouts_.erase(it); - } else if (it->status == ESPTimerStatus::Stopped || it->status == ESPTimerStatus::Completed) { - it = timeouts_.erase(it); - } else { - ++it; - } - } - unlock(); - - for (auto& fn : toCall) { - if (fn) fn(); - } - - vTaskDelay(pdMS_TO_TICKS(1)); - } - hTimeout_ = nullptr; - vTaskDelete(nullptr); + while (running_.load(std::memory_order_acquire)) { + const uint32_t now = millis(); + TimerVector> toCall{ + TimerAllocator>(usePSRAMBuffers_) + }; + + lock(); + toCall.reserve(timeouts_.size()); + // Collect callbacks due and remove completed + auto it = timeouts_.begin(); + while (it != timeouts_.end()) { + if (it->status == ESPTimerStatus::Running && now >= it->dueAtMs) { + toCall.push_back(it->cb); + it = timeouts_.erase(it); + } else if (it->status == ESPTimerStatus::Stopped || + it->status == ESPTimerStatus::Completed) { + it = timeouts_.erase(it); + } else { + ++it; + } + } + unlock(); + + for (auto &fn : toCall) { + if (fn) + fn(); + } + + vTaskDelay(pdMS_TO_TICKS(1)); + } + hTimeout_ = nullptr; + vTaskDelete(nullptr); } void ESPTimer::intervalTask() { - while (running_.load(std::memory_order_acquire)) { - const uint32_t now = millis(); - TimerVector> toCall{TimerAllocator>(usePSRAMBuffers_)}; - - lock(); - toCall.reserve(intervals_.size()); - for (auto& it : intervals_) { - if (it.status == ESPTimerStatus::Running) { - if (now - it.lastFireMs >= it.periodMs) { - it.lastFireMs = now; - toCall.push_back(it.cb); - } - } - } - // Erase stopped ones - intervals_.erase(std::remove_if(intervals_.begin(), intervals_.end(), [](const IntervalItem& n) { - return n.status == ESPTimerStatus::Stopped || n.status == ESPTimerStatus::Completed; - }), - intervals_.end()); - unlock(); - - for (auto& fn : toCall) { - if (fn) fn(); - } - - vTaskDelay(pdMS_TO_TICKS(1)); - } - hInterval_ = nullptr; - vTaskDelete(nullptr); + while (running_.load(std::memory_order_acquire)) { + const uint32_t now = millis(); + TimerVector> toCall{ + TimerAllocator>(usePSRAMBuffers_) + }; + + lock(); + toCall.reserve(intervals_.size()); + for (auto &it : intervals_) { + if (it.status == ESPTimerStatus::Running) { + if (now - it.lastFireMs >= it.periodMs) { + it.lastFireMs = now; + toCall.push_back(it.cb); + } + } + } + // Erase stopped ones + intervals_.erase( + std::remove_if( + intervals_.begin(), + intervals_.end(), + [](const IntervalItem &n) { + return n.status == ESPTimerStatus::Stopped || + n.status == ESPTimerStatus::Completed; + } + ), + intervals_.end() + ); + unlock(); + + for (auto &fn : toCall) { + if (fn) + fn(); + } + + vTaskDelay(pdMS_TO_TICKS(1)); + } + hInterval_ = nullptr; + vTaskDelete(nullptr); } void ESPTimer::secTask() { - while (running_.load(std::memory_order_acquire)) { - const uint32_t now = millis(); - struct Call { std::function fn; int arg; }; - TimerVector toCall{TimerAllocator(usePSRAMBuffers_)}; - - lock(); - toCall.reserve(secs_.size()); - for (auto& it : secs_) { - if (it.status == ESPTimerStatus::Running) { - if (now - it.lastTickMs >= 1000) { - it.lastTickMs = now; - int secLeft = 0; - if (it.endAtMs > now) { - uint32_t remaining = it.endAtMs - now; - // Round up so we only report 0 when no time remains - secLeft = static_cast((static_cast(remaining) + 999) / 1000); - } - toCall.push_back({it.cb, secLeft}); - if (now >= it.endAtMs) { - it.status = ESPTimerStatus::Completed; - } - } - } - } - secs_.erase(std::remove_if(secs_.begin(), secs_.end(), [](const SecItem& n) { - return n.status == ESPTimerStatus::Stopped || n.status == ESPTimerStatus::Completed; - }), - secs_.end()); - unlock(); - - for (auto& c : toCall) { - if (c.fn) c.fn(c.arg); - } - - vTaskDelay(pdMS_TO_TICKS(10)); - } - hSec_ = nullptr; - vTaskDelete(nullptr); + while (running_.load(std::memory_order_acquire)) { + const uint32_t now = millis(); + struct Call { + std::function fn; + int arg; + }; + TimerVector toCall{TimerAllocator(usePSRAMBuffers_)}; + + lock(); + toCall.reserve(secs_.size()); + for (auto &it : secs_) { + if (it.status == ESPTimerStatus::Running) { + if (now - it.lastTickMs >= 1000) { + it.lastTickMs = now; + int secLeft = 0; + if (it.endAtMs > now) { + uint32_t remaining = it.endAtMs - now; + // Round up so we only report 0 when no time remains + secLeft = static_cast((static_cast(remaining) + 999) / 1000); + } + toCall.push_back({it.cb, secLeft}); + if (now >= it.endAtMs) { + it.status = ESPTimerStatus::Completed; + } + } + } + } + secs_.erase( + std::remove_if( + secs_.begin(), + secs_.end(), + [](const SecItem &n) { + return n.status == ESPTimerStatus::Stopped || + n.status == ESPTimerStatus::Completed; + } + ), + secs_.end() + ); + unlock(); + + for (auto &c : toCall) { + if (c.fn) + c.fn(c.arg); + } + + vTaskDelay(pdMS_TO_TICKS(10)); + } + hSec_ = nullptr; + vTaskDelete(nullptr); } void ESPTimer::msTask() { - while (running_.load(std::memory_order_acquire)) { - const uint32_t now = millis(); - struct Call { std::function fn; uint32_t arg; }; - TimerVector toCall{TimerAllocator(usePSRAMBuffers_)}; - - lock(); - toCall.reserve(mss_.size()); - for (auto& it : mss_) { - if (it.status == ESPTimerStatus::Running) { - // Fire at ~1ms cadence; on busy systems it may be coarser - if (now - it.lastTickMs >= 1) { - it.lastTickMs = now; - uint32_t msLeft = 0; - if (it.endAtMs > now) msLeft = it.endAtMs - now; - toCall.push_back({it.cb, msLeft}); - if (now >= it.endAtMs) { - it.status = ESPTimerStatus::Completed; - } - } - } - } - mss_.erase(std::remove_if(mss_.begin(), mss_.end(), [](const MsItem& n) { - return n.status == ESPTimerStatus::Stopped || n.status == ESPTimerStatus::Completed; - }), - mss_.end()); - unlock(); - - for (auto& c : toCall) { - if (c.fn) c.fn(c.arg); - } - - vTaskDelay(pdMS_TO_TICKS(1)); - } - hMs_ = nullptr; - vTaskDelete(nullptr); + while (running_.load(std::memory_order_acquire)) { + const uint32_t now = millis(); + struct Call { + std::function fn; + uint32_t arg; + }; + TimerVector toCall{TimerAllocator(usePSRAMBuffers_)}; + + lock(); + toCall.reserve(mss_.size()); + for (auto &it : mss_) { + if (it.status == ESPTimerStatus::Running) { + // Fire at ~1ms cadence; on busy systems it may be coarser + if (now - it.lastTickMs >= 1) { + it.lastTickMs = now; + uint32_t msLeft = 0; + if (it.endAtMs > now) + msLeft = it.endAtMs - now; + toCall.push_back({it.cb, msLeft}); + if (now >= it.endAtMs) { + it.status = ESPTimerStatus::Completed; + } + } + } + } + mss_.erase( + std::remove_if( + mss_.begin(), + mss_.end(), + [](const MsItem &n) { + return n.status == ESPTimerStatus::Stopped || + n.status == ESPTimerStatus::Completed; + } + ), + mss_.end() + ); + unlock(); + + for (auto &c : toCall) { + if (c.fn) + c.fn(c.arg); + } + + vTaskDelay(pdMS_TO_TICKS(1)); + } + hMs_ = nullptr; + vTaskDelete(nullptr); } void ESPTimer::minTask() { - while (running_.load(std::memory_order_acquire)) { - const uint32_t now = millis(); - struct Call { std::function fn; int arg; }; - TimerVector toCall{TimerAllocator(usePSRAMBuffers_)}; - - lock(); - toCall.reserve(mins_.size()); - for (auto& it : mins_) { - if (it.status == ESPTimerStatus::Running) { - if (now - it.lastTickMs >= 60000) { - it.lastTickMs = now; - int minLeft = 0; - if (it.endAtMs > now) { - uint32_t remaining = it.endAtMs - now; - // Round up partial minutes so counters don't drop straight to zero - minLeft = static_cast((static_cast(remaining) + 60000 - 1) / 60000); - } - toCall.push_back({it.cb, minLeft}); - if (now >= it.endAtMs) { - it.status = ESPTimerStatus::Completed; - } - } - } - } - mins_.erase(std::remove_if(mins_.begin(), mins_.end(), [](const MinItem& n) { - return n.status == ESPTimerStatus::Stopped || n.status == ESPTimerStatus::Completed; - }), - mins_.end()); - unlock(); - - for (auto& c : toCall) { - if (c.fn) c.fn(c.arg); - } - - vTaskDelay(pdMS_TO_TICKS(100)); - } - hMin_ = nullptr; - vTaskDelete(nullptr); + while (running_.load(std::memory_order_acquire)) { + const uint32_t now = millis(); + struct Call { + std::function fn; + int arg; + }; + TimerVector toCall{TimerAllocator(usePSRAMBuffers_)}; + + lock(); + toCall.reserve(mins_.size()); + for (auto &it : mins_) { + if (it.status == ESPTimerStatus::Running) { + if (now - it.lastTickMs >= 60000) { + it.lastTickMs = now; + int minLeft = 0; + if (it.endAtMs > now) { + uint32_t remaining = it.endAtMs - now; + // Round up partial minutes so counters don't drop straight to zero + minLeft = static_cast( + (static_cast(remaining) + 60000 - 1) / 60000 + ); + } + toCall.push_back({it.cb, minLeft}); + if (now >= it.endAtMs) { + it.status = ESPTimerStatus::Completed; + } + } + } + } + mins_.erase( + std::remove_if( + mins_.begin(), + mins_.end(), + [](const MinItem &n) { + return n.status == ESPTimerStatus::Stopped || + n.status == ESPTimerStatus::Completed; + } + ), + mins_.end() + ); + unlock(); + + for (auto &c : toCall) { + if (c.fn) + c.fn(c.arg); + } + + vTaskDelay(pdMS_TO_TICKS(100)); + } + hMin_ = nullptr; + vTaskDelete(nullptr); } diff --git a/src/esp_timer/timer.h b/src/esp_timer/timer.h index 0f2122f..4212b22 100644 --- a/src/esp_timer/timer.h +++ b/src/esp_timer/timer.h @@ -1,178 +1,175 @@ #pragma once +#include "timer_allocator.h" #include -#include -#include -#include #include #include -#include #include -#include "timer_allocator.h" +#include +#include +#include +#include // Public types -enum class ESPTimerStatus : uint8_t { - Invalid = 0, - Running, - Paused, - Stopped, - Completed -}; +enum class ESPTimerStatus : uint8_t { Invalid = 0, Running, Paused, Stopped, Completed }; struct ESPTimerConfig { - // Stack sizes per task type (bytes) - uint16_t stackSizeTimeout = 4096 * sizeof(StackType_t); - uint16_t stackSizeInterval = 4096 * sizeof(StackType_t); - uint16_t stackSizeSec = 4096 * sizeof(StackType_t); - uint16_t stackSizeMs = 4096 * sizeof(StackType_t); - uint16_t stackSizeMin = 4096 * sizeof(StackType_t); - - // Priorities per task - UBaseType_t priorityTimeout = 1; - UBaseType_t priorityInterval = 1; - UBaseType_t prioritySec = 1; - UBaseType_t priorityMs = 2; // default slightly higher as it wakes up more often - UBaseType_t priorityMin = 1; - - // Core affinity (-1 means no pin/any core) - int8_t coreTimeout = -1; - int8_t coreInterval = -1; - int8_t coreSec = -1; - int8_t coreMs = -1; - int8_t coreMin = -1; - - // Prefer PSRAM-backed buffers for timer-owned dynamic containers. - // Falls back to default heap automatically when unavailable. - bool usePSRAMBuffers = false; + // Stack sizes per task type (bytes) + uint16_t stackSizeTimeout = 4096 * sizeof(StackType_t); + uint16_t stackSizeInterval = 4096 * sizeof(StackType_t); + uint16_t stackSizeSec = 4096 * sizeof(StackType_t); + uint16_t stackSizeMs = 4096 * sizeof(StackType_t); + uint16_t stackSizeMin = 4096 * sizeof(StackType_t); + + // Priorities per task + UBaseType_t priorityTimeout = 1; + UBaseType_t priorityInterval = 1; + UBaseType_t prioritySec = 1; + UBaseType_t priorityMs = 2; // default slightly higher as it wakes up more often + UBaseType_t priorityMin = 1; + + // Core affinity (-1 means no pin/any core) + int8_t coreTimeout = -1; + int8_t coreInterval = -1; + int8_t coreSec = -1; + int8_t coreMs = -1; + int8_t coreMin = -1; + + // Prefer PSRAM-backed buffers for timer-owned dynamic containers. + // Falls back to default heap automatically when unavailable. + bool usePSRAMBuffers = false; }; class ESPTimer { - public: - ~ESPTimer(); - - void init(const ESPTimerConfig& cfg = ESPTimerConfig()); - void deinit(); - bool isInitialized() const { return initialized_; } - - // Scheduling - uint32_t setTimeout(std::function cb, uint32_t delayMs); - uint32_t setInterval(std::function cb, uint32_t periodMs); - uint32_t setSecCounter(std::function cb, uint32_t totalMs); - uint32_t setMsCounter(std::function cb, uint32_t totalMs); - uint32_t setMinCounter(std::function cb, uint32_t totalMs); - - // Pause: set status to Paused if currently Running; returns true on state change - bool pauseTimer(uint32_t id); - bool pauseInterval(uint32_t id); - bool pauseSecCounter(uint32_t id); - bool pauseMsCounter(uint32_t id); - bool pauseMinCounter(uint32_t id); - - // Resume: set status to Running if currently Paused; returns true on state change - bool resumeTimer(uint32_t id); - bool resumeInterval(uint32_t id); - bool resumeSecCounter(uint32_t id); - bool resumeMsCounter(uint32_t id); - bool resumeMinCounter(uint32_t id); - - // Toggle running status between Running <-> Paused; returns true if now Running - bool toggleRunStatusTimer(uint32_t id); - bool toggleRunStatusInterval(uint32_t id); - bool toggleRunStatusSecCounter(uint32_t id); - bool toggleRunStatusMsCounter(uint32_t id); - bool toggleRunStatusMinCounter(uint32_t id); - - // Clear (stop and remove) timers; returns true on success - bool clearTimeout(uint32_t id); - bool clearInterval(uint32_t id); - bool clearSecCounter(uint32_t id); - bool clearMsCounter(uint32_t id); - bool clearMinCounter(uint32_t id); - - // Status - ESPTimerStatus getStatus(uint32_t id); - - private: - enum class Type : uint8_t { Timeout, Interval, Sec, Ms, Min }; - - struct BaseItem { - uint32_t id = 0; - ESPTimerStatus status = ESPTimerStatus::Running; - Type type; - uint32_t createdMs = 0; - }; - - struct TimeoutItem : BaseItem { - std::function cb; - uint32_t dueAtMs = 0; - }; - - struct IntervalItem : BaseItem { - std::function cb; - uint32_t periodMs = 0; - uint32_t lastFireMs = 0; - }; - - struct SecItem : BaseItem { - std::function cb; - uint32_t endAtMs = 0; - uint32_t lastTickMs = 0; - }; - - struct MsItem : BaseItem { - std::function cb; - uint32_t endAtMs = 0; - uint32_t lastTickMs = 0; - }; - - struct MinItem : BaseItem { - std::function cb; - uint32_t endAtMs = 0; - uint32_t lastTickMs = 0; - }; - - // Storage per type - TimerVector timeouts_; - TimerVector intervals_; - TimerVector secs_; - TimerVector mss_; - TimerVector mins_; - - // FreeRTOS bits - SemaphoreHandle_t mutex_ = nullptr; - TaskHandle_t hTimeout_ = nullptr; - TaskHandle_t hInterval_ = nullptr; - TaskHandle_t hSec_ = nullptr; - TaskHandle_t hMs_ = nullptr; - TaskHandle_t hMin_ = nullptr; - - ESPTimerConfig cfg_{}; - bool initialized_ = false; - std::atomic running_{false}; - uint32_t nextId_ = 1; - bool usePSRAMBuffers_ = false; - - uint32_t nextId(); - void lock(); - void unlock(); - - // Task loops - static void timeoutTaskTrampoline(void* arg); - static void intervalTaskTrampoline(void* arg); - static void secTaskTrampoline(void* arg); - static void msTaskTrampoline(void* arg); - static void minTaskTrampoline(void* arg); - - void timeoutTask(); - void intervalTask(); - void secTask(); - void msTask(); - void minTask(); - - // Helpers - bool pauseItem(Type type, uint32_t id); - bool resumeItem(Type type, uint32_t id); - ESPTimerStatus togglePause(Type type, uint32_t id); // internal: returns new status or Invalid if not found - bool clearItem(Type type, uint32_t id); - ESPTimerStatus getItemStatus(Type type, uint32_t id); + public: + ~ESPTimer(); + + void init(const ESPTimerConfig &cfg = ESPTimerConfig()); + void deinit(); + bool isInitialized() const { + return initialized_; + } + + // Scheduling + uint32_t setTimeout(std::function cb, uint32_t delayMs); + uint32_t setInterval(std::function cb, uint32_t periodMs); + uint32_t setSecCounter(std::function cb, uint32_t totalMs); + uint32_t setMsCounter(std::function cb, uint32_t totalMs); + uint32_t setMinCounter(std::function cb, uint32_t totalMs); + + // Pause: set status to Paused if currently Running; returns true on state change + bool pauseTimer(uint32_t id); + bool pauseInterval(uint32_t id); + bool pauseSecCounter(uint32_t id); + bool pauseMsCounter(uint32_t id); + bool pauseMinCounter(uint32_t id); + + // Resume: set status to Running if currently Paused; returns true on state change + bool resumeTimer(uint32_t id); + bool resumeInterval(uint32_t id); + bool resumeSecCounter(uint32_t id); + bool resumeMsCounter(uint32_t id); + bool resumeMinCounter(uint32_t id); + + // Toggle running status between Running <-> Paused; returns true if now Running + bool toggleRunStatusTimer(uint32_t id); + bool toggleRunStatusInterval(uint32_t id); + bool toggleRunStatusSecCounter(uint32_t id); + bool toggleRunStatusMsCounter(uint32_t id); + bool toggleRunStatusMinCounter(uint32_t id); + + // Clear (stop and remove) timers; returns true on success + bool clearTimeout(uint32_t id); + bool clearInterval(uint32_t id); + bool clearSecCounter(uint32_t id); + bool clearMsCounter(uint32_t id); + bool clearMinCounter(uint32_t id); + + // Status + ESPTimerStatus getStatus(uint32_t id); + + private: + enum class Type : uint8_t { Timeout, Interval, Sec, Ms, Min }; + + struct BaseItem { + uint32_t id = 0; + ESPTimerStatus status = ESPTimerStatus::Running; + Type type; + uint32_t createdMs = 0; + }; + + struct TimeoutItem : BaseItem { + std::function cb; + uint32_t dueAtMs = 0; + }; + + struct IntervalItem : BaseItem { + std::function cb; + uint32_t periodMs = 0; + uint32_t lastFireMs = 0; + }; + + struct SecItem : BaseItem { + std::function cb; + uint32_t endAtMs = 0; + uint32_t lastTickMs = 0; + }; + + struct MsItem : BaseItem { + std::function cb; + uint32_t endAtMs = 0; + uint32_t lastTickMs = 0; + }; + + struct MinItem : BaseItem { + std::function cb; + uint32_t endAtMs = 0; + uint32_t lastTickMs = 0; + }; + + // Storage per type + TimerVector timeouts_; + TimerVector intervals_; + TimerVector secs_; + TimerVector mss_; + TimerVector mins_; + + // FreeRTOS bits + SemaphoreHandle_t mutex_ = nullptr; + TaskHandle_t hTimeout_ = nullptr; + TaskHandle_t hInterval_ = nullptr; + TaskHandle_t hSec_ = nullptr; + TaskHandle_t hMs_ = nullptr; + TaskHandle_t hMin_ = nullptr; + + ESPTimerConfig cfg_{}; + bool initialized_ = false; + std::atomic running_{false}; + uint32_t nextId_ = 1; + bool usePSRAMBuffers_ = false; + + uint32_t nextId(); + void lock(); + void unlock(); + + // Task loops + static void timeoutTaskTrampoline(void *arg); + static void intervalTaskTrampoline(void *arg); + static void secTaskTrampoline(void *arg); + static void msTaskTrampoline(void *arg); + static void minTaskTrampoline(void *arg); + + void timeoutTask(); + void intervalTask(); + void secTask(); + void msTask(); + void minTask(); + + // Helpers + bool pauseItem(Type type, uint32_t id); + bool resumeItem(Type type, uint32_t id); + ESPTimerStatus + togglePause(Type type, uint32_t id); // internal: returns new status or Invalid if not found + bool clearItem(Type type, uint32_t id); + ESPTimerStatus getItemStatus(Type type, uint32_t id); }; diff --git a/src/esp_timer/timer_allocator.h b/src/esp_timer/timer_allocator.h index 7527469..bd0adea 100644 --- a/src/esp_timer/timer_allocator.h +++ b/src/esp_timer/timer_allocator.h @@ -17,82 +17,80 @@ #include namespace timer_allocator_detail { -inline void* allocate(std::size_t bytes, bool usePSRAMBuffers) noexcept { +inline void *allocate(std::size_t bytes, bool usePSRAMBuffers) noexcept { #if ESP_TIMER_HAS_BUFFER_MANAGER - return ESPBufferManager::allocate(bytes, usePSRAMBuffers); + return ESPBufferManager::allocate(bytes, usePSRAMBuffers); #else - (void)usePSRAMBuffers; - return std::malloc(bytes); + (void)usePSRAMBuffers; + return std::malloc(bytes); #endif } -inline void deallocate(void* ptr) noexcept { +inline void deallocate(void *ptr) noexcept { #if ESP_TIMER_HAS_BUFFER_MANAGER - ESPBufferManager::deallocate(ptr); + ESPBufferManager::deallocate(ptr); #else - std::free(ptr); + std::free(ptr); #endif } -} // namespace timer_allocator_detail - -template -class TimerAllocator { - public: - using value_type = T; - - TimerAllocator() noexcept = default; - explicit TimerAllocator(bool usePSRAMBuffers) noexcept : usePSRAMBuffers_(usePSRAMBuffers) {} - - template - TimerAllocator(const TimerAllocator& other) noexcept : usePSRAMBuffers_(other.usePSRAMBuffers()) {} - - T* allocate(std::size_t n) { - if (n == 0) { - return nullptr; - } - if (n > (std::numeric_limits::max() / sizeof(T))) { +} // namespace timer_allocator_detail + +template class TimerAllocator { + public: + using value_type = T; + + TimerAllocator() noexcept = default; + explicit TimerAllocator(bool usePSRAMBuffers) noexcept : usePSRAMBuffers_(usePSRAMBuffers) { + } + + template + TimerAllocator(const TimerAllocator &other) noexcept + : usePSRAMBuffers_(other.usePSRAMBuffers()) { + } + + T *allocate(std::size_t n) { + if (n == 0) { + return nullptr; + } + if (n > (std::numeric_limits::max() / sizeof(T))) { #if defined(__cpp_exceptions) - throw std::bad_alloc(); + throw std::bad_alloc(); #else - std::abort(); + std::abort(); #endif - } + } - void* memory = timer_allocator_detail::allocate(n * sizeof(T), usePSRAMBuffers_); - if (memory == nullptr) { + void *memory = timer_allocator_detail::allocate(n * sizeof(T), usePSRAMBuffers_); + if (memory == nullptr) { #if defined(__cpp_exceptions) - throw std::bad_alloc(); + throw std::bad_alloc(); #else - std::abort(); + std::abort(); #endif - } - return static_cast(memory); - } + } + return static_cast(memory); + } - void deallocate(T* ptr, std::size_t) noexcept { - timer_allocator_detail::deallocate(ptr); - } + void deallocate(T *ptr, std::size_t) noexcept { + timer_allocator_detail::deallocate(ptr); + } - bool usePSRAMBuffers() const noexcept { - return usePSRAMBuffers_; - } + bool usePSRAMBuffers() const noexcept { + return usePSRAMBuffers_; + } - template - bool operator==(const TimerAllocator& other) const noexcept { - return usePSRAMBuffers_ == other.usePSRAMBuffers(); - } + template bool operator==(const TimerAllocator &other) const noexcept { + return usePSRAMBuffers_ == other.usePSRAMBuffers(); + } - template - bool operator!=(const TimerAllocator& other) const noexcept { - return !(*this == other); - } + template bool operator!=(const TimerAllocator &other) const noexcept { + return !(*this == other); + } - private: - template - friend class TimerAllocator; + private: + template friend class TimerAllocator; - bool usePSRAMBuffers_ = false; + bool usePSRAMBuffers_ = false; }; -template -using TimerVector = std::vector>; +template using TimerVector = std::vector>; diff --git a/test/test_basic/test_main.cpp b/test/test_basic/test_main.cpp index 3c3a9bd..e192c69 100644 --- a/test/test_basic/test_main.cpp +++ b/test/test_basic/test_main.cpp @@ -3,86 +3,87 @@ #include void test_api_compiles() { - ESPTimer timer; - ESPTimerConfig cfg; - cfg.usePSRAMBuffers = true; - timer.init(cfg); - TEST_ASSERT_TRUE(timer.isInitialized()); - - auto id1 = timer.setTimeout([]() {}, 1000); - auto id2 = timer.setInterval([]() {}, 20); - auto id3 = timer.setSecCounter([](int) {}, 1000); - auto id4 = timer.setMsCounter([](uint32_t) {}, 100); - auto id5 = timer.setMinCounter([](int) {}, 60000); - auto id6 = timer.setTimeout([]() {}, 1000); - - TEST_ASSERT_TRUE(id1 > 0); - TEST_ASSERT_TRUE(id2 > 0); - TEST_ASSERT_TRUE(id3 > 0); - TEST_ASSERT_TRUE(id4 > 0); - TEST_ASSERT_TRUE(id5 > 0); - TEST_ASSERT_TRUE(id6 > 0); - - // Pause then resume; both should return true if found and state changed - TEST_ASSERT_TRUE(timer.pauseInterval(id2)); - TEST_ASSERT_TRUE(timer.resumeInterval(id2)); - - // Toggle should return new running state - bool running = timer.toggleRunStatusInterval(id2); - TEST_ASSERT_FALSE(running); - running = timer.toggleRunStatusInterval(id2); - TEST_ASSERT_TRUE(running); - - TEST_ASSERT_TRUE(timer.clearTimeout(id1)); - TEST_ASSERT_TRUE(timer.clearTimeout(id6)); - - // Clear should return true once - TEST_ASSERT_TRUE(timer.clearInterval(id2)); - - timer.deinit(); - TEST_ASSERT_FALSE(timer.isInitialized()); + ESPTimer timer; + ESPTimerConfig cfg; + cfg.usePSRAMBuffers = true; + timer.init(cfg); + TEST_ASSERT_TRUE(timer.isInitialized()); + + auto id1 = timer.setTimeout([]() {}, 1000); + auto id2 = timer.setInterval([]() {}, 20); + auto id3 = timer.setSecCounter([](int) {}, 1000); + auto id4 = timer.setMsCounter([](uint32_t) {}, 100); + auto id5 = timer.setMinCounter([](int) {}, 60000); + auto id6 = timer.setTimeout([]() {}, 1000); + + TEST_ASSERT_TRUE(id1 > 0); + TEST_ASSERT_TRUE(id2 > 0); + TEST_ASSERT_TRUE(id3 > 0); + TEST_ASSERT_TRUE(id4 > 0); + TEST_ASSERT_TRUE(id5 > 0); + TEST_ASSERT_TRUE(id6 > 0); + + // Pause then resume; both should return true if found and state changed + TEST_ASSERT_TRUE(timer.pauseInterval(id2)); + TEST_ASSERT_TRUE(timer.resumeInterval(id2)); + + // Toggle should return new running state + bool running = timer.toggleRunStatusInterval(id2); + TEST_ASSERT_FALSE(running); + running = timer.toggleRunStatusInterval(id2); + TEST_ASSERT_TRUE(running); + + TEST_ASSERT_TRUE(timer.clearTimeout(id1)); + TEST_ASSERT_TRUE(timer.clearTimeout(id6)); + + // Clear should return true once + TEST_ASSERT_TRUE(timer.clearInterval(id2)); + + timer.deinit(); + TEST_ASSERT_FALSE(timer.isInitialized()); } void test_deinit_pre_init_is_safe_and_idempotent() { - ESPTimer timer; + ESPTimer timer; - TEST_ASSERT_FALSE(timer.isInitialized()); - timer.deinit(); - TEST_ASSERT_FALSE(timer.isInitialized()); - timer.deinit(); - TEST_ASSERT_FALSE(timer.isInitialized()); + TEST_ASSERT_FALSE(timer.isInitialized()); + timer.deinit(); + TEST_ASSERT_FALSE(timer.isInitialized()); + timer.deinit(); + TEST_ASSERT_FALSE(timer.isInitialized()); } void test_reinit_lifecycle() { - ESPTimer timer; - - timer.init(); - TEST_ASSERT_TRUE(timer.isInitialized()); - auto firstId = timer.setTimeout([]() {}, 5); - TEST_ASSERT_TRUE(firstId > 0); - - timer.deinit(); - TEST_ASSERT_FALSE(timer.isInitialized()); - timer.deinit(); - TEST_ASSERT_FALSE(timer.isInitialized()); - - timer.init(); - TEST_ASSERT_TRUE(timer.isInitialized()); - auto secondId = timer.setInterval([]() {}, 5); - TEST_ASSERT_TRUE(secondId > 0); - TEST_ASSERT_TRUE(timer.clearInterval(secondId)); - - timer.deinit(); - TEST_ASSERT_FALSE(timer.isInitialized()); + ESPTimer timer; + + timer.init(); + TEST_ASSERT_TRUE(timer.isInitialized()); + auto firstId = timer.setTimeout([]() {}, 5); + TEST_ASSERT_TRUE(firstId > 0); + + timer.deinit(); + TEST_ASSERT_FALSE(timer.isInitialized()); + timer.deinit(); + TEST_ASSERT_FALSE(timer.isInitialized()); + + timer.init(); + TEST_ASSERT_TRUE(timer.isInitialized()); + auto secondId = timer.setInterval([]() {}, 5); + TEST_ASSERT_TRUE(secondId > 0); + TEST_ASSERT_TRUE(timer.clearInterval(secondId)); + + timer.deinit(); + TEST_ASSERT_FALSE(timer.isInitialized()); } void setup() { - delay(2000); - UNITY_BEGIN(); - RUN_TEST(test_api_compiles); - RUN_TEST(test_deinit_pre_init_is_safe_and_idempotent); - RUN_TEST(test_reinit_lifecycle); - UNITY_END(); + delay(2000); + UNITY_BEGIN(); + RUN_TEST(test_api_compiles); + RUN_TEST(test_deinit_pre_init_is_safe_and_idempotent); + RUN_TEST(test_reinit_lifecycle); + UNITY_END(); } -void loop() {} +void loop() { +}