Skip to main content

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}