Skip to main content

embassy_task_watchdog/
lib.rs

1//! # embassy-task-watchdog
2//!
3//! A robust, flexible watchdog management library for embedded systems that
4//! multiplexes multiple task watchdogs into a single hardware watchdog timer,
5//! preventing system lockups when tasks fail to respond
6//!
7//! This crate provides a task registration pattern that monitors multiple
8//! tasks and ensures they are all still active, feeding the hardware
9//! watchdog only if all tasks are healthy.
10//!
11//!
12//! ![Multiplexed Task Diagram](https://raw.githubusercontent.com/piersfinlayson/task-watchdog/refs/heads/main/docs/images/multiplex.svg)
13//!
14//! ## Key Features
15//!
16//! - **Task Multiplexing**: Consolidates multiple independent task watchdogs
17//!   into a single hardware watchdog, triggering if any task fails to check in
18//! - **Static and Automated Task Management**: Tasks are registered at compile-time,
19//!   allowing hassle-free integration without dynamic memory allocation, and with
20//!   minimal boilerplate using the provided `#[task]` macro.  By default, the library
21//!   supports 32 watchdog tasks. The limit can be changed by setting the
22//!   `EMBASSY_TASK_WATCHDOG_MAX_TASKS` variable either in
23//!   [your `.cargo/config.toml`](https://github.com/sunipkm/embassy-task-watchdog/blob/master/examples/task-pico2/.cargo/config.toml), or by passing
24//!   it as an environment variable to cargo, e.g. `EMBASSY_TASK_WATCHDOG_MAX_TASKS=8 cargo build`.
25//!   The check is disabled in debug builds to prevent errors in IDEs, but exceeding the
26//!   number of tasks will trigger a compiler error in the release build.
27//! - **Async Support**: Works with asynchronous (Embassy) execution environments
28//! - **Configurable Timeouts**: Individual timeout durations for each
29//!   registered task
30//! - **`no_std` Compatible**: Designed for resource-constrained embedded
31//!   environments without an operating system
32//!
33//! ## Usage
34//!
35//! The following is a complete, minimal, example for using the task-watchdog
36//! crate using embassy-rs on an RP2040 or RP2350 (Pico or Pico 2).
37//! It uses static allocation (no alloc), and creates two tasks with
38//! different timeouts, both of which are policed by task-watchdog, and in
39//! turn, the hardware watchdog.
40//!
41//! ```rust
42//! # #![no_std]
43//! # #![no_main]
44//! # use defmt_rtt as _;
45//! # use embassy_executor::Spawner;
46//! # use embassy_rp::config::Config;
47//! # use embassy_task_watchdog::{
48//! #     WatchdogConfig, create_watchdog,
49//! #     embassy_rp::{TaskWatchdog, WatchdogRunner},
50//! # };
51//! # use embassy_time::{Duration, Timer};
52//! # use panic_probe as _;
53//! # use static_cell::StaticCell;
54//!
55//! #[embassy_executor::main]
56//! async fn main(spawner: Spawner) {
57//!     // Initialize the hardare peripherals
58//!     let p = embassy_rp::init(Config::default());
59//!     // Create the watchdog runner, store it in a static cell, and get the watchdog and watchdog runner task.
60//!     let (watchdog, watchdogtask) = create_watchdog!(p.WATCHDOG, config);
61//!     // Spawn tasks that will feed the watchdog
62//!     spawner.must_spawn(main_task(watchdog));
63//!     spawner.must_spawn(second_task(watchdog));
64//!     // Finally spawn the watchdog - this will start the hardware watchdog, and feed it
65//!     // for as long as _all_ tasks are healthy.
66//!     spawner.must_spawn(watchdog_task(watchdogtask));
67//! }
68//! // Provide a simple embassy task for the watchdog
69//! #[embassy_executor::task]
70//! async fn watchdog_task(watchdog: WatchdogRunner) -> ! {
71//!     watchdog.run().await
72//! }
73//! // Implement your main task
74//! #[embassy_task_watchdog::task(timeout = Duration::from_millis(1500))]
75//! async fn main_task(watchdog: TaskWatchdog) -> ! {
76//!     loop {
77//!         // Feed the watchdog
78//!         watchdog.feed().await;
79//!         // Do some work
80//!         Timer::after(Duration::from_millis(1000)).await;
81//!     }
82//! }
83//! // Implement your second task
84//! #[embassy_task_watchdog::task(timeout = Duration::from_millis(2000))]
85//! async fn second_task(watchdog: TaskWatchdog) -> ! {
86//!     loop {
87//!         // Feed the watchdog
88//!         watchdog.feed().await;
89//!         // Do some work
90//!         Timer::after(Duration::from_millis(2000)).await;
91//!     }
92//! }
93//! ```
94//! See the [examples](https://github.com/sunipkm/embassy-task-watchdog/tree/master/examples)
95//! for more usage examples.
96//!
97//! ## Targets
98//!
99//! For embedded devices you need to install and specify your target when
100//! building.  Use:
101//! - RP2040 - `thumbv6m-none-eabi`
102//! - RP2350 - `thumbv8m.main-none-eabihf`
103//!
104//! ## Feature Flags
105//!
106//! The following feature flags are supported
107//!
108//! - `rp`: Enable the Raspberry Pi MCU-specific embassy implementation
109//! - `defmt-embassy-rp`: Enable logging with defmt for the RP2040 and RP2350 embassy
110//! - `stm32`: Enable the STM32 MCU-specific embassy implementation
111//! - `defmt-embassy-stm32`: Enable logging with defmt for the STM32 embassy
112//! - `defmt`: Enable [`defmt`] logging of associated structs and enums.
113//! - `defmt-messages`: Enable `defmt` logging of events and errors in the library.
114//!
115//! ### Example Feature/Target combination
116//!
117//! This builds the library for RP2040 with embassy and defmt support:
118//!
119//! ```bash
120//! cargo build --features rp,defmt-embassy-rp --target thumbv6m-none-eabi
121//! ```
122//! #### Note
123//! It is recommended to build the project and run it by writing the build configuration
124//! in `.cargo/config.toml`, and executing `cargo build` without any additional
125//! arguments.
126//!
127//! ### Inspiration
128//! This work is inspired heavily by the `task-watchdog` crate by Piers Finlayson, which provides
129//! a similar task multiplexing watchdog for embedded systems. It has not been maintained in almost
130//! a year (last commit was on April 10, 2025). This crate is a fork of that work, with the following
131//! goals:
132//! - Update the codebase to be compatible with the latest versions of Rust and Embassy, and to
133//!   use modern Rust features and idioms.
134//! - Automate the task registration process with a procedural macro, to reduce boilerplate and
135//!   make it easier to use.
136//! - Get rid of custom task identifier types through the `task_watchdog::Id` trait.
137//!
138//! To achieve these goals, the codebase has been refactored and the scope has been limited to
139//! embassy-based async applications, which is the primary use case for this crate.  The API has
140//! been redesigned to be more ergonomic and easier to use, while still providing the same core
141//! functionality of multiplexing multiple task watchdogs into a single hardware watchdog timer.
142//!
143// Copyright (c) 2026 Sunip K. Mukherjee <sunipkmukherjee@gmail.com>
144//
145// Apache 2.0 or MIT licensed, at your option.
146
147#![no_std]
148#![warn(missing_docs)]
149#![cfg_attr(docsrs, feature(doc_cfg))]
150
151mod runtime;
152use embassy_time::Duration;
153
154pub use embassy_task_watchdog_macros::task;
155
156#[cfg(feature = "defmt-messages")]
157#[allow(unused_imports)]
158use defmt::{debug, error, info, trace, warn};
159
160// A replacement for the defmt logging macros, when defmt is not provided
161#[cfg(not(feature = "defmt-messages"))]
162mod log_impl {
163    #![allow(unused_macros)]
164    #![allow(unused_imports)]
165    // Macros are defined as _ to avoid conflicts with built-in attribute
166    // names
167    macro_rules! _trace {
168        ($($arg:tt)*) => {};
169    }
170    macro_rules! _debug {
171        ($($arg:tt)*) => {};
172    }
173    macro_rules! _info {
174        ($($arg:tt)*) => {};
175    }
176    macro_rules! _warn {
177        ($($arg:tt)*) => {};
178    }
179    macro_rules! _error {
180        ($($arg:tt)*) => {};
181    }
182    pub(crate) use _debug as debug;
183    pub(crate) use _error as error;
184    pub(crate) use _info as info;
185    pub(crate) use _trace as trace;
186    pub(crate) use _warn as warn;
187}
188#[cfg(not(feature = "defmt-messages"))]
189use log_impl::*;
190
191pub(crate) use embassy_task_watchdog_numtasks::MAX_TASKS;
192
193/// Represents a hardware-level watchdog that can be fed and reset the system.
194pub trait HardwareWatchdog {
195    /// Start the hardware watchdog with the given timeout.
196    fn start(&mut self, timeout: Duration);
197
198    /// Feed the hardware watchdog to prevent a system reset.
199    fn feed(&mut self);
200
201    /// Trigger a hardware reset.
202    fn trigger_reset(&mut self, reason: Option<heapless::String<32>>) -> !;
203
204    /// Get the reason for the last reset, if available.
205    fn reset_reason(&mut self) -> ResetReason;
206}
207
208/// Represents the reason for a system reset.
209#[derive(Debug, Clone, PartialEq, Eq)]
210#[cfg_attr(feature = "defmt", derive(defmt::Format))]
211pub enum ResetReason {
212    /// Reset was forced by software.
213    Forced(heapless::String<32>),
214
215    /// Reset was caused by watchdog timeout.
216    TimedOut,
217
218    /// Reset was caused by an unknown reason.
219    Unknown,
220
221    /// No reset has occurred since the last time the reason was cleared.
222    None,
223}
224
225/// Configuration for the watchdog.
226#[derive(Debug)]
227#[cfg_attr(feature = "defmt", derive(defmt::Format))]
228pub struct WatchdogConfig {
229    /// Timeout to start the hardware watchdog with.
230    pub(crate) hardware_timeout: Duration,
231
232    /// Interval at which to check if tasks have fed the watchdog.  Must be
233    /// less than the hardware timeout, or the hardware watchdog will reset
234    /// the system, before the task-watchdog has a chance to check tasks and
235    /// feed it.
236    pub(crate) check_interval: Duration,
237}
238
239impl WatchdogConfig {
240    /// Create a new configuration with specified timeout values
241    pub fn new(hardware_timeout: Duration, check_interval: Duration) -> Self {
242        Self {
243            hardware_timeout,
244            check_interval,
245        }
246    }
247
248    /// Create a default configuration with standard timeout values:
249    /// - Hardware timeout: 5000ms
250    /// - Check interval: 1000ms
251    fn default() -> Self {
252        Self::new(Duration::from_millis(5000), Duration::from_millis(1000))
253    }
254}
255
256impl Default for WatchdogConfig {
257    /// Create a default configuration with standard timeout values:
258    /// - Hardware timeout: 5000ms
259    /// - Check interval: 1000ms
260    fn default() -> Self {
261        Self::default()
262    }
263}
264
265mod impl_macro;
266
267/// An async implementation of embassy-task-watchdog for use with the RP2040 and RP2350
268/// embassy implementations.
269///
270/// This module requires the `rp` feature flag to be enabled.
271///
272/// The main entrypoint into this module is the [`create_watchdog`] macro, which returns
273/// the [`embassy_rp::TaskWatchdog`] passed to the tasks, and the [`embassy_rp::WatchdogRunner`].
274/// The [`embassy_rp::WatchdogRunner`] is awaited in a spawned [`embassy_executor::task`] to
275/// monitor the tasks and feed the hardware watchdog.
276/// See the documentation for that macro for more details and an example.
277///
278/// There is an equivalent `embassy_stm32` module for STM32, enabled by
279/// the `stm32` feature flag.
280#[cfg(feature = "rp")]
281#[cfg_attr(docsrs, doc(cfg(feature = "rp")))]
282pub mod embassy_rp;
283
284/// An async implementation of embassy-task-watchdog for use with the STM32
285/// embassy implementations.
286///
287/// This module requires the `stm32` feature flag to be enabled.
288///
289/// The main entrypoint into this module is the [`create_watchdog`] macro, which returns
290/// the [`embassy_stm32::TaskWatchdog`] passed to the tasks, and the [`embassy_stm32::WatchdogRunner`].
291/// The [`embassy_stm32::WatchdogRunner`] is awaited in a spawned [`embassy_executor::task`] to
292/// monitor the tasks and feed the hardware watchdog.
293/// See the documentation for that macro for more details and an example.
294///
295/// There is an equivalent `embassy_rp` module for RP2040 and RP2350, enabled by
296/// the `rp` feature flag.
297#[cfg(feature = "stm32")]
298#[cfg_attr(docsrs, doc(cfg(feature = "stm32")))]
299pub mod embassy_stm32;
300
301/// Initialize the static memory for the watchdog, and return the watchdog and
302/// the watchdog runner task. Pass the [`TaskWatchdog` struct](https://docs.rs/embassy-task-watchdog/latest/embassy_task_watchdog/embassy_rp/struct.RpTaskWatchdog.html)
303/// to your tasks to be able to feed the watchdog. Execute
304/// [`WatchdogRunner::run` function](https://docs.rs/embassy-task-watchdog/latest/embassy_task_watchdog/embassy_rp/struct.RpWatchdogRunner.html)
305/// inside a spawned task to monitor the tasks and feed the hardware watchdog.
306#[cfg(all(feature = "rp", not(feature = "stm32")))]
307#[macro_export]
308macro_rules! create_watchdog {
309    ($wdt: expr, $config: expr) => {{
310        use $crate::embassy_rp::Watchdog;
311        // Create a static to hold the task-watchdog object, so it has static
312        // lifetime and can be shared with tasks.
313        static WATCHDOG: static_cell::StaticCell<Watchdog> = static_cell::StaticCell::new();
314        // Create the watchdog runner and store it in the static cell
315        let watchdog = Watchdog::new($wdt, $config);
316        WATCHDOG.init(watchdog).build()
317    }};
318}
319
320#[macro_export]
321#[cfg(all(feature = "stm32", not(feature = "rp")))]
322/// Initialize the static memory for the watchdog, and return the watchdog and
323/// the watchdog runner task. Pass the [`TaskWatchdog` struct](https://docs.rs/embassy-task-watchdog/latest/embassy_task_watchdog/embassy_stm32/struct.Stm32TaskWatchdog.html)
324/// to your tasks to be able to feed the watchdog. Execute
325/// [`WatchdogRunner::run` function](https://docs.rs/embassy-task-watchdog/latest/embassy_task_watchdog/embassy_stm32/struct.Stm32WatchdogRunner.html)
326/// inside a spawned task to monitor the tasks and feed the hardware watchdog.
327macro_rules! create_watchdog {
328    ($wdt: expr, $config: expr) => {{
329        use $crate::embassy_stm32::Watchdog;
330        // Create a static to hold the task-watchdog object, so it has static
331        // lifetime and can be shared with tasks.
332        static WATCHDOG: static_cell::StaticCell<Watchdog> = static_cell::StaticCell::new();
333        // Create the watchdog runner and store it in the static cell
334        let watchdog = Watchdog::new($wdt, $config);
335        WATCHDOG.init(watchdog).build()
336    }};
337}
338
339#[cfg(all(feature = "stm32", feature = "rp"))]
340#[macro_export]
341/// Initialize the static memory for the watchdog, and return the watchdog and
342/// the watchdog runner task. Pass the [`TaskWatchdog` struct](https://docs.rs/embassy-task-watchdog/latest/embassy_task_watchdog/embassy_rp/struct.RpTaskWatchdog.html)
343/// to your tasks to be able to feed the watchdog. Execute the
344/// [`WatchdogRunner::run` function](https://docs.rs/embassy-task-watchdog/latest/embassy_task_watchdog/embassy_rp/struct.RpWatchdogRunner.html)
345/// inside a spawned task to monitor the tasks and feed the hardware watchdog.
346macro_rules! create_watchdog {
347    ($wdt: expr, $config: expr) => {
348        compile_error!("Cannot use create_watchdog macro with both rp and stm32 features enabled. Please choose one or the other.")
349    };
350}