Skip to main content

autocore_std/banner/
wls15.rs

1//! Banner WLS15P IO-Link multi-color light strip.
2//!
3//! This module provides enums for the WLS15P's PDO fields and two function blocks:
4//!
5//! - [`Wls15RunMode`] — IO-Link run mode control with preset animations
6//!   (alert, knight rider, pulse, spectrum, etc.)
7//! - [`Wls15Digital`] — Simple digital (two-wire) control with red/green/blue
8//!   selection and optional blinking
9//!
10//! # IO-Link Run Mode Example
11//!
12//! ```ignore
13//! use autocore_std::banner::wls15::{Wls15RunMode, Animation, Color, ColorIntensity, Speed};
14//!
15//! let mut light = Wls15RunMode::new();
16//!
17//! // Red alert — scrolls out from center
18//! light.alert(Color::Red, ColorIntensity::High, Speed::Medium);
19//!
20//! // Knight Rider scanner effect
21//! light.knight_rider(Color::Red);
22//!
23//! // Breathing pulse
24//! light.pulse(Color::Green, ColorIntensity::High, Speed::Slow);
25//!
26//! // Rainbow spectrum
27//! light.spectrum(Speed::Fast);
28//!
29//! // Turn off
30//! light.off();
31//! ```
32//!
33//! # Digital Control Example
34//!
35//! ```
36//! use autocore_std::banner::wls15::Wls15Digital;
37//! use std::time::Duration;
38//!
39//! let mut light = Wls15Digital::new();
40//!
41//! light.green();
42//! light.red();
43//! light.blue();
44//! light.off();
45//!
46//! // Blink at 500ms interval
47//! light.blink_on(Duration::from_millis(500));
48//! light.call(); // call every scan cycle
49//!
50//! light.blink_off();
51//! ```
52
53use std::time::Duration;
54use crate::fb::{RTrig, FTrig, Ton};
55
56// ---------------------------------------------------------------------------
57// Enums
58// ---------------------------------------------------------------------------
59
60/// Animation modes for the WLS15P IO-Link run mode.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62#[repr(u8)]
63pub enum Animation {
64    Off = 0,
65    Steady = 1,
66    Flash = 2,
67    TwoColorFlash = 3,
68    TwoColorShift = 4,
69    EndsSteady = 5,
70    EndsFlash = 6,
71    Scroll = 7,
72    CenterScroll = 8,
73    Bounce = 9,
74    CenterBounce = 10,
75    /// Breathing effect — smooth intensity sweep.
76    IntensitySweep = 11,
77    TwoColorSweep = 12,
78    /// All colors sweeping across the strip.
79    Spectrum = 13,
80    SingleEndSteady = 14,
81    SingleEndFlash = 15,
82}
83
84/// Color codes for the WLS15P IO-Link PDOs.
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86#[repr(u8)]
87pub enum Color {
88    Green = 0,
89    Red = 1,
90    Orange = 2,
91    Amber = 3,
92    Yellow = 4,
93    LimeGreen = 5,
94    SpringGreen = 6,
95    Cyan = 7,
96    SkyBlue = 8,
97    Blue = 9,
98    Violet = 10,
99    Magenta = 11,
100    Rose = 12,
101    DaylightWhite = 13,
102    Custom1 = 14,
103    Custom2 = 15,
104    IncandescentWhite = 16,
105    WarmWhite = 17,
106    FluorescentWhite = 18,
107    NeutralWhite = 19,
108    CoolWhite = 20,
109}
110
111/// Color intensity levels for the WLS15P IO-Link PDOs.
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113#[repr(u8)]
114pub enum ColorIntensity {
115    High = 0,
116    Low = 1,
117    Medium = 2,
118    Off = 3,
119    Custom = 4,
120}
121
122/// Animation direction for the WLS15P IO-Link PDOs.
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124#[repr(u8)]
125pub enum Direction {
126    Up = 0,
127    Down = 1,
128}
129
130/// Pulse pattern codes for the WLS15P IO-Link PDOs.
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132#[repr(u8)]
133pub enum PulsePattern {
134    Normal = 0,
135    Strobe = 1,
136    ThreePulse = 2,
137    Sos = 3,
138    Random = 4,
139}
140
141/// Scroll or bounce style for the WLS15P IO-Link PDOs.
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143#[repr(u8)]
144pub enum ScrollStyle {
145    Solid = 0,
146    Tail = 1,
147    Ripple = 2,
148}
149
150/// Animation speed codes for the WLS15P IO-Link PDOs.
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152#[repr(u8)]
153pub enum Speed {
154    Medium = 0,
155    Fast = 1,
156    Slow = 2,
157    CustomFlashRate = 3,
158}
159
160// ---------------------------------------------------------------------------
161// Wls15RunMode — IO-Link run mode control
162// ---------------------------------------------------------------------------
163
164/// IO-Link run mode controller for the Banner WLS15P light strip.
165///
166/// Each output field corresponds to a PDO byte that should be linked to the
167/// device's IO-Link process data. Call the preset methods (`alert`, `pulse`,
168/// `knight_rider`, `spectrum`, `off`) to configure the animation, or set the
169/// fields directly for full control.
170#[derive(Debug, Clone)]
171pub struct Wls15RunMode {
172    /// PDO: Animation mode.
173    pub animation: u8,
174    /// PDO: Color 1.
175    pub color1: u8,
176    /// PDO: Color 1 intensity.
177    pub color1_intensity: u8,
178    /// PDO: Color 2.
179    pub color2: u8,
180    /// PDO: Color 2 intensity.
181    pub color2_intensity: u8,
182    /// PDO: Speed.
183    pub speed: u8,
184    /// PDO: Pulse pattern.
185    pub pulse_pattern: u8,
186    /// PDO: Scroll/bounce style.
187    pub scroll_bounce_style: u8,
188    /// PDO: Percent width of color 1 (0-100).
189    pub percent_width_color1: u8,
190    /// PDO: Direction.
191    pub direction: u8,
192}
193
194impl Wls15RunMode {
195    /// Create a new WLS15 run mode controller with all outputs at zero (off).
196    pub fn new() -> Self {
197        Self {
198            animation: 0,
199            color1: 0,
200            color1_intensity: 0,
201            color2: 0,
202            color2_intensity: 0,
203            speed: 0,
204            pulse_pattern: 0,
205            scroll_bounce_style: 0,
206            percent_width_color1: 0,
207            direction: 0,
208        }
209    }
210
211    /// Turn the light off.
212    pub fn off(&mut self) {
213        self.animation = Animation::Off as u8;
214    }
215
216    /// Red alert — scrolls out from center.
217    ///
218    /// Classic sci-fi alert indication. Uses center-scroll animation
219    /// with a single color (color 2 intensity is off).
220    pub fn alert(&mut self, color: Color, intensity: ColorIntensity, speed: Speed) {
221        self.color1 = color as u8;
222        self.speed = speed as u8;
223        self.color1_intensity = intensity as u8;
224        self.color2_intensity = ColorIntensity::Off as u8;
225        self.percent_width_color1 = 0;
226        self.animation = Animation::CenterScroll as u8;
227    }
228
229    /// Knight Rider — bouncing scanner with a tail over a dark background.
230    ///
231    /// Classic 80's scanner effect. The primary color bounces across the
232    /// strip with a tail effect.
233    pub fn knight_rider(&mut self, color: Color) {
234        self.color2 = Color::WarmWhite as u8;
235        self.scroll_bounce_style = ScrollStyle::Tail as u8;
236        self.color2_intensity = ColorIntensity::Off as u8;
237        self.percent_width_color1 = 40;
238        self.color1 = color as u8;
239        self.color1_intensity = ColorIntensity::High as u8;
240        self.speed = Speed::Medium as u8;
241        self.animation = Animation::Bounce as u8;
242    }
243
244    /// Pulse — smooth breathing effect.
245    ///
246    /// The light smoothly fades in and out with the selected color.
247    pub fn pulse(&mut self, color: Color, intensity: ColorIntensity, speed: Speed) {
248        self.color1 = color as u8;
249        self.speed = speed as u8;
250        self.color1_intensity = intensity as u8;
251        self.color2_intensity = ColorIntensity::Off as u8;
252        self.percent_width_color1 = 0;
253        self.animation = Animation::TwoColorSweep as u8;
254    }
255
256    /// Spectrum — all colors flowing across the strip.
257    ///
258    /// A rainbow effect where every color sweeps across the light.
259    pub fn spectrum(&mut self, speed: Speed) {
260        self.animation = Animation::Spectrum as u8;
261        self.speed = speed as u8;
262        self.percent_width_color1 = 0;
263    }
264
265    /// Steady — solid single color.
266    pub fn steady(&mut self, color: Color, intensity: ColorIntensity) {
267        self.color1 = color as u8;
268        self.color1_intensity = intensity as u8;
269        self.animation = Animation::Steady as u8;
270    }
271
272    /// Flash — single color flashing.
273    pub fn flash(&mut self, color: Color, intensity: ColorIntensity, speed: Speed) {
274        self.color1 = color as u8;
275        self.color1_intensity = intensity as u8;
276        self.speed = speed as u8;
277        self.animation = Animation::Flash as u8;
278    }
279
280    /// Write the current run-mode settings into a [`Wls15RunModeView`] — i.e.
281    /// the device's ten output PDO bytes. Call once per scan cycle after
282    /// configuring the animation.
283    ///
284    /// [`Wls15RunModeView`]: crate::banner::wls15_view::Wls15RunModeView
285    pub fn apply(&self, view: &mut crate::banner::wls15_view::Wls15RunModeView<'_>) {
286        *view.animation = self.animation;
287        *view.color1 = self.color1;
288        *view.color1_intensity = self.color1_intensity;
289        *view.speed = self.speed;
290        *view.pulse_pattern = self.pulse_pattern;
291        *view.color2 = self.color2;
292        *view.color2_intensity = self.color2_intensity;
293        *view.scroll_bounce_style = self.scroll_bounce_style;
294        *view.percent_width_color1 = self.percent_width_color1;
295        *view.direction = self.direction;
296    }
297}
298
299impl Default for Wls15RunMode {
300    fn default() -> Self {
301        Self::new()
302    }
303}
304
305// ---------------------------------------------------------------------------
306// Wls15Digital — Two-wire digital control
307// ---------------------------------------------------------------------------
308
309/// Digital (two-wire) controller for the Banner WLS15P light strip.
310///
311/// Controls the light using two digital outputs (Q1, Q2) to select colors:
312///
313/// | Q1 | Q2 | Color |
314/// |----|----|-------|
315/// | false | false | Off |
316/// | true | false | Red |
317/// | false | true | Green |
318/// | true | true | Blue |
319///
320/// Supports optional blinking via an internal timer. Call [`call()`](Self::call)
321/// every scan cycle to update the outputs.
322#[derive(Debug, Clone)]
323pub struct Wls15Digital {
324    /// Output: connect to channel/input 1 of the light strip.
325    pub q1: bool,
326    /// Output: connect to channel/input 2 of the light strip.
327    pub q2: bool,
328
329    req_q1: bool,
330    req_q2: bool,
331    enable_blink: bool,
332    blink_time: Duration,
333    blink_timer: Ton,
334    blink_bit: bool,
335    rt_blink: RTrig,
336    ft_blink: FTrig,
337}
338
339impl Wls15Digital {
340    /// Create a new digital light controller (off, no blink).
341    pub fn new() -> Self {
342        Self {
343            q1: false,
344            q2: false,
345            req_q1: false,
346            req_q2: false,
347            enable_blink: false,
348            blink_time: Duration::from_secs(1),
349            blink_timer: Ton::new(),
350            blink_bit: false,
351            rt_blink: RTrig::new(),
352            ft_blink: FTrig::new(),
353        }
354    }
355
356    /// Set color to off.
357    pub fn off(&mut self) {
358        self.req_q1 = false;
359        self.req_q2 = false;
360    }
361
362    /// Set color to red.
363    pub fn red(&mut self) {
364        self.req_q1 = true;
365        self.req_q2 = false;
366    }
367
368    /// Set color to green.
369    pub fn green(&mut self) {
370        self.req_q1 = false;
371        self.req_q2 = true;
372    }
373
374    /// Set color to blue.
375    pub fn blue(&mut self) {
376        self.req_q1 = true;
377        self.req_q2 = true;
378    }
379
380    /// Enable blinking at the specified interval.
381    pub fn blink_on(&mut self, interval: Duration) {
382        self.enable_blink = true;
383        self.blink_time = interval;
384    }
385
386    /// Disable blinking — outputs follow the requested color directly.
387    pub fn blink_off(&mut self) {
388        self.enable_blink = false;
389    }
390
391    /// Update outputs. Call once per scan cycle.
392    ///
393    /// When blinking is enabled, the outputs toggle between the requested
394    /// color and off at the configured interval. When blinking is disabled,
395    /// the outputs track the requested color directly.
396    pub fn call(&mut self) {
397        // Blink timer — toggles blink_bit when elapsed
398        if self.blink_timer.call(true, self.blink_time) {
399            self.blink_bit = !self.blink_bit;
400            self.blink_timer.reset();
401        }
402
403        let rising = self.rt_blink.call(self.blink_bit);
404        let falling = self.ft_blink.call(self.blink_bit);
405
406        if self.enable_blink {
407            if rising {
408                self.q1 = self.req_q1;
409                self.q2 = self.req_q2;
410            }
411            if falling {
412                self.q1 = false;
413                self.q2 = false;
414            }
415        } else {
416            self.q1 = self.req_q1;
417            self.q2 = self.req_q2;
418        }
419    }
420}
421
422impl Default for Wls15Digital {
423    fn default() -> Self {
424        Self::new()
425    }
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431
432    #[test]
433    fn test_run_mode_off() {
434        let mut light = Wls15RunMode::new();
435        light.steady(Color::Red, ColorIntensity::High);
436        assert_eq!(light.animation, Animation::Steady as u8);
437        light.off();
438        assert_eq!(light.animation, Animation::Off as u8);
439    }
440
441    #[test]
442    fn test_run_mode_alert() {
443        let mut light = Wls15RunMode::new();
444        light.alert(Color::Red, ColorIntensity::High, Speed::Medium);
445        assert_eq!(light.animation, Animation::CenterScroll as u8);
446        assert_eq!(light.color1, Color::Red as u8);
447        assert_eq!(light.color1_intensity, ColorIntensity::High as u8);
448        assert_eq!(light.color2_intensity, ColorIntensity::Off as u8);
449    }
450
451    #[test]
452    fn test_run_mode_knight_rider() {
453        let mut light = Wls15RunMode::new();
454        light.knight_rider(Color::Red);
455        assert_eq!(light.animation, Animation::Bounce as u8);
456        assert_eq!(light.scroll_bounce_style, ScrollStyle::Tail as u8);
457        assert_eq!(light.color1, Color::Red as u8);
458        assert_eq!(light.percent_width_color1, 40);
459    }
460
461    #[test]
462    fn test_run_mode_pulse() {
463        let mut light = Wls15RunMode::new();
464        light.pulse(Color::Green, ColorIntensity::High, Speed::Slow);
465        assert_eq!(light.animation, Animation::TwoColorSweep as u8);
466        assert_eq!(light.color1, Color::Green as u8);
467        assert_eq!(light.speed, Speed::Slow as u8);
468    }
469
470    #[test]
471    fn test_run_mode_spectrum() {
472        let mut light = Wls15RunMode::new();
473        light.spectrum(Speed::Fast);
474        assert_eq!(light.animation, Animation::Spectrum as u8);
475        assert_eq!(light.speed, Speed::Fast as u8);
476    }
477
478    #[test]
479    fn test_digital_colors() {
480        let mut light = Wls15Digital::new();
481
482        light.red();
483        light.call();
484        assert_eq!((light.q1, light.q2), (true, false));
485
486        light.green();
487        light.call();
488        assert_eq!((light.q1, light.q2), (false, true));
489
490        light.blue();
491        light.call();
492        assert_eq!((light.q1, light.q2), (true, true));
493
494        light.off();
495        light.call();
496        assert_eq!((light.q1, light.q2), (false, false));
497    }
498
499    #[test]
500    fn test_digital_blink_off_tracks_color() {
501        let mut light = Wls15Digital::new();
502        light.red();
503        light.blink_off();
504        light.call();
505        assert_eq!((light.q1, light.q2), (true, false));
506    }
507
508    #[test]
509    fn test_enum_values() {
510        assert_eq!(Animation::Off as u8, 0);
511        assert_eq!(Animation::Spectrum as u8, 13);
512        assert_eq!(Color::Green as u8, 0);
513        assert_eq!(Color::Red as u8, 1);
514        assert_eq!(Color::CoolWhite as u8, 20);
515        assert_eq!(ColorIntensity::High as u8, 0);
516        assert_eq!(ColorIntensity::Off as u8, 3);
517        assert_eq!(Direction::Up as u8, 0);
518        assert_eq!(Direction::Down as u8, 1);
519        assert_eq!(PulsePattern::Sos as u8, 3);
520        assert_eq!(ScrollStyle::Tail as u8, 1);
521        assert_eq!(Speed::Fast as u8, 1);
522    }
523}