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;