autocore-std 3.3.53

Standard library for AutoCore control programs - shared memory, IPC, and logging utilities
Documentation
//! Borrowed view into GlobalMemory for one Banner WLS15P light strip in
//! IO-Link **run mode**.
//!
//! Groups the ten output PDO bytes that make up the WLS15P's run-mode process
//! data into a single typed struct with lifetime-bound references. One
//! [`Wls15RunModeView`] is built per-tick via the [`wls15_run_mode_view!`]
//! macro, then written by
//! [`Wls15RunMode::apply`](crate::banner::wls15::Wls15RunMode::apply).
//!
//! # RxPDO byte layout
//!
//! The WLS15P run-mode output process data is ten contiguous bytes. Note that
//! this byte order differs from the field order of
//! [`Wls15RunMode`](crate::banner::wls15::Wls15RunMode); the macro below maps
//! each field to its correct byte.
//!
//! | Byte | Field                  |
//! |------|------------------------|
//! | 0    | `animation`            |
//! | 1    | `color1`               |
//! | 2    | `color1_intensity`     |
//! | 3    | `speed`                |
//! | 4    | `pulse_pattern`        |
//! | 5    | `color2`               |
//! | 6    | `color2_intensity`     |
//! | 7    | `scroll_bounce_style`  |
//! | 8    | `percent_width_color1` |
//! | 9    | `direction`            |
//!
//! # GM naming convention
//!
//! For a light wired to IMPACT67 port X3, autocore generates one `u8` variable
//! per output byte. The macro takes the common prefix and appends
//! `_output_byte_0` .. `_output_byte_9`:
//!
//! ```text
//! impact67_0_port_x3_rxpdo_port_x3_output_byte_0   // animation
//! impact67_0_port_x3_rxpdo_port_x3_output_byte_1   // color1
//! ...
//! impact67_0_port_x3_rxpdo_port_x3_output_byte_9   // direction
//! ```
//!
//! Use the [`wls15_run_mode_view!`] macro to construct the view at the start of
//! each control cycle — no hand-written borrowing is required.

/// Borrowed view into GlobalMemory for one WLS15P light strip in run mode.
///
/// Holds mutable references to the ten run-mode output (RxPDO) bytes.
/// Construct via the [`wls15_run_mode_view!`] macro and write it with
/// [`Wls15RunMode::apply`](crate::banner::wls15::Wls15RunMode::apply).
pub struct Wls15RunModeView<'a> {
    /// Byte 0 — animation mode.
    pub animation: &'a mut u8,
    /// Byte 1 — color 1.
    pub color1: &'a mut u8,
    /// Byte 2 — color 1 intensity.
    pub color1_intensity: &'a mut u8,
    /// Byte 3 — speed.
    pub speed: &'a mut u8,
    /// Byte 4 — pulse pattern.
    pub pulse_pattern: &'a mut u8,
    /// Byte 5 — color 2.
    pub color2: &'a mut u8,
    /// Byte 6 — color 2 intensity.
    pub color2_intensity: &'a mut u8,
    /// Byte 7 — scroll / bounce style.
    pub scroll_bounce_style: &'a mut u8,
    /// Byte 8 — percent width of color 1 (0-100).
    pub percent_width_color1: &'a mut u8,
    /// Byte 9 — direction.
    pub direction: &'a mut u8,
}

