typed_fsm/lib.rs
1//! # typed-fsm: Event-Driven Finite State Machine Microframework
2//!
3//! A lightweight, zero-cost, **event-driven** FSM generator for Rust with **ISR and concurrency support**.
4//! Designed for embedded systems (no-std compatible) and high-performance applications.
5//!
6//! ## Features
7//!
8//! - **Event-Driven Architecture** - Built from the ground up for event-based systems
9//! - **ISR-Safe Dispatch** - Call `dispatch()` from interrupt service routines (optional `concurrent` feature)
10//! - **Thread-Safe Concurrency** - Safe concurrent access from multiple threads with atomic protection
11//! - **Zero-cost abstraction** - Compiles to efficient jump tables with no runtime overhead
12//! - **Type-safe** - Compile-time validation of state transitions and events
13//! - **No allocations** - Uses enums and static dispatch (no `Box`, `dyn`, or heap)
14//! - **Embedded-ready** - `#![no_std]` compatible with zero dependencies by default
15//! - **Stateful states** - States can carry typed data
16//! - **Lifecycle hooks** - `entry`, `process`, and `exit` actions per state
17//!
18//! ## Quick Start: Simplest Example (Blink)
19//!
20//! The simplest state machine alternates between two states:
21//!
22//! ```rust
23//! use typed_fsm::{state_machine, Transition};
24//!
25//! // Context: Shared state across all states
26//! struct LedContext {
27//! tick_count: u32,
28//! }
29//!
30//! // Event: Simple tick event
31//! #[derive(Debug, Clone)]
32//! enum Event {
33//! Tick,
34//! }
35//!
36//! // State machine with two states: On and Off
37//! state_machine! {
38//! Name: BlinkFSM,
39//! Context: LedContext,
40//! Event: Event,
41//!
42//! States: {
43//! On => {
44//! entry: |ctx| {
45//! ctx.tick_count += 1;
46//! }
47//!
48//! process: |_ctx, event| {
49//! match event {
50//! Event::Tick => Transition::To(BlinkFSM::Off),
51//! }
52//! }
53//! },
54//!
55//! Off => {
56//! entry: |ctx| {
57//! ctx.tick_count += 1;
58//! }
59//!
60//! process: |_ctx, event| {
61//! match event {
62//! Event::Tick => Transition::To(BlinkFSM::On),
63//! }
64//! }
65//! }
66//! }
67//! }
68//!
69//! // Usage
70//! let mut ctx = LedContext { tick_count: 0 };
71//! let mut led = BlinkFSM::On;
72//!
73//! // ⚠️ CRITICAL: Must call init() before event loop!
74//! led.init(&mut ctx);
75//!
76//! // Dispatch events
77//! led.dispatch(&mut ctx, &Event::Tick); // On → Off
78//! led.dispatch(&mut ctx, &Event::Tick); // Off → On
79//! assert_eq!(ctx.tick_count, 3); // Initial entry + 2 transitions
80//! ```
81//!
82//! ## More Complex Example: Light with Brightness
83//!
84//! States can handle multiple events and modify context:
85//!
86//! ```rust
87//! # use typed_fsm::{state_machine, Transition};
88//! // Define your context (shared state)
89//! struct LightContext {
90//! brightness: u8,
91//! }
92//!
93//! // Define your events
94//! #[derive(Debug, Clone)]
95//! enum LightEvent {
96//! TurnOn,
97//! TurnOff,
98//! }
99//!
100//! // Create your state machine
101//! state_machine! {
102//! Name: LightFSM,
103//! Context: LightContext,
104//! Event: LightEvent,
105//!
106//! States: {
107//! Off => {
108//! entry: |ctx| {
109//! ctx.brightness = 0;
110//! }
111//!
112//! process: |_ctx, evt| {
113//! match evt {
114//! LightEvent::TurnOn => Transition::To(LightFSM::On),
115//! _ => Transition::None
116//! }
117//! }
118//! },
119//!
120//! On => {
121//! entry: |ctx| {
122//! ctx.brightness = 100;
123//! }
124//!
125//! process: |_ctx, evt| {
126//! match evt {
127//! LightEvent::TurnOff => Transition::To(LightFSM::Off),
128//! _ => Transition::None
129//! }
130//! }
131//! }
132//! }
133//! }
134//!
135//! // Use the state machine
136//! let mut ctx = LightContext { brightness: 0 };
137//! let mut fsm = LightFSM::Off;
138//!
139//! // ⚠️ CRITICAL: Always call init() before dispatching events!
140//! fsm.init(&mut ctx);
141//!
142//! fsm.dispatch(&mut ctx, &LightEvent::TurnOn);
143//! assert_eq!(ctx.brightness, 100);
144//! ```
145//!
146//! ## Understanding Transitions
147//!
148//! The `process` hook **must return** a `Transition` enum to tell the state machine what to do:
149//!
150//! ### `Transition::None` - Stay in Current State
151//!
152//! Use when an event should be handled but doesn't change the state:
153//!
154//! ```rust
155//! # use typed_fsm::{state_machine, Transition};
156//! # struct Context { data: u32 }
157//! # #[derive(Debug, Clone)]
158//! # enum Event { Update(u32), Ignore }
159//! # state_machine! {
160//! # Name: FSM,
161//! # Context: Context,
162//! # Event: Event,
163//! # States: {
164//! # Active => {
165//! process: |ctx, evt| {
166//! match evt {
167//! Event::Update(value) => {
168//! ctx.data = *value; // Update context
169//! Transition::None // Stay in same state
170//! },
171//! Event::Ignore => Transition::None
172//! }
173//! }
174//! # }
175//! # }
176//! # }
177//! ```
178//!
179//! **When `Transition::None` is returned:**
180//! - ✅ `process` executes
181//! - ❌ `exit` does NOT execute (no state change)
182//! - ❌ `entry` does NOT execute (no state change)
183//! - ✅ State remains unchanged
184//!
185//! ### `Transition::To(State)` - Move to New State
186//!
187//! Use when an event should trigger a state change:
188//!
189//! ```rust
190//! # use typed_fsm::{state_machine, Transition};
191//! # struct Context { }
192//! # #[derive(Debug, Clone)]
193//! # enum Event { Start, Stop }
194//! # state_machine! {
195//! # Name: Machine,
196//! # Context: Context,
197//! # Event: Event,
198//! # States: {
199//! # Idle => {
200//! process: |ctx, evt| {
201//! match evt {
202//! Event::Start => {
203//! Transition::To(Machine::Running { speed: 100 })
204//! },
205//! Event::Stop => Transition::None
206//! }
207//! }
208//! # },
209//! # Running { speed: u32 } => {
210//! # process: |ctx, evt| { Transition::None }
211//! # }
212//! # }
213//! # }
214//! ```
215//!
216//! **When `Transition::To(State)` is returned:**
217//! 1. ✅ `process` executes and returns new state
218//! 2. ✅ Current state's `exit` executes (if defined)
219//! 3. ✅ New state's `entry` executes (if defined)
220//! 4. ✅ State updates to the new state
221//!
222//! **Key Points:**
223//! - Every `process` block **must** return a `Transition`
224//! - Use `Transition::None` for events that don't change state
225//! - Use `Transition::To(State)` for events that trigger transitions
226//! - You can update context in `process` before returning
227//! - The transition type determines whether `exit`/`entry` hooks run
228//!
229//! ## Thread Safety and Concurrency
230//!
231//! FSMs are automatically `Send + Sync` if their fields are `Send + Sync`.
232//! This enables safe concurrent usage through Rust's standard concurrency primitives.
233//!
234//! ### Arc<Mutex<>> Pattern
235//!
236//! ```rust,no_run
237//! # use typed_fsm::{state_machine, Transition};
238//! # use std::sync::{Arc, Mutex};
239//! # use std::thread;
240//! # struct Context { }
241//! # #[derive(Debug, Clone)]
242//! # enum Event { Tick }
243//! # state_machine! {
244//! # Name: FSM,
245//! # Context: Context,
246//! # Event: Event,
247//! # States: {
248//! # Active => {
249//! # process: |ctx, evt| { Transition::None }
250//! # }
251//! # }
252//! # }
253//! let fsm = Arc::new(Mutex::new(FSM::Active));
254//! let ctx = Arc::new(Mutex::new(Context { }));
255//!
256//! let fsm_clone = Arc::clone(&fsm);
257//! let ctx_clone = Arc::clone(&ctx);
258//!
259//! thread::spawn(move || {
260//! let mut fsm = fsm_clone.lock().unwrap();
261//! let mut ctx = ctx_clone.lock().unwrap();
262//! fsm.dispatch(&mut *ctx, &Event::Tick);
263//! });
264//! ```
265//!
266//! See `examples/traffic_intersection.rs` for a complete concurrent FSM example.
267//!
268//! **Note:** The core framework is `#![no_std]` compatible. Concurrency examples
269//! use std, but FSMs work in no_std environments with alternatives like `spin::Mutex`.
270//!
271//! ## ISR and Multithreading Safety (Feature: `concurrent`)
272//!
273//! For **interrupt service routines (ISRs)** and true **concurrent multithreading**,
274//! enable the optional `concurrent` feature. This adds protection against re-entrant
275//! dispatch calls using atomic operations and lock-free queues.
276//!
277//! This feature supports all architectures (including AVR and ARM Cortex-M) by automatically
278//! adapting to the target platform via the `portable-atomic` crate.
279//!
280//! ### When to Use
281//!
282//! Enable `concurrent` when:
283//! - **ISRs call `dispatch()`**: Interrupt handlers need to generate events
284//! - **Multiple threads call `dispatch()`**: Concurrent access from different threads
285//! - **ISRs + Threads**: Combined scenario (e.g., RTOS environments)
286//!
287//! ### How It Works
288//!
289//! 1. **Immediate execution**: If no dispatch is active, executes immediately
290//! 2. **Queue if busy**: If dispatch is already active, event is queued (capacity: 16 events)
291//! 3. **FIFO processing**: Queued events are processed in order before releasing lock
292//! 4. **Atomic protection**: Uses `portable_atomic::AtomicBool` with compare-exchange and `critical_section::Mutex`
293//!
294//! ### Requirements
295//!
296//! - **Event type must be `Clone`**: Events are cloned when enqueued
297//! - **critical-section implementation**: Requires a `critical-section` provider for your platform
298//! - For `std`: Use `critical-section = { version = "1.1", features = ["std"] }`
299//! - For embedded: Use your HAL's critical-section implementation
300//!
301//! ### Important Limitations
302//!
303//! - **Queue capacity**: Fixed at 16 events. Events are silently dropped when queue is full.
304//! - **Shared statics**: All FSMs of the same type share global static variables (lock + queue).
305//! This is normally not an issue as each FSM type has a unique name.
306//!
307//! ### Usage
308//!
309//! ```toml
310//! [dependencies]
311//! typed-fsm = { version = "0.4", features = ["concurrent"] }
312//! ```
313//!
314//! ### Complete Examples
315//!
316//! - `examples/concurrent_isr.rs` - Simulated ISR with event queuing
317//! - `examples/concurrent_threads.rs` - Multithreading with concurrent dispatch
318//!
319//! **Performance:** ~10-15% overhead when enabled, zero overhead when disabled.
320//!
321//! ## Examples
322//!
323//! See the `examples/` directory for complete examples:
324//! - \*\*\[New\]\*\* Raspberry Pi Pico 2 W Demo: \[typed-fsm-pico-test\](<https://github.com/afmiguel/typed-fsm-pico-test>) - Real-world usage on RP2350 interacting with Hardware (LED, ADC, Timer).
325//! - \*\*\[New\]\*\* Arduino Uno Demo: \[typed-fsm-arduino-test\](<https://github.com/afmiguel/typed-fsm-arduino-test>) - Real-world usage on ATmega328P (AVR) with concurrent ISRs.
326//! - `motor.rs` - Motor control (complex, event-driven) - **start here!**
327//! - `traffic_light.rs` - Traffic light controller (simple, event-driven)
328//! - `guards.rs` - Conditional transitions (ATM, door lock, orders)
329//! - `logging.rs` - FSM with instrumentation
330//! - `timeouts.rs` - Timer pattern (WiFi, session, debouncing)
331//! - `concurrent_isr.rs` - ISR-safe dispatch (requires `concurrent` feature)
332//! - `concurrent_threads.rs` - Thread-safe dispatch (requires `concurrent` feature)
333
334#![no_std]
335
336// The state_machine! macro is automatically available at the crate root
337// due to #[macro_export] in fsm.rs
338mod fsm;
339
340// Re-export the core types
341pub use fsm::Transition;