pub trait TimedStateMachine {
type Event;
type Action;
type TimerId: Copy + Eq + Debug;
// Required methods
fn on_event(
&mut self,
event: Self::Event,
) -> Response<Self::Action, Self::TimerId>;
fn on_timeout(
&mut self,
timer_id: Self::TimerId,
) -> Response<Self::Action, Self::TimerId>;
}Expand description
A timed finite state machine.
Unlike a classic FSM where transitions depend only on (State, Event),
a TimedStateMachine can express transitions that depend on the
absence of events within a time window. This is achieved by
including TimerCommands in the
Response, which the runtime interprets to set or kill timers.
The state machine itself has no side effects. It never calls
platform timer APIs directly. Instead, it returns a Response
containing timer commands, and the runtime executes them.
§Two kinds of input
| Method | Meaning |
|---|---|
on_event | An external event arrived (key press, byte received, …) |
on_timeout | A previously requested timer fired — the absence of events was detected |
Both methods return the same Response type, so the runtime can
handle them uniformly via dispatch.
§Timer IDs
The TimerId type identifies timers within the
state machine. Choose the type that fits your needs:
TimerId | When to use |
|---|---|
() | Exactly one timer — simplest case |
enum Timer { … } | Multiple named timers with distinct roles |
u8 / u32 | Indexed timers generated dynamically |
§Invariants
on_eventandon_timeoutmust not produce side effects. All effects are expressed through the returnedResponse.- A
Responsewithconsumed = falsemeans the event was not handled by this machine and should be forwarded to the next handler in the chain. The machine’s internal state must remain unchanged in that case (as ifon_eventwas never called). - Implementations should be deterministic: the same sequence of inputs always produces the same sequence of outputs.
§Example: single timer
use std::time::Duration;
use timed_fsm::{TimedStateMachine, Response};
struct DebounceFilter {
pending: Option<bool>,
}
impl DebounceFilter {
fn new() -> Self { Self { pending: None } }
}
impl TimedStateMachine for DebounceFilter {
type Event = bool; // GPIO level: true = high, false = low
type Action = bool; // Confirmed level
type TimerId = (); // One debounce timer
fn on_event(&mut self, event: bool) -> Response<bool, ()> {
self.pending = Some(event);
Response::consume()
.with_timer((), Duration::from_millis(20))
}
fn on_timeout(&mut self, _: ()) -> Response<bool, ()> {
match self.pending.take() {
Some(level) => Response::emit_one(level),
None => Response::pass_through(),
}
}
}§Example: multiple timers
When a state machine needs more than one concurrent timer, use an
enum as TimerId so each timer has a descriptive name.
use std::time::Duration;
use timed_fsm::{TimedStateMachine, Response};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Timer { Debounce, RepeatDelay, RepeatInterval }
struct KeyRepeater { key: Option<u8> }
impl KeyRepeater {
fn new() -> Self { Self { key: None } }
}
impl TimedStateMachine for KeyRepeater {
type Event = Option<u8>; // Some(key) = pressed, None = released
type Action = u8;
type TimerId = Timer;
fn on_event(&mut self, event: Option<u8>) -> Response<u8, Timer> {
match event {
Some(key) => {
self.key = Some(key);
Response::consume()
.with_timer(Timer::Debounce, Duration::from_millis(10))
.with_kill_timer(Timer::RepeatDelay)
.with_kill_timer(Timer::RepeatInterval)
}
None => {
self.key = None;
Response::consume()
.with_kill_timer(Timer::Debounce)
.with_kill_timer(Timer::RepeatDelay)
.with_kill_timer(Timer::RepeatInterval)
}
}
}
fn on_timeout(&mut self, id: Timer) -> Response<u8, Timer> {
match id {
Timer::Debounce => match self.key {
Some(k) => Response::emit_one(k)
.with_timer(Timer::RepeatDelay, Duration::from_millis(500)),
None => Response::pass_through(),
},
Timer::RepeatDelay => match self.key {
Some(k) => Response::emit_one(k)
.with_timer(Timer::RepeatInterval, Duration::from_millis(50)),
None => Response::pass_through(),
},
Timer::RepeatInterval => match self.key {
Some(k) => Response::emit_one(k)
.with_timer(Timer::RepeatInterval, Duration::from_millis(50)),
None => Response::pass_through(),
},
}
}
}Required Associated Types§
Sourcetype Action
type Action
The action type produced by transitions.
Actions are collected in Response::actions
and forwarded to ActionExecutor::execute
by the runtime.
Required Methods§
Sourcefn on_timeout(
&mut self,
timer_id: Self::TimerId,
) -> Response<Self::Action, Self::TimerId>
fn on_timeout( &mut self, timer_id: Self::TimerId, ) -> Response<Self::Action, Self::TimerId>
Process a timer timeout and return the transition result.
Called by the runtime when a timer previously requested via
TimerCommand::Set fires.
The timer_id identifies which timer expired, allowing the state
machine to distinguish multiple concurrent timers.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".