blinksy/
control.rs

1//! # Control System
2//!
3//! [`Control`] is the central control system for Blinksy: connecting a layout, pattern,
4//! and driver together to form a complete LED control pipeline.
5//!
6//! As [`Control`] has a complex generic type signature, [`ControlBuilder`] is a builder to help
7//! you create [`Control`] instances.
8
9use core::marker::PhantomData;
10
11use crate::{
12    color::{ColorCorrection, FromColor},
13    dimension::{Dim1d, Dim2d, LayoutForDim},
14    driver::Driver as DriverTrait,
15    pattern::Pattern as PatternTrait,
16};
17
18/// Central LED control system.
19///
20/// A [`Control`] is made up of:
21///
22/// - A [`layout`](crate::layout)
23/// - A [`pattern`](crate::pattern)
24/// - A [`driver`](crate::driver)
25///
26/// You can use [`Control`] to
27///
28/// - Set a global brightness
29/// - Set a global color correction.
30/// - Send a frame of colors from the pattern to the driver.
31///
32/// Tip: Use [`ControlBuilder`] to build your [`Control`] struct.
33///
34/// # Type Parameters
35///
36/// * `Dim` - The dimension marker ([`Dim1d`] or [`Dim2d`])
37/// * `Layout` - The [`layout`](crate::layout) type
38/// * `Pattern` - The [`pattern`](crate::pattern) type
39/// * `Driver` - The LED [`driver`](crate::driver) type
40///
41/// # Example
42///
43/// ```rust,ignore
44/// use blinksy::{
45///     ControlBuilder,
46///     layout1d,
47///     patterns::rainbow::{Rainbow, RainbowParams}
48/// };
49///
50/// // Define a 1d layout of 60 LEDs
51/// layout1d!(Layout, 60);
52///
53/// // Create a control system
54/// let mut control = ControlBuilder::new_1d()
55///     .with_layout::<Layout>()
56///     .with_pattern::<Rainbow>(RainbowParams::default())
57///     .with_driver(/* LED driver */)
58///     .build();
59///
60/// // Use the control system
61/// control.set_brightness(0.5);
62///
63/// // Main control loop
64/// loop {
65///     control.tick(/* current time in milliseconds */).unwrap();
66/// }
67/// ```
68pub struct Control<Dim, Layout, Pattern, Driver> {
69    dim: PhantomData<Dim>,
70    layout: PhantomData<Layout>,
71    pattern: Pattern,
72    driver: Driver,
73    brightness: f32,
74    correction: ColorCorrection,
75}
76
77impl<Dim, Layout, Pattern, Driver> Control<Dim, Layout, Pattern, Driver> {
78    /// Creates a new control system.
79    ///
80    /// # Arguments
81    ///
82    /// * `pattern` - The pattern to use
83    /// * `driver` - The LED driver to use
84    ///
85    /// # Returns
86    ///
87    /// A new Control instance with default brightness
88    pub fn new(pattern: Pattern, driver: Driver) -> Self {
89        Self {
90            dim: PhantomData,
91            layout: PhantomData,
92            pattern,
93            driver,
94            brightness: 1.,
95            correction: ColorCorrection::default(),
96        }
97    }
98
99    /// Sets the overall brightness level.
100    ///
101    /// # Arguments
102    ///
103    /// * `brightness` - Brightness level from 0.0 (off) to 1.0 (full)
104    pub fn set_brightness(&mut self, brightness: f32) {
105        self.brightness = brightness;
106    }
107
108    /// Sets a color correction.
109    ///
110    /// # Arguments
111    ///
112    /// * `correction` - Color correction factors
113    pub fn set_color_correction(&mut self, correction: ColorCorrection) {
114        self.correction = correction;
115    }
116}
117
118impl<Dim, Layout, Pattern, Driver> Control<Dim, Layout, Pattern, Driver>
119where
120    Layout: LayoutForDim<Dim>,
121    Pattern: PatternTrait<Dim, Layout>,
122    Driver: DriverTrait,
123    Driver::Color: FromColor<Pattern::Color>,
124{
125    /// Updates the LED state based on the current time.
126    ///
127    /// This method:
128    /// 1. Calls the pattern to generate colors
129    /// 2. Passes the colors and brightness to the driver
130    ///
131    /// # Arguments
132    ///
133    /// * `time_in_ms` - Current time in milliseconds
134    ///
135    /// # Returns
136    ///
137    /// Result indicating success or an error from the driver
138    pub fn tick(&mut self, time_in_ms: u64) -> Result<(), Driver::Error> {
139        let pixels = self.pattern.tick(time_in_ms);
140        self.driver.write(pixels, self.brightness, self.correction)
141    }
142}
143
144///
145/// The builder allows your to build up your [`Control`] system one-by-one
146/// and handles the combination of generic types and contraints that [`Control`] expects.
147pub struct ControlBuilder<Dim, Layout, Pattern, Driver> {
148    dim: PhantomData<Dim>,
149    layout: PhantomData<Layout>,
150    pattern: Pattern,
151    driver: Driver,
152}
153
154impl ControlBuilder<(), (), (), ()> {
155    /// Starts building a one-dimensional control system.
156    ///
157    /// # Returns
158    ///
159    /// A builder initialized for 1D
160    pub fn new_1d() -> ControlBuilder<Dim1d, (), (), ()> {
161        ControlBuilder {
162            dim: PhantomData,
163            layout: PhantomData,
164            pattern: (),
165            driver: (),
166        }
167    }
168}
169
170impl ControlBuilder<(), (), (), ()> {
171    /// Starts building a two-dimensional control system.
172    ///
173    /// # Returns
174    ///
175    /// A builder initialized for 2D
176    pub fn new_2d() -> ControlBuilder<Dim2d, (), (), ()> {
177        ControlBuilder {
178            dim: PhantomData,
179            layout: PhantomData,
180            pattern: (),
181            driver: (),
182        }
183    }
184}
185
186impl<Dim, Pattern, Driver> ControlBuilder<Dim, (), Pattern, Driver> {
187    /// Specifies the layout type for the control system.
188    ///
189    /// # Type Parameters
190    ///
191    /// * `Layout` - The layout type implementing Layout that corresponds to Dim
192    ///
193    /// # Returns
194    ///
195    /// Builder with layout type specified
196    pub fn with_layout<Layout>(self) -> ControlBuilder<Dim, Layout, Pattern, Driver>
197    where
198        Layout: LayoutForDim<Dim>,
199    {
200        ControlBuilder {
201            dim: PhantomData,
202            layout: PhantomData,
203            pattern: self.pattern,
204            driver: self.driver,
205        }
206    }
207}
208
209impl<Dim, Layout, Driver> ControlBuilder<Dim, Layout, (), Driver>
210where
211    Layout: LayoutForDim<Dim>,
212{
213    /// Specifies the pattern and its parameters.
214    ///
215    /// # Type Parameters
216    ///
217    /// * `Pattern` - The pattern type implementing Pattern<Dim, Layout>
218    ///
219    /// # Arguments
220    ///
221    /// * `params` - The pattern parameters
222    ///
223    /// # Returns
224    ///
225    /// Builder with pattern specified
226    pub fn with_pattern<Pattern>(
227        self,
228        params: Pattern::Params,
229    ) -> ControlBuilder<Dim, Layout, Pattern, Driver>
230    where
231        Pattern: PatternTrait<Dim, Layout>,
232    {
233        let pattern = Pattern::new(params);
234        ControlBuilder {
235            dim: self.dim,
236            layout: self.layout,
237            pattern,
238            driver: self.driver,
239        }
240    }
241}
242
243impl<Dim, Layout, Pattern> ControlBuilder<Dim, Layout, Pattern, ()> {
244    /// Specifies the LED driver for the control system.
245    ///
246    /// # Arguments
247    ///
248    /// * `driver` - The LED driver instance
249    ///
250    /// # Returns
251    ///
252    /// Builder with driver specified
253    pub fn with_driver<Driver>(self, driver: Driver) -> ControlBuilder<Dim, Layout, Pattern, Driver>
254    where
255        Driver: DriverTrait,
256    {
257        ControlBuilder {
258            dim: self.dim,
259            layout: self.layout,
260            pattern: self.pattern,
261            driver,
262        }
263    }
264}
265
266impl<Dim, Layout, Pattern, Driver> ControlBuilder<Dim, Layout, Pattern, Driver>
267where
268    Layout: LayoutForDim<Dim>,
269    Pattern: PatternTrait<Dim, Layout>,
270    Driver: DriverTrait,
271    Driver::Color: FromColor<Pattern::Color>,
272{
273    /// Builds the final [`Control`] struct.
274    ///
275    /// # Returns
276    ///
277    /// A fully configured Control instance
278    pub fn build(self) -> Control<Dim, Layout, Pattern, Driver> {
279        Control::new(self.pattern, self.driver)
280    }
281}