diff --git a/components/task/include/task.hpp b/components/task/include/task.hpp index 0ca322b59..9bfa990ea 100644 --- a/components/task/include/task.hpp +++ b/components/task/include/task.hpp @@ -227,12 +227,14 @@ class Task : public espp::BaseComponent { /** * @brief Start the task watchdog for this task. * @return true if the watchdog was started, false otherwise. + * @note This function is only available on ESP */ bool start_watchdog(); /** * @brief Stop the task watchdog for this task. * @return true if the watchdog was stopped, false otherwise. + * @note This function is only available on ESP */ bool stop_watchdog(); @@ -241,6 +243,7 @@ class Task : public espp::BaseComponent { * @param timeout_ms Timeout in milliseconds for the watchdog. * @param panic_on_timeout Whether or not to panic on timeout. * @return true if the watchdog was initialized, false otherwise. + * @note This function is only available on ESP * @note This function will not monitor the idle tasks. * @note If the watchdog has not been configured, then this function will call * `esp_task_wdt_init`, otherwise it will then call @@ -253,6 +256,7 @@ class Task : public espp::BaseComponent { * @param timeout The timeout for the watchdog. * @param panic_on_timeout Whether or not to panic on timeout. * @return true if the watchdog was initialized, false otherwise. + * @note This function is only available on ESP * @note This function will not monitor the idle tasks. * @note If the watchdog has not been configured, then this function will call * `esp_task_wdt_init`, otherwise it will then call @@ -267,6 +271,7 @@ class Task : public espp::BaseComponent { * @param ec Error code to set if there was an error retrieving the info. * @return std::string containing the task watchdog info, or an empty string * if there was no timeout or there was an error retrieving the info. + * @note This function is only available on ESP * @note This function will only return info for tasks which are still * registered with the watchdog. If you call this after you have called * stop_watchdog() for a task, then even if the task triggered the @@ -278,7 +283,7 @@ class Task : public espp::BaseComponent { * @brief Get the info (as a string) for the task of the current context. * @return std::string containing name, core ID, priority, and stack high * water mark (B) - * @note This function is only available on ESP32 + * @note This function is only available on ESP */ static std::string get_info(); @@ -287,7 +292,7 @@ class Task : public espp::BaseComponent { * @param task Reference to the task for which you want the information. * @return std::string containing name, core ID, priority, and stack high * water mark (B) - * @note This function is only available on ESP32 + * @note This function is only available on ESP */ static std::string get_info(const Task &task); #endif diff --git a/components/timer/example/main/timer_example.cpp b/components/timer/example/main/timer_example.cpp index 37a2092ec..05d9607be 100644 --- a/components/timer/example/main/timer_example.cpp +++ b/components/timer/example/main/timer_example.cpp @@ -51,6 +51,39 @@ extern "C" void app_main(void) { std::this_thread::sleep_for(num_seconds_to_run * 1s); } + // timer watchdog example + { + logger.info("Starting timer watchdog example"); + //! [timer watchdog example] + static constexpr bool panic_on_watchdog_timeout = false; + espp::Task::configure_task_watchdog(300ms, panic_on_watchdog_timeout); + auto timer_fn = []() { + static size_t iterations{0}; + fmt::print("[{:.3f}] #iterations = {}\n", elapsed(), iterations); + iterations++; + // we don't want to stop, so return false + return false; + }; + auto timer = espp::Timer({.name = "Timer 1", + .period = 500ms, + .callback = timer_fn, + .log_level = espp::Logger::Verbosity::DEBUG}); + timer.start_watchdog(); // start the watchdog timer for this timer + std::this_thread::sleep_for(500ms); + std::error_code ec; + std::string watchdog_info = espp::Task::get_watchdog_info(ec); + if (ec) { + fmt::print("Error getting watchdog info: {}\n", ec.message()); + } else if (!watchdog_info.empty()) { + fmt::print("Watchdog info: {}\n", watchdog_info); + } else { + fmt::print("No watchdog info available\n"); + } + // NOTE: the timer and the watchdog will both automatically get stopped when + // the task goes out of scope and is destroyed. + //! [timer watchdog example] + } + // timer with delay example { logger.info("Starting timer with delay example"); @@ -246,6 +279,63 @@ extern "C" void app_main(void) { std::this_thread::sleep_for(num_seconds_to_run * 1s); } + // high resolution timer watchdog example + { + logger.info("Starting high resolution timer watchdog example"); + //! [high resolution timer watchdog example] + logger.set_rate_limit(100ms); + auto timer_fn = [&]() { + static size_t iterations{0}; + iterations++; + logger.info_rate_limited("High resolution timer callback: {}", iterations); + // we don't want to stop, so return false + return false; + }; + auto high_resolution_timer = + espp::HighResolutionTimer({.name = "High Resolution Timer 1", + .callback = timer_fn, + .log_level = espp::Logger::Verbosity::DEBUG}); + uint64_t period_us = 100; + bool started = high_resolution_timer.start(period_us); + logger.info("High resolution timer 1 started: {}", started); + + // make another HighResolutionTimer + auto high_resolution_timer2 = + espp::HighResolutionTimer({.name = "High Resolution Timer 2", + .callback = timer_fn, + .log_level = espp::Logger::Verbosity::DEBUG}); + period_us = 1000 * 500; // 500ms, which is longer than the watchdog period + started = high_resolution_timer2.start(period_us); + logger.info("High resolution timer 2 started: {}", started); + + // configure the task watchdog + static constexpr bool panic_on_watchdog_timeout = false; + espp::Task::configure_task_watchdog(300ms, panic_on_watchdog_timeout); + + // start the watchdog timer for this timer + high_resolution_timer2.start_watchdog(); + + std::this_thread::sleep_for(550ms); + + std::error_code ec; + std::string watchdog_info = espp::Task::get_watchdog_info(ec); + if (ec) { + fmt::print("Error getting watchdog info: {}\n", ec.message()); + } else if (!watchdog_info.empty()) { + fmt::print("Watchdog info: {}\n", watchdog_info); + } else { + fmt::print("No watchdog info available\n"); + } + + // now stop the watchdog timer + high_resolution_timer2.stop_watchdog(); + + // delay some more so we can see the watchdog timer has stopped + std::this_thread::sleep_for(500ms); + + //! [high resolution timer watchdog example] + } + logger.info("Example complete!"); while (true) { diff --git a/components/timer/include/high_resolution_timer.hpp b/components/timer/include/high_resolution_timer.hpp index c5179e704..936a36a00 100644 --- a/components/timer/include/high_resolution_timer.hpp +++ b/components/timer/include/high_resolution_timer.hpp @@ -26,6 +26,8 @@ namespace espp { /// /// \section high_resolution_timer_ex1 High Resolution Timer Example /// \snippet timer_example.cpp high resolution timer example +/// \section high_resolution_timer_ex2 High Resolution Timer Watchdog Example +/// \snippet timer_example.cpp high resolution timer watchdog example class HighResolutionTimer : public espp::BaseComponent { public: /// Callback type for the timer @@ -44,156 +46,71 @@ class HighResolutionTimer : public espp::BaseComponent { /// Constructor /// @param config Configuration of the timer - explicit HighResolutionTimer(const Config &config) - : BaseComponent(config.name, config.log_level) - , skip_unhandled_events_(config.skip_unhandled_events) - , dispatch_method_(config.dispatch_method) - , callback_(config.callback) { - using namespace std::chrono_literals; - // set a default logger rate limit (can always be set later by caller) - set_log_rate_limit(100ms); - } + explicit HighResolutionTimer(const Config &config); /// Destructor - ~HighResolutionTimer() { - stop(); - if (timer_handle_) { - esp_timer_delete(timer_handle_); - timer_handle_ = nullptr; - } - } + ~HighResolutionTimer(); /// Start the timer /// @param period_us Period of the timer in microseconds, or timeout if /// oneshot is true /// @param oneshot True if the timer should be oneshot, false if periodic /// @return True if the timer was started successfully, false otherwise - bool start(uint64_t period_us = 0, bool oneshot = false) { - if (is_running()) { - stop(); - } - - // store whether the timer is oneshot or periodic - oneshot_ = oneshot; - - esp_err_t err = ESP_OK; - - if (timer_handle_ == nullptr) { - esp_timer_create_args_t timer_args; - timer_args.callback = timer_callback; - timer_args.arg = this; - timer_args.dispatch_method = dispatch_method_; - timer_args.name = get_name().c_str(); - timer_args.skip_unhandled_events = skip_unhandled_events_; - - err = esp_timer_create(&timer_args, &timer_handle_); - if (err != ESP_OK) { - logger_.error("Failed to create timer: {}", esp_err_to_name(err)); - return false; - } - } - - if (oneshot) { - err = esp_timer_start_once(timer_handle_, period_us); - } else { - err = esp_timer_start_periodic(timer_handle_, period_us); - } - - if (err != ESP_OK) { - logger_.error("Failed to start timer: {}", esp_err_to_name(err)); - return false; - } - return true; - } + bool start(uint64_t period_us = 0, bool oneshot = false); /// Start the timer in oneshot mode /// @param timeout_us Timeout of the timer in microseconds /// @return True if the timer was started successfully, false otherwise - bool oneshot(uint64_t timeout_us = 0) { return start(timeout_us, true); } + bool oneshot(uint64_t timeout_us = 0); /// Start the timer in periodic mode /// @param period_us Period of the timer in microseconds /// @return True if the timer was started successfully, false otherwise - bool periodic(uint64_t period_us = 0) { return start(period_us, false); } + bool periodic(uint64_t period_us = 0); /// Stop the timer - void stop() { - if (is_running()) { - esp_timer_stop(timer_handle_); - } - } + void stop(); + + /// Start the watchdog timer + /// @return True if the watchdog timer was started successfully, false + /// otherwise + bool start_watchdog(); + + /// Stop the watchdog timer + /// @return True if the watchdog timer was stopped successfully, false + /// otherwise + bool stop_watchdog(); /// Check if the timer is running /// @return True if the timer is running, false otherwise - bool is_running() const { return timer_handle_ && esp_timer_is_active(timer_handle_); } + bool is_running() const; /// Is the timer oneshot? /// @return True if the timer is a oneshot timer, false if it is perioic. - bool is_oneshot() const { return oneshot_; }; + bool is_oneshot() const; /// Is the timer periodic? /// @return True if the timer is a periodic timer, false if it is oneshot. - bool is_periodic() const { return !oneshot_; }; + bool is_periodic() const; /// Set the period of the timer in microseconds /// @param period_us Period of the timer in microseconds /// @note This function will restart the timer if it is running - void set_period(uint64_t period_us) { - esp_err_t err = ESP_OK; - if (is_running()) { - err = esp_timer_restart(timer_handle_, period_us); - } else if (timer_handle_) { - if (oneshot_) { - err = esp_timer_start_once(timer_handle_, period_us); - } else { - err = esp_timer_start_periodic(timer_handle_, period_us); - } - } else { - logger_.error("Cannot set period for timer, start() has not been called!"); - } - if (err != ESP_OK) { - logger_.error("Failed to set timer period: {}", esp_err_to_name(err)); - } - } + void set_period(uint64_t period_us); /// Get the period of the timer in microseconds /// @return Period of the timer in microseconds /// @note This function will return 0 if the timer is not running /// @note This function will return the period of the timer, not the /// remaining time - uint64_t get_period() { - uint64_t period_us = 0; - esp_err_t err = ESP_OK; - if (oneshot_) { - err = esp_timer_get_expiry_time(timer_handle_, &period_us); - } else { - err = esp_timer_get_period(timer_handle_, &period_us); - } - if (err != ESP_OK) { - logger_.error("Failed to get timer period: {}", esp_err_to_name(err)); - return 0; - } - logger_.debug("Timer period for {} timer: {} us", oneshot_.load() ? "oneshot" : "periodic", - period_us); - return period_us; - } + uint64_t get_period(); protected: - static void timer_callback(void *arg) { - auto timer = static_cast(arg); - if (!timer) { - return; - } - timer->handle_timer_callback(); - } - - void handle_timer_callback() { - logger_.debug_rate_limited("Timer expired, calling callback"); - if (callback_) { - callback_(); - } - } + static void timer_callback(void *arg); + + void handle_timer_callback(); + esp_task_wdt_user_handle_t wdt_handle_{nullptr}; bool skip_unhandled_events_{false}; esp_timer_dispatch_t dispatch_method_{ESP_TIMER_TASK}; esp_timer_handle_t timer_handle_{nullptr}; diff --git a/components/timer/include/timer.hpp b/components/timer/include/timer.hpp index 6b442cb0e..90030c151 100644 --- a/components/timer/include/timer.hpp +++ b/components/timer/include/timer.hpp @@ -37,17 +37,19 @@ namespace espp { /// /// \section timer_ex1 Timer Example 1 /// \snippet timer_example.cpp timer example -/// \section timer_ex2 Timer Delay Example +/// \section timer_ex2 Timer Watchdog Example +/// \snippet timer_example.cpp timer watchdog example +/// \section timer_ex3 Timer Delay Example /// \snippet timer_example.cpp timer delay example -/// \section timer_ex3 Oneshot Timer Example +/// \section timer_ex4 Oneshot Timer Example /// \snippet timer_example.cpp timer oneshot example -/// \section timer_ex4 Timer Cancel Itself Example +/// \section timer_ex5 Timer Cancel Itself Example /// \snippet timer_example.cpp timer cancel itself example -/// \section timer_ex5 Oneshot Timer Cancel Itself Then Start again with Delay Example +/// \section timer_ex6 Oneshot Timer Cancel Itself Then Start again with Delay Example /// \snippet timer_example.cpp timer oneshot restart example -/// \section timer_ex6 Timer Update Period Example +/// \section timer_ex7 Timer Update Period Example /// \snippet timer_example.cpp timer update period example -/// \section timer_ex7 Timer AdvancedConfig Example +/// \section timer_ex8 Timer AdvancedConfig Example /// \snippet timer_example.cpp timer advanced config example class Timer : public BaseComponent { public: @@ -116,6 +118,24 @@ class Timer : public BaseComponent { /// @details Cancels the timer. void cancel(); +#if defined(ESP_PLATFORM) || defined(_DOXYGEN_) + /// @brief Start the task watchdog for the timer. + /// @return true if the watchdog was started, false otherwise. + /// @note This function is only available on ESP + /// @see stop_watchdog() + /// @see Task::start_watchdog() + /// @see Task::stop_watchdog() + bool start_watchdog(); + + /// @brief Stop the task watchdog for the timer. + /// @return true if the watchdog was stopped, false otherwise. + /// @note This function is only available on ESP + /// @see start_watchdog() + /// @see Task::start_watchdog() + /// @see Task::stop_watchdog() + bool stop_watchdog(); +#endif + /// @brief Set the period of the timer. /// @details Sets the period of the timer. /// @param period The period of the timer. diff --git a/components/timer/src/high_resolution_timer.cpp b/components/timer/src/high_resolution_timer.cpp new file mode 100644 index 000000000..c4f560acb --- /dev/null +++ b/components/timer/src/high_resolution_timer.cpp @@ -0,0 +1,163 @@ +#include "high_resolution_timer.hpp" + +using namespace espp; + +HighResolutionTimer::HighResolutionTimer(const Config &config) + : BaseComponent(config.name, config.log_level) + , skip_unhandled_events_(config.skip_unhandled_events) + , dispatch_method_(config.dispatch_method) + , callback_(config.callback) { + using namespace std::chrono_literals; + // set a default logger rate limit (can always be set later by caller) + set_log_rate_limit(100ms); +} + +HighResolutionTimer::~HighResolutionTimer() { + stop_watchdog(); + stop(); + if (timer_handle_) { + esp_timer_delete(timer_handle_); + timer_handle_ = nullptr; + } +} + +bool HighResolutionTimer::start(uint64_t period_us, bool oneshot) { + if (is_running()) { + stop(); + } + + // store whether the timer is oneshot or periodic + oneshot_ = oneshot; + + esp_err_t err = ESP_OK; + + if (timer_handle_ == nullptr) { + esp_timer_create_args_t timer_args; + timer_args.callback = timer_callback; + timer_args.arg = this; + timer_args.dispatch_method = dispatch_method_; + timer_args.name = get_name().c_str(); + timer_args.skip_unhandled_events = skip_unhandled_events_; + + err = esp_timer_create(&timer_args, &timer_handle_); + if (err != ESP_OK) { + logger_.error("Failed to create timer: {}", esp_err_to_name(err)); + return false; + } + } + + if (oneshot) { + err = esp_timer_start_once(timer_handle_, period_us); + } else { + err = esp_timer_start_periodic(timer_handle_, period_us); + } + + if (err != ESP_OK) { + logger_.error("Failed to start timer: {}", esp_err_to_name(err)); + return false; + } + return true; +} + +bool HighResolutionTimer::start_watchdog() { + if (wdt_handle_) { + logger_.debug("Watchdog timer already running"); + return false; + } + if (!is_running()) { + logger_.error("Cannot start watchdog timer, timer is not running"); + return false; + } + auto err = esp_task_wdt_add_user(get_name().c_str(), &wdt_handle_); + if (err != ESP_OK) { + logger_.error("Failed to start watchdog timer: {}", esp_err_to_name(err)); + wdt_handle_ = nullptr; + return false; + } + return true; +} + +bool HighResolutionTimer::stop_watchdog() { + if (!wdt_handle_) { + logger_.debug("Watchdog timer not running"); + return false; + } + auto err = esp_task_wdt_delete_user(wdt_handle_); + if (err != ESP_OK) { + logger_.error("Failed to stop watchdog timer: {}", esp_err_to_name(err)); + return false; + } + wdt_handle_ = nullptr; + return true; +} + +bool HighResolutionTimer::oneshot(uint64_t timeout_us) { return start(timeout_us, true); } + +bool HighResolutionTimer::periodic(uint64_t period_us) { return start(period_us, false); } + +void HighResolutionTimer::stop() { + if (is_running()) { + esp_timer_stop(timer_handle_); + } +} + +bool HighResolutionTimer::is_running() const { + return timer_handle_ && esp_timer_is_active(timer_handle_); +} + +bool HighResolutionTimer::is_oneshot() const { return oneshot_; }; + +bool HighResolutionTimer::is_periodic() const { return !oneshot_; }; + +void HighResolutionTimer::set_period(uint64_t period_us) { + esp_err_t err = ESP_OK; + if (is_running()) { + err = esp_timer_restart(timer_handle_, period_us); + } else if (timer_handle_) { + if (oneshot_) { + err = esp_timer_start_once(timer_handle_, period_us); + } else { + err = esp_timer_start_periodic(timer_handle_, period_us); + } + } else { + logger_.error("Cannot set period for timer, start() has not been called!"); + } + if (err != ESP_OK) { + logger_.error("Failed to set timer period: {}", esp_err_to_name(err)); + } +} + +uint64_t HighResolutionTimer::get_period() { + uint64_t period_us = 0; + esp_err_t err = ESP_OK; + if (oneshot_) { + err = esp_timer_get_expiry_time(timer_handle_, &period_us); + } else { + err = esp_timer_get_period(timer_handle_, &period_us); + } + if (err != ESP_OK) { + logger_.error("Failed to get timer period: {}", esp_err_to_name(err)); + return 0; + } + logger_.debug("Timer period for {} timer: {} us", oneshot_.load() ? "oneshot" : "periodic", + period_us); + return period_us; +} + +void HighResolutionTimer::timer_callback(void *arg) { + auto timer = static_cast(arg); + if (!timer) { + return; + } + timer->handle_timer_callback(); +} + +void HighResolutionTimer::handle_timer_callback() { + logger_.debug_rate_limited("Timer expired, calling callback"); + if (callback_) { + callback_(); + } + if (wdt_handle_) { + esp_task_wdt_reset_user(wdt_handle_); + } +} diff --git a/components/timer/src/timer.cpp b/components/timer/src/timer.cpp index c57d620ae..d06ba901c 100644 --- a/components/timer/src/timer.cpp +++ b/components/timer/src/timer.cpp @@ -79,6 +79,12 @@ void Timer::cancel() { task_->stop(); } +#if defined(ESP_PLATFORM) +bool Timer::start_watchdog() { return task_->start_watchdog(); } + +bool Timer::stop_watchdog() { return task_->stop_watchdog(); } +#endif + void Timer::set_period(const std::chrono::duration &period) { if (period.count() < 0) { logger_.warn("period cannot be negative, not setting");