/// Create a [`Wls15RunModeView`] by projecting the ten run-mode output bytes
/// that share a common GlobalMemory prefix.
///
/// Fields must follow the naming convention `{prefix}_output_byte_0` through
/// `{prefix}_output_byte_9`. See the module docs for the byte layout.
///
/// # Example
///
/// ```ignore
/// let mut view = wls15_run_mode_view!(ctx.gm, impact67_0_port_x3_rxpdo_port_x3);
/// self.status_light.apply(&mut view);
/// ```
#[macro_export]
macro_rules! wls15_run_mode_view {
    ($gm:expr, $prefix:ident) => {
        $crate::paste::paste! {
            $crate::banner::wls15_view::Wls15RunModeView {
                animation:            &mut $gm.[<$prefix _output_byte_0>],
                color1:               &mut $gm.[<$prefix _output_byte_1>],
                color1_intensity:     &mut $gm.[<$prefix _output_byte_2>],
                speed:                &mut $gm.[<$prefix _output_byte_3>],
                pulse_pattern:        &mut $gm.[<$prefix _output_byte_4>],
                color2:               &mut $gm.[<$prefix _output_byte_5>],
                color2_intensity:     &mut $gm.[<$prefix _output_byte_6>],
                scroll_bounce_style:  &mut $gm.[<$prefix _output_byte_7>],
                percent_width_color1: &mut $gm.[<$prefix _output_byte_8>],
                direction:            &mut $gm.[<$prefix _output_byte_9>],
            }
        }
    };
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::banner::wls15::{Wls15RunMode, Animation, Color, ColorIntensity, Speed};

    /// Local storage for the ten run-mode PDO bytes used in tests. Avoids
    /// depending on auto-generated GlobalMemory field names.
    #[derive(Default)]
    pub(crate) struct TestPdo {
        pub bytes: [u8; 10],
    }

    impl TestPdo {
        pub(crate) fn view(&mut self) -> Wls15RunModeView<'_> {
            let [b0, b1, b2, b3, b4, b5, b6, b7, b8, b9] = &mut self.bytes;
            Wls15RunModeView {
                animation: b0,
                color1: b1,
                color1_intensity: b2,
                speed: b3,
                pulse_pattern: b4,
                color2: b5,
                color2_intensity: b6,
                scroll_bounce_style: b7,
                percent_width_color1: b8,
                direction: b9,
            }
        }
    }

    #[test]
    fn apply_maps_fields_to_correct_bytes() {
        let mut light = Wls15RunMode::new();
        light.alert(Color::Red, ColorIntensity::High, Speed::Medium);
        // alert() => CenterScroll animation, color1=Red, color1_intensity=High,
        // color2_intensity=Off, speed=Medium, percent_width_color1=0.

        let mut pdo = TestPdo::default();
        {
            let mut view = pdo.view();
            light.apply(&mut view);
        }

        assert_eq!(pdo.bytes[0], Animation::CenterScroll as u8); // byte 0: animation
        assert_eq!(pdo.bytes[1], Color::Red as u8);              // byte 1: color1
        assert_eq!(pdo.bytes[2], ColorIntensity::High as u8);    // byte 2: color1_intensity
        assert_eq!(pdo.bytes[3], Speed::Medium as u8);           // byte 3: speed
        assert_eq!(pdo.bytes[6], ColorIntensity::Off as u8);     // byte 6: color2_intensity
    }

    #[test]
    fn apply_off_zeroes_animation() {
        let mut light = Wls15RunMode::new();
        light.steady(Color::Blue, ColorIntensity::High);
        light.off();

        let mut pdo = TestPdo::default();
        {
            let mut view = pdo.view();
            light.apply(&mut view);
        }
        assert_eq!(pdo.bytes[0], Animation::Off as u8);
    }

    /// Mock GlobalMemory with the field names the macro expects for the
    /// prefix `light`. Verifies the `wls15_run_mode_view!` macro expands and
    /// concatenates identifiers correctly (path resolution, `paste`, and ten
    /// disjoint `&mut` borrows of one struct).
    #[derive(Default)]
    struct FakeGm {
        light_output_byte_0: u8,
        light_output_byte_1: u8,
        light_output_byte_2: u8,
        light_output_byte_3: u8,
        light_output_byte_4: u8,
        light_output_byte_5: u8,
        light_output_byte_6: u8,
        light_output_byte_7: u8,
        light_output_byte_8: u8,
        light_output_byte_9: u8,
    }

    #[test]
    fn macro_builds_view_and_writes_through() {
        let mut gm = FakeGm::default();
        let mut light = Wls15RunMode::new();
        light.flash(Color::Green, ColorIntensity::High, Speed::Fast);
        {
            let mut view = crate::wls15_run_mode_view!(gm, light);
            light.apply(&mut view);
        }
        assert_eq!(gm.light_output_byte_0, Animation::Flash as u8);
        assert_eq!(gm.light_output_byte_1, Color::Green as u8);
        assert_eq!(gm.light_output_byte_2, ColorIntensity::High as u8);
        assert_eq!(gm.light_output_byte_3, Speed::Fast as u8);
    }

    #[test]
    fn knight_rider_lands_in_run_mode_bytes() {
        let mut light = Wls15RunMode::new();
        light.knight_rider(Color::Red);

        let mut pdo = TestPdo::default();
        {
            let mut view = pdo.view();
            light.apply(&mut view);
        }
        assert_eq!(pdo.bytes[0], Animation::Bounce as u8);                 // animation
        assert_eq!(pdo.bytes[1], Color::Red as u8);                       // color1
        assert_eq!(pdo.bytes[5], Color::WarmWhite as u8);                 // color2 (byte 5)
        assert_eq!(pdo.bytes[7], crate::banner::wls15::ScrollStyle::Tail as u8); // scroll_bounce_style (byte 7)
        assert_eq!(pdo.bytes[8], 40);                                      // percent_width_color1 (byte 8)
    }
}