1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
use crateResponse;
/// 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 [`TimerCommand`](crate::response::TimerCommand)s 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`](Self::on_event) | An external event arrived (key press, byte received, …) |
/// | [`on_timeout`](Self::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`](crate::dispatch::dispatch).
///
/// # Timer IDs
///
/// The [`TimerId`](Self::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_event` and `on_timeout` **must not produce side effects**.
/// All effects are expressed through the returned [`Response`].
/// - A `Response` with `consumed = false` means 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 if `on_event` was 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(),
/// },
/// }
/// }
/// }
/// ```