smart_leds_matrix/
lib.rs

1//! # Smart Leds Matrix
2//!
3//! This is a library that adapts [smart-leds](https://crates.io/crates/smart-leds) driver implementations to the
4//! [embedded-graphics](https://docs.rs/embedded-graphics/latest/embedded_graphics/) crate by wrapping the LED
5//! driver into a `Drawable` display target.
6//!
7
8#![no_std]
9
10use embedded_graphics_core::{
11    draw_target::DrawTarget,
12    geometry::{OriginDimensions, Size},
13    pixelcolor::{Rgb888, RgbColor},
14    Pixel,
15};
16
17use smart_leds::{brightness, gamma, SmartLedsWrite, RGB8};
18
19pub mod layout;
20use layout::Layout;
21
22/// The wrapper for the LED driver.
23///
24/// This receives the `SmartLedsWriter` trait implementations along with a
25/// `Transformation` that describes the pixels mapping between the LED
26/// strip placement and the matrix's x y coordinates.
27pub struct SmartLedMatrix<T, L, const N: usize> {
28    writer: T,
29    layout: L,
30    content: [RGB8; N],
31    brightness: u8,
32}
33
34impl<T, L, const N: usize> SmartLedMatrix<T, L, N> {
35    pub fn set_brightness(&mut self, new_brightness: u8) {
36        self.brightness = new_brightness;
37    }
38
39    pub fn brightness(&self) -> u8 {
40        self.brightness
41    }
42}
43
44impl<T: SmartLedsWrite, L: Layout, const N: usize> SmartLedMatrix<T, L, N>
45where
46    <T as SmartLedsWrite>::Color: From<RGB8>,
47{
48    pub fn new(writer: T, layout: L) -> Self {
49        Self {
50            writer,
51            layout,
52            content: [RGB8::default(); N],
53            brightness: 255,
54        }
55    }
56
57    pub fn flush(&mut self) -> Result<(), T::Error> {
58        let iter = brightness(self.content.as_slice().iter().cloned(), self.brightness);
59        self.writer.write(iter)
60    }
61    pub fn flush_with_gamma(&mut self) -> Result<(), T::Error> {
62        let iter = brightness(
63            gamma(self.content.as_slice().iter().cloned()),
64            self.brightness,
65        );
66        self.writer.write(iter)
67    }
68}
69
70impl<T: SmartLedsWrite, L: Layout, const N: usize> DrawTarget for SmartLedMatrix<T, L, N>
71where
72    <T as SmartLedsWrite>::Color: From<RGB8>,
73{
74    type Color = Rgb888;
75    type Error = core::convert::Infallible;
76
77    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
78    where
79        I: IntoIterator<Item = Pixel<Rgb888>>,
80    {
81        for Pixel(pos, color) in pixels {
82            if let Some(t) = self
83                .layout
84                .map(pos)
85                .and_then(|index| self.content.get_mut(index))
86            {
87                *t = RGB8::new(color.r(), color.g(), color.b());
88            }
89        }
90
91        Ok(())
92    }
93}
94
95impl<T, L: Layout, const N: usize> OriginDimensions for SmartLedMatrix<T, L, N> {
96    fn size(&self) -> Size {
97        self.layout.size()
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    use crate::layout::Rectangular;
106    use embedded_graphics_core::{geometry::Point, prelude::Dimensions, primitives::PointsIter};
107
108    struct MockWriter<'a, const N: usize> {
109        content: &'a mut [RGB8; N],
110    }
111
112    impl<'a, const N: usize> SmartLedsWrite for MockWriter<'a, N> {
113        type Error = ();
114        type Color = RGB8;
115
116        fn write<T, I>(&mut self, iterator: T) -> Result<(), Self::Error>
117        where
118            T: IntoIterator<Item = I>,
119            I: Into<Self::Color>,
120        {
121            let mut i = 0;
122            for color in iterator {
123                self.content[i] = color.into();
124                i += 1;
125            }
126            Ok(())
127        }
128    }
129
130    fn get64pixels(color: Rgb888) -> [Pixel<Rgb888>; 64] {
131        let mut pixels: [Pixel<Rgb888>; 64] = [Pixel(Point::new(0, 0), Rgb888::BLACK); 64];
132        for x in 0..8 {
133            for y in 0..8 {
134                pixels[x * 8 + y] = Pixel(Point::new(x as i32, y as i32), color);
135            }
136        }
137        pixels
138    }
139
140    #[test]
141    fn test_y_inversion() {
142        let content = &mut [RGB8::new(0, 0, 0); 64];
143        let writer = MockWriter { content };
144        let mut matrix =
145            SmartLedMatrix::<_, _, { 8 * 8 }>::new(writer, Rectangular::new_invert_y(8, 8));
146        let mut pixels = get64pixels(Rgb888::BLACK);
147
148        pixels[0] = Pixel(Point::new(0, 0), Rgb888::WHITE);
149
150        matrix.draw_iter(pixels).unwrap();
151        matrix.flush().unwrap();
152
153        for i in 0..64 {
154            if i == 56 {
155                assert_eq!(
156                    content[i],
157                    RGB8::new(255, 255, 255),
158                    r#"expected a white pixel after inversion"#
159                );
160                continue;
161            }
162            assert_eq!(content[i], RGB8::new(0, 0, 0), r#"expected black pixel"#);
163        }
164    }
165
166    #[test]
167    fn test_identity() {
168        let content = &mut [RGB8::new(0, 0, 0); 64];
169        let writer = MockWriter { content };
170        let mut matrix = SmartLedMatrix::<_, _, { 8 * 8 }>::new(writer, Rectangular::new(8, 8));
171        let mut pixels = get64pixels(Rgb888::BLACK);
172
173        pixels[0] = Pixel(Point::new(0, 0), Rgb888::WHITE);
174
175        matrix.draw_iter(pixels).unwrap();
176        matrix.flush().unwrap();
177
178        for i in 0..64 {
179            if i == 0 {
180                assert_eq!(
181                    content[i],
182                    RGB8::new(255, 255, 255),
183                    r#"expected a white pixel on it's original place"#
184                );
185                continue;
186            }
187            assert_eq!(content[i], RGB8::new(0, 0, 0), r#"expected black pixel"#);
188        }
189    }
190
191    #[test]
192    fn test_brightness() {
193        let content = &mut [RGB8::new(0, 0, 0); 64];
194        let writer = MockWriter { content };
195        let mut matrix = SmartLedMatrix::<_, _, { 8 * 8 }>::new(writer, Rectangular::new(8, 8));
196        let pixels = get64pixels(Rgb888::WHITE);
197
198        assert_eq!(
199            matrix.brightness(),
200            255,
201            r#"initial brightness shall be set to max (255)"#
202        );
203        matrix.set_brightness(10);
204        assert_eq!(matrix.brightness(), 10, r#"brightness shall be set to 10"#);
205
206        matrix.draw_iter(pixels).unwrap();
207        matrix.flush().unwrap();
208
209        for i in 0..64 {
210            assert_eq!(content[i], RGB8::new(10, 10, 10), r#"expected black pixel"#);
211        }
212    }
213
214    #[test]
215    fn custom_layout() {
216        struct CustomLayout;
217
218        /// Custom layout with a different number of LEDs per row.
219        ///
220        /// # LED indices:
221        /// ```text
222        /// 0 1 2
223        /// 3 4 5 6
224        /// 7 8 9 10 11
225        /// ```
226        impl Layout for CustomLayout {
227            fn map(&self, p: Point) -> Option<usize> {
228                const LED_PER_ROW: [u8; 3] = [3, 4, 5];
229
230                if p.y < 0
231                    || p.y >= LED_PER_ROW.len() as i32
232                    || p.x < 0
233                    || p.x >= i32::from(LED_PER_ROW[p.y as usize])
234                {
235                    return None;
236                }
237
238                let mut index = 0;
239                for y in 0..p.y as usize {
240                    index += usize::from(LED_PER_ROW[y]);
241                }
242                index += p.x as usize;
243
244                Some(index)
245            }
246
247            fn size(&self) -> Size {
248                Size::new(5, 3)
249            }
250        }
251
252        let content = &mut [RGB8::new(0, 0, 0); 3 + 4 + 5];
253        let writer = MockWriter { content };
254        let mut matrix = SmartLedMatrix::<_, _, { 3 + 4 + 5 }>::new(writer, CustomLayout);
255
256        // draw vertical line of red pixels on the left edge
257        let mut bb = matrix.bounding_box();
258        bb.size.width = 1;
259        matrix
260            .draw_iter(bb.points().map(|p| Pixel(p, Rgb888::RED)))
261            .unwrap();
262
263        matrix.flush().unwrap();
264
265        const B: RGB8 = RGB8::new(0, 0, 0);
266        const R: RGB8 = RGB8::new(255, 0, 0);
267        assert_eq!(
268            content,
269            &[
270                R, B, B, //
271                R, B, B, B, //
272                R, B, B, B, B, //
273            ]
274        );
275    }
276}