autocore-std 3.3.40

Standard library for AutoCore control programs - shared memory, IPC, and logging utilities
Documentation
use std::time::{Duration, Instant};

/// Timer On Delay (TON)
///
/// A timer that delays turning on the output. The output `q` becomes `true`
/// after the enable input `en` has been continuously `true` for the preset
/// time `pt`. The elapsed time is available in `et`.
///
/// This is equivalent to the IEC 61131-3 TON function block.
///
/// # Behavior
///
/// - When `en` becomes `true`, the timer starts counting from zero
/// - While counting, `et` shows the elapsed time and `q` is `false`
/// - When `et` reaches `pt`, `q` becomes `true` and `et` is clamped to `pt`
/// - When `en` becomes `false`, the timer resets: `q` = `false`, `et` = 0
///
/// # Example
///
/// ```
/// use autocore_std::fb::Ton;
/// use std::time::Duration;
///
/// let mut timer = Ton::new();
/// let delay = Duration::from_secs(5);
///
/// // Timer disabled - output is false
/// assert_eq!(timer.call(false, delay), false);
/// assert_eq!(timer.et, Duration::ZERO);
///
/// // Enable timer - starts counting
/// timer.call(true, delay);
/// assert_eq!(timer.q, false);  // Not done yet
/// // timer.et is now counting up...
///
/// // Disable resets the timer
/// timer.call(false, delay);
/// assert_eq!(timer.et, Duration::ZERO);
/// ```
///
/// # Timing Diagram
///
/// ```text
///  en: _____|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|_____
///   q: _____________|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|_____
///  et: 0---|++++++++|PT----------------|0----
///          ^        ^                  ^
///          |        |                  |
///      en rises   et=pt             en falls
/// ```
///
/// # Use Cases
///
/// - Motor start delay (allow contactors to engage)
/// - Debouncing switches (ignore brief transitions)
/// - Timeout detection (alarm if condition persists too long)
#[derive(Debug, Clone)]
pub struct Ton {
    /// Input: Enable the timer (true = counting, false = reset)
    pub en: bool,
    /// Input: Preset time (duration before output activates)
    pub pt: Duration,
    /// Output: Timer done (true when elapsed time >= preset time)
    pub q: bool,
    /// Output: Elapsed time since timer was enabled
    pub et: Duration,

    start_time: Option<Instant>,
    active: bool,
}

impl Ton {
    /// Creates a new timer with default values.
    ///
    /// The timer starts in the disabled state with zero elapsed time.
    ///
    /// # Example
    ///
    /// ```
    /// use autocore_std::fb::Ton;
    ///
    /// let timer = Ton::new();
    /// assert_eq!(timer.q, false);
    /// assert_eq!(timer.et, std::time::Duration::ZERO);
    /// ```
    pub fn new() -> Self {
        Self {
            en: false,
            pt: Duration::default(),
            q: false,
            et: Duration::default(),
            start_time: None,
            active: false,
        }
    }

    /// Executes the timer logic.
    ///
    /// Call this method once per control cycle. The timer counts real elapsed
    /// time (not cycles), so the output timing is independent of scan rate.
    ///
    /// # Arguments
    ///
    /// * `en` - Enable input: `true` to run timer, `false` to reset
    /// * `pt` - Preset time: duration before output activates
    ///
    /// # Returns
    ///
    /// The current state of the output `q` (true if timer has elapsed).
    ///
    /// # Example
    ///
    /// ```
    /// use autocore_std::fb::Ton;
    /// use std::time::Duration;
    ///
    /// let mut timer = Ton::new();
    ///
    /// // Use in a control loop
    /// let motor_request = true;
    /// let start_delay = Duration::from_millis(500);
    ///
    /// let motor_enabled = timer.call(motor_request, start_delay);
    /// // motor_enabled will be true after 500ms of motor_request being true
    /// ```
    pub fn call(&mut self, en: bool, pt: Duration) -> bool {
        self.en = en;
        self.pt = pt;

        if !self.en {
            // Reset
            self.q = false;
            self.et = Duration::ZERO;
            self.start_time = None;
            self.active = false;
        } else {
            if !self.active {
                // Rising edge of EN - start timing
                self.start_time = Some(Instant::now());
                self.active = true;
                self.et = Duration::ZERO;
                self.q = false;
            } else {
                // Timer running
                if let Some(start) = self.start_time {
                    self.et = start.elapsed();
                    if self.et >= self.pt {
                        self.et = self.pt; // Clamp ET to PT
                        self.q = true;
                    }
                }
            }
        }
        self.q
    }

    /// Resets the timer to its initial state.
    ///
    /// This is equivalent to calling `call(false, ...)`.
    ///
    /// # Example
    ///
    /// ```
    /// use autocore_std::fb::Ton;
    /// use std::time::Duration;
    ///
    /// let mut timer = Ton::new();
    /// timer.call(true, Duration::from_secs(1));
    /// // ... timer is running ...
    ///
    /// timer.reset();
    /// assert_eq!(timer.q, false);
    /// assert_eq!(timer.et, Duration::ZERO);
    /// ```
    pub fn reset(&mut self) {
        self.q = false;
        self.et = Duration::ZERO;
        self.start_time = None;
        self.active = false;
    }
}

impl Default for Ton {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_ton_basic() {
        let mut timer = Ton::new();
        let pt = Duration::from_millis(50);

        // Disabled
        assert_eq!(timer.call(false, pt), false);
        assert_eq!(timer.et, Duration::ZERO);

        // Enable
        assert_eq!(timer.call(true, pt), false);
        assert!(timer.et < pt);

        // Wait for timer
        std::thread::sleep(Duration::from_millis(60));
        assert_eq!(timer.call(true, pt), true);
        assert_eq!(timer.et, pt);

        // Reset
        timer.reset();
        assert_eq!(timer.q, false);
        assert_eq!(timer.et, Duration::ZERO);
    }
}