hyperion/instance/
device.rs

1use async_trait::async_trait;
2use thiserror::Error;
3
4use crate::models::{self, DeviceConfig};
5
6mod common;
7
8// Device implementation modules
9
10mod dummy;
11mod file;
12mod ws2812spi;
13
14#[derive(Debug, Error)]
15pub enum DeviceError {
16    #[error("device not supported: {0}")]
17    NotSupported(&'static str),
18    #[error("i/o error: {0}")]
19    FuturesIo(#[from] futures_io::Error),
20    #[error("Format error: {0}")]
21    FormatError(#[from] std::fmt::Error),
22}
23
24#[async_trait]
25trait DeviceImpl: Send {
26    /// Set the device implementation's view of the LED data to the given values
27    ///
28    /// # Panics
29    ///
30    /// Implementations are allowed to panic if led_data.len() != hardware_led_count. The [Device]
31    /// wrapper is responsible for ensuring the given slice is the right size.
32    async fn set_led_data(&mut self, led_data: &[models::Color]) -> Result<(), DeviceError>;
33
34    /// Update the device implementation's temporal data. For devices that require regular rewrites
35    /// (regardless of actual changes in the LED data), this should return a future that performs
36    /// the required work.
37    async fn update(&mut self) -> Result<(), DeviceError>;
38}
39
40pub struct Device {
41    name: String,
42    inner: Box<dyn DeviceImpl>,
43    led_data: Vec<models::Color>,
44    notified_inconsistent_led_data: bool,
45}
46
47impl Device {
48    fn build_inner(config: models::Device) -> Result<Box<dyn DeviceImpl>, DeviceError> {
49        Ok(match config {
50            models::Device::Dummy(dummy) => Box::new(dummy::DummyDevice::new(dummy)?),
51            models::Device::Ws2812Spi(ws2812spi) => {
52                Box::new(ws2812spi::Ws2812SpiDevice::new(ws2812spi)?)
53            }
54            models::Device::File(file) => Box::new(file::FileDevice::new(file)?),
55            other => {
56                return Err(DeviceError::NotSupported(other.into()));
57            }
58        })
59    }
60
61    #[instrument(skip(config))]
62    pub async fn new(name: &str, config: models::Device) -> Result<Self, DeviceError> {
63        let led_count = config.hardware_led_count();
64        let inner = Self::build_inner(config)?;
65
66        Ok(Self {
67            name: name.to_owned(),
68            inner,
69            led_data: vec![Default::default(); led_count],
70            notified_inconsistent_led_data: false,
71        })
72    }
73
74    #[instrument(skip(led_data))]
75    pub async fn set_led_data(&mut self, led_data: &[models::Color]) -> Result<(), DeviceError> {
76        // Store the LED data for updates
77        let led_count = led_data.len();
78        let hw_led_count = self.led_data.len();
79
80        if led_count == hw_led_count {
81            self.led_data.copy_from_slice(led_data);
82            self.notified_inconsistent_led_data = false;
83        } else if led_count > hw_led_count {
84            // Too much data in led_data
85            // Take only the slice that fits
86            self.led_data.copy_from_slice(&led_data[..hw_led_count]);
87
88            if !self.notified_inconsistent_led_data {
89                self.notified_inconsistent_led_data = true;
90                warn!(
91                    "too much LED data for device: {} extra",
92                    led_count - hw_led_count
93                );
94            }
95        } else {
96            // Not enough data
97            // Take the given data
98            self.led_data[..led_count].copy_from_slice(led_data);
99            // And pad with zeros
100            self.led_data[led_count..].fill(Default::default());
101
102            if !self.notified_inconsistent_led_data {
103                self.notified_inconsistent_led_data = true;
104                warn!(
105                    "not enough LED data for device: {} missing",
106                    hw_led_count - led_count
107                );
108            }
109        }
110
111        // Notify device of new write: some devices write immediately
112        self.inner.set_led_data(&self.led_data).await
113    }
114
115    #[instrument]
116    pub async fn update(&mut self) -> Result<(), DeviceError> {
117        self.inner.update().await
118    }
119}
120
121impl std::fmt::Debug for Device {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        f.debug_struct("Device").field("name", &self.name).finish()
124    }
125}