autocore_std/fb/ton.rs
1use std::time::{Duration, Instant};
2
3/// Timer On Delay (TON)
4///
5/// A timer that delays turning on the output. The output `q` becomes `true`
6/// after the enable input `en` has been continuously `true` for the preset
7/// time `pt`. The elapsed time is available in `et`.
8///
9/// This is equivalent to the IEC 61131-3 TON function block.
10///
11/// # Behavior
12///
13/// - When `en` becomes `true`, the timer starts counting from zero
14/// - While counting, `et` shows the elapsed time and `q` is `false`
15/// - When `et` reaches `pt`, `q` becomes `true` and `et` is clamped to `pt`
16/// - When `en` becomes `false`, the timer resets: `q` = `false`, `et` = 0
17///
18/// # Example
19///
20/// ```
21/// use autocore_std::fb::Ton;
22/// use std::time::Duration;
23///
24/// let mut timer = Ton::new();
25/// let delay = Duration::from_secs(5);
26///
27/// // Timer disabled - output is false
28/// assert_eq!(timer.call(false, delay), false);
29/// assert_eq!(timer.et, Duration::ZERO);
30///
31/// // Enable timer - starts counting
32/// timer.call(true, delay);
33/// assert_eq!(timer.q, false); // Not done yet
34/// // timer.et is now counting up...
35///
36/// // Disable resets the timer
37/// timer.call(false, delay);
38/// assert_eq!(timer.et, Duration::ZERO);
39/// ```
40///
41/// # Timing Diagram
42///
43/// ```text
44/// en: _____|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|_____
45/// q: _____________|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|_____
46/// et: 0---|++++++++|PT----------------|0----
47/// ^ ^ ^
48/// | | |
49/// en rises et=pt en falls
50/// ```
51///
52/// # Use Cases
53///
54/// - Motor start delay (allow contactors to engage)
55/// - Debouncing switches (ignore brief transitions)
56/// - Timeout detection (alarm if condition persists too long)
57#[derive(Debug, Clone)]
58pub struct Ton {
59 /// Input: Enable the timer (true = counting, false = reset)
60 pub en: bool,
61 /// Input: Preset time (duration before output activates)
62 pub pt: Duration,
63 /// Output: Timer done (true when elapsed time >= preset time)
64 pub q: bool,
65 /// Output: Elapsed time since timer was enabled
66 pub et: Duration,
67
68 start_time: Option<Instant>,
69 active: bool,
70}
71
72impl Ton {
73 /// Creates a new timer with default values.
74 ///
75 /// The timer starts in the disabled state with zero elapsed time.
76 ///
77 /// # Example
78 ///
79 /// ```
80 /// use autocore_std::fb::Ton;
81 ///
82 /// let timer = Ton::new();
83 /// assert_eq!(timer.q, false);
84 /// assert_eq!(timer.et, std::time::Duration::ZERO);
85 /// ```
86 pub fn new() -> Self {
87 Self {
88 en: false,
89 pt: Duration::default(),
90 q: false,
91 et: Duration::default(),
92 start_time: None,
93 active: false,
94 }
95 }
96
97 /// Executes the timer logic.
98 ///
99 /// Call this method once per control cycle. The timer counts real elapsed
100 /// time (not cycles), so the output timing is independent of scan rate.
101 ///
102 /// # Arguments
103 ///
104 /// * `en` - Enable input: `true` to run timer, `false` to reset
105 /// * `pt` - Preset time: duration before output activates
106 ///
107 /// # Returns
108 ///
109 /// The current state of the output `q` (true if timer has elapsed).
110 ///
111 /// # Example
112 ///
113 /// ```
114 /// use autocore_std::fb::Ton;
115 /// use std::time::Duration;
116 ///
117 /// let mut timer = Ton::new();
118 ///
119 /// // Use in a control loop
120 /// let motor_request = true;
121 /// let start_delay = Duration::from_millis(500);
122 ///
123 /// let motor_enabled = timer.call(motor_request, start_delay);
124 /// // motor_enabled will be true after 500ms of motor_request being true
125 /// ```
126 pub fn call(&mut self, en: bool, pt: Duration) -> bool {
127 self.en = en;
128 self.pt = pt;
129
130 if !self.en {
131 // Reset
132 self.q = false;
133 self.et = Duration::ZERO;
134 self.start_time = None;
135 self.active = false;
136 } else {
137 if !self.active {
138 // Rising edge of EN - start timing
139 self.start_time = Some(Instant::now());
140 self.active = true;
141 self.et = Duration::ZERO;
142 self.q = false;
143 } else {
144 // Timer running
145 if let Some(start) = self.start_time {
146 self.et = start.elapsed();
147 if self.et >= self.pt {
148 self.et = self.pt; // Clamp ET to PT
149 self.q = true;
150 }
151 }
152 }
153 }
154 self.q
155 }
156
157 /// Resets the timer to its initial state.
158 ///
159 /// This is equivalent to calling `call(false, ...)`.
160 ///
161 /// # Example
162 ///
163 /// ```
164 /// use autocore_std::fb::Ton;
165 /// use std::time::Duration;
166 ///
167 /// let mut timer = Ton::new();
168 /// timer.call(true, Duration::from_secs(1));
169 /// // ... timer is running ...
170 ///
171 /// timer.reset();
172 /// assert_eq!(timer.q, false);
173 /// assert_eq!(timer.et, Duration::ZERO);
174 /// ```
175 pub fn reset(&mut self) {
176 self.q = false;
177 self.et = Duration::ZERO;
178 self.start_time = None;
179 self.active = false;
180 }
181}
182
183impl Default for Ton {
184 fn default() -> Self {
185 Self::new()
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn test_ton_basic() {
195 let mut timer = Ton::new();
196 let pt = Duration::from_millis(50);
197
198 // Disabled
199 assert_eq!(timer.call(false, pt), false);
200 assert_eq!(timer.et, Duration::ZERO);
201
202 // Enable
203 assert_eq!(timer.call(true, pt), false);
204 assert!(timer.et < pt);
205
206 // Wait for timer
207 std::thread::sleep(Duration::from_millis(60));
208 assert_eq!(timer.call(true, pt), true);
209 assert_eq!(timer.et, pt);
210
211 // Reset
212 timer.reset();
213 assert_eq!(timer.q, false);
214 assert_eq!(timer.et, Duration::ZERO);
215 }
216}