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//! 
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}