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
281impl Default for Wls15RunMode {
282    fn default() -> Self {
283        Self::new()
284    }
285}
286
287// ---------------------------------------------------------------------------
288// Wls15Digital — Two-wire digital control
289// ---------------------------------------------------------------------------
290
291/// Digital (two-wire) controller for the Banner WLS15P light strip.
292///
293/// Controls the light using two digital outputs (Q1, Q2) to select colors:
294///
295/// | Q1 | Q2 | Color |
296/// |----|----|-------|
297/// | false | false | Off |
298/// | true | false | Red |
299/// | false | true | Green |
300/// | true | true | Blue |
301///
302/// Supports optional blinking via an internal timer. Call [`call()`](Self::call)
303/// every scan cycle to update the outputs.
304#[derive(Debug, Clone)]
305pub struct Wls15Digital {
306    /// Output: connect to channel/input 1 of the light strip.
307    pub q1: bool,
308    /// Output: connect to channel/input 2 of the light strip.
309    pub q2: bool,
310
311    req_q1: bool,
312    req_q2: bool,
313    enable_blink: bool,
314    blink_time: Duration,
315    blink_timer: Ton,
316    blink_bit: bool,
317    rt_blink: RTrig,
318    ft_blink: FTrig,
319}
320
321impl Wls15Digital {
322    /// Create a new digital light controller (off, no blink).
323    pub fn new() -> Self {
324        Self {
325            q1: false,
326            q2: false,
327            req_q1: false,
328            req_q2: false,
329            enable_blink: false,
330            blink_time: Duration::from_secs(1),
331            blink_timer: Ton::new(),
332            blink_bit: false,
333            rt_blink: RTrig::new(),
334            ft_blink: FTrig::new(),
335        }
336    }
337
338    /// Set color to off.
339    pub fn off(&mut self) {
340        self.req_q1 = false;
341        self.req_q2 = false;
342    }
343
344    /// Set color to red.
345    pub fn red(&mut self) {
346        self.req_q1 = true;
347        self.req_q2 = false;
348    }
349
350    /// Set color to green.
351    pub fn green(&mut self) {
352        self.req_q1 = false;
353        self.req_q2 = true;
354    }
355
356    /// Set color to blue.
357    pub fn blue(&mut self) {
358        self.req_q1 = true;
359        self.req_q2 = true;
360    }
361
362    /// Enable blinking at the specified interval.
363    pub fn blink_on(&mut self, interval: Duration) {
364        self.enable_blink = true;
365        self.blink_time = interval;
366    }
367
368    /// Disable blinking — outputs follow the requested color directly.
369    pub fn blink_off(&mut self) {
370        self.enable_blink = false;
371    }
372
373    /// Update outputs. Call once per scan cycle.
374    ///
375    /// When blinking is enabled, the outputs toggle between the requested
376    /// color and off at the configured interval. When blinking is disabled,
377    /// the outputs track the requested color directly.
378    pub fn call(&mut self) {
379        // Blink timer — toggles blink_bit when elapsed
380        if self.blink_timer.call(true, self.blink_time) {
381            self.blink_bit = !self.blink_bit;
382            self.blink_timer.reset();
383        }
384
385        let rising = self.rt_blink.call(self.blink_bit);
386        let falling = self.ft_blink.call(self.blink_bit);
387
388        if self.enable_blink {
389            if rising {
390                self.q1 = self.req_q1;
391                self.q2 = self.req_q2;
392            }
393            if falling {
394                self.q1 = false;
395                self.q2 = false;
396            }
397        } else {
398            self.q1 = self.req_q1;
399            self.q2 = self.req_q2;
400        }
401    }
402}
403
404impl Default for Wls15Digital {
405    fn default() -> Self {
406        Self::new()
407    }
408}
409
410#[cfg(test)]
411mod tests {
412    use super::*;
413
414    #[test]
415    fn test_run_mode_off() {
416        let mut light = Wls15RunMode::new();
417        light.steady(Color::Red, ColorIntensity::High);
418        assert_eq!(light.animation, Animation::Steady as u8);
419        light.off();
420        assert_eq!(light.animation, Animation::Off as u8);
421    }
422
423    #[test]
424    fn test_run_mode_alert() {
425        let mut light = Wls15RunMode::new();
426        light.alert(Color::Red, ColorIntensity::High, Speed::Medium);
427        assert_eq!(light.animation, Animation::CenterScroll as u8);
428        assert_eq!(light.color1, Color::Red as u8);
429        assert_eq!(light.color1_intensity, ColorIntensity::High as u8);
430        assert_eq!(light.color2_intensity, ColorIntensity::Off as u8);
431    }
432
433    #[test]
434    fn test_run_mode_knight_rider() {
435        let mut light = Wls15RunMode::new();
436        light.knight_rider(Color::Red);
437        assert_eq!(light.animation, Animation::Bounce as u8);
438        assert_eq!(light.scroll_bounce_style, ScrollStyle::Tail as u8);
439        assert_eq!(light.color1, Color::Red as u8);
440        assert_eq!(light.percent_width_color1, 40);
441    }
442
443    #[test]
444    fn test_run_mode_pulse() {
445        let mut light = Wls15RunMode::new();
446        light.pulse(Color::Green, ColorIntensity::High, Speed::Slow);
447        assert_eq!(light.animation, Animation::TwoColorSweep as u8);
448        assert_eq!(light.color1, Color::Green as u8);
449        assert_eq!(light.speed, Speed::Slow as u8);
450    }
451
452    #[test]
453    fn test_run_mode_spectrum() {
454        let mut light = Wls15RunMode::new();
455        light.spectrum(Speed::Fast);
456        assert_eq!(light.animation, Animation::Spectrum as u8);
457        assert_eq!(light.speed, Speed::Fast as u8);
458    }
459
460    #[test]
461    fn test_digital_colors() {
462        let mut light = Wls15Digital::new();
463
464        light.red();
465        light.call();
466        assert_eq!((light.q1, light.q2), (true, false));
467
468        light.green();
469        light.call();
470        assert_eq!((light.q1, light.q2), (false, true));
471
472        light.blue();
473        light.call();
474        assert_eq!((light.q1, light.q2), (true, true));
475
476        light.off();
477        light.call();
478        assert_eq!((light.q1, light.q2), (false, false));
479    }
480
481    #[test]
482    fn test_digital_blink_off_tracks_color() {
483        let mut light = Wls15Digital::new();
484        light.red();
485        light.blink_off();
486        light.call();
487        assert_eq!((light.q1, light.q2), (true, false));
488    }
489
490    #[test]
491    fn test_enum_values() {
492        assert_eq!(Animation::Off as u8, 0);
493        assert_eq!(Animation::Spectrum as u8, 13);
494        assert_eq!(Color::Green as u8, 0);
495        assert_eq!(Color::Red as u8, 1);
496        assert_eq!(Color::CoolWhite as u8, 20);
497        assert_eq!(ColorIntensity::High as u8, 0);
498        assert_eq!(ColorIntensity::Off as u8, 3);
499        assert_eq!(Direction::Up as u8, 0);
500        assert_eq!(Direction::Down as u8, 1);
501        assert_eq!(PulsePattern::Sos as u8, 3);
502        assert_eq!(ScrollStyle::Tail as u8, 1);
503        assert_eq!(Speed::Fast as u8, 1);
504    }
505}