hyperion/instance/device/
ws2812spi.rs

1use async_trait::async_trait;
2use spidev::{SpiModeFlags, Spidev, SpidevOptions, SpidevTransfer};
3
4use super::{common::*, DeviceError};
5use crate::models;
6
7pub type Ws2812SpiDevice = Rewriter<Ws2812SpiImpl>;
8
9pub struct Ws2812SpiImpl {
10    dev: ImplState,
11    notified_error: bool,
12    buf: Vec<u8>,
13}
14
15const SPI_BYTES_PER_LED: usize = 3 * SPI_BYTES_PER_COLOUR;
16const SPI_BYTES_PER_COLOUR: usize = 4;
17const SPI_FRAME_END_LATCH_BYTES: usize = 116;
18const BITPAIR_TO_BYTE: [u8; 4] = [0b10001000, 0b10001100, 0b11001000, 0b11001100];
19
20enum ImplState {
21    Pending(models::Ws2812Spi),
22    Ready(Spidev),
23}
24
25impl ImplState {
26    fn as_dev(&self) -> Option<&Spidev> {
27        match self {
28            ImplState::Ready(dev) => Some(dev),
29            _ => None,
30        }
31    }
32
33    fn try_init(&mut self) -> Result<&Spidev, DeviceError> {
34        match self {
35            ImplState::Pending(config) => {
36                // Initialize SPI device
37                let mut dev = Spidev::open(&config.output)?;
38                let options = SpidevOptions::new()
39                    .bits_per_word(8)
40                    .max_speed_hz(config.rate as _)
41                    .mode(SpiModeFlags::SPI_MODE_0)
42                    .build();
43                dev.configure(&options)?;
44
45                info!(path = %config.output, "initialized SPI device");
46
47                *self = Self::from(dev);
48                Ok(self.as_dev().unwrap())
49            }
50
51            ImplState::Ready(dev) => Ok(dev),
52        }
53    }
54}
55
56impl From<&models::Ws2812Spi> for ImplState {
57    fn from(value: &models::Ws2812Spi) -> Self {
58        Self::Pending(value.clone())
59    }
60}
61
62impl From<Spidev> for ImplState {
63    fn from(value: Spidev) -> Self {
64        Self::Ready(value)
65    }
66}
67
68#[async_trait]
69impl WritingDevice for Ws2812SpiImpl {
70    type Config = models::Ws2812Spi;
71
72    fn new(config: &models::Ws2812Spi) -> Result<Self, DeviceError> {
73        // Buffer for SPI tranfers
74        let buf = vec![
75            0;
76            config.hardware_led_count as usize * SPI_BYTES_PER_LED
77                + SPI_FRAME_END_LATCH_BYTES
78        ];
79
80        let mut dev = ImplState::from(config);
81
82        // Try to open the device early
83        if let Err(error) = dev.try_init() {
84            warn!(%error, path = %config.output, "failed to initialize SPI device, will try again later");
85        }
86
87        Ok(Self {
88            dev,
89            notified_error: false,
90            buf,
91        })
92    }
93
94    async fn set_let_data(
95        &mut self,
96        config: &Self::Config,
97        led_data: &[models::Color],
98    ) -> Result<(), DeviceError> {
99        // Update buffer
100        let mut ptr = 0;
101        for led in led_data {
102            let (r, g, b) = config.color_order.reorder_from_rgb(*led).into_components();
103            let mut color_bits = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
104
105            for j in (0..SPI_BYTES_PER_LED).rev() {
106                self.buf[ptr + j] = BITPAIR_TO_BYTE[(color_bits & 0x3) as usize];
107                color_bits >>= 2;
108            }
109
110            ptr += SPI_BYTES_PER_LED;
111        }
112
113        for dst in self.buf.iter_mut().skip(ptr) {
114            *dst = 0;
115        }
116
117        if config.invert {
118            for byte in &mut self.buf {
119                *byte = !*byte;
120            }
121        }
122
123        Ok(())
124    }
125
126    async fn write(&mut self) -> Result<(), DeviceError> {
127        // Perform SPI transfer
128        let mut transfer = SpidevTransfer::write(&self.buf);
129
130        // Try writing to the device
131        match self.dev.try_init() {
132            Ok(dev) => {
133                self.notified_error = false;
134                dev.transfer(&mut transfer)?;
135            }
136            Err(err) => {
137                if !self.notified_error {
138                    self.notified_error = true;
139                    error!(error = %err, "failed to initialize SPI device");
140                }
141            }
142        }
143
144        Ok(())
145    }
146}