Skip to content

Commit

Permalink
feat(timer): Update Timer and HighResolutionTimer to have `start_…
Browse files Browse the repository at this point in the history
…watchdog()` and `stop_watchdog()` methods (#324)
  • Loading branch information
finger563 authored Aug 30, 2024
1 parent 3592f8f commit b56403e
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 118 deletions.
9 changes: 7 additions & 2 deletions components/task/include/task.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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();

Expand All @@ -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
Expand Down
90 changes: 90 additions & 0 deletions components/timer/example/main/timer_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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) {
Expand Down
137 changes: 27 additions & 110 deletions components/timer/include/high_resolution_timer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<HighResolutionTimer *>(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};
Expand Down
Loading

0 comments on commit b56403e

Please sign in to comment.