1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use async_trait::async_trait;
use thiserror::Error;

use crate::models::{self, DeviceConfig};

mod common;

// Device implementation modules

mod dummy;
mod file;
mod ws2812spi;

#[derive(Debug, Error)]
pub enum DeviceError {
    #[error("device not supported: {0}")]
    NotSupported(&'static str),
    #[error("i/o error: {0}")]
    FuturesIo(#[from] futures_io::Error),
    #[error("Format error: {0}")]
    FormatError(#[from] std::fmt::Error),
}

#[async_trait]
trait DeviceImpl: Send {
    /// Set the device implementation's view of the LED data to the given values
    ///
    /// # Panics
    ///
    /// Implementations are allowed to panic if led_data.len() != hardware_led_count. The [Device]
    /// wrapper is responsible for ensuring the given slice is the right size.
    async fn set_led_data(&mut self, led_data: &[models::Color]) -> Result<(), DeviceError>;

    /// Update the device implementation's temporal data. For devices that require regular rewrites
    /// (regardless of actual changes in the LED data), this should return a future that performs
    /// the required work.
    async fn update(&mut self) -> Result<(), DeviceError>;
}

pub struct Device {
    name: String,
    inner: Box<dyn DeviceImpl>,
    led_data: Vec<models::Color>,
    notified_inconsistent_led_data: bool,
}

impl Device {
    fn build_inner(config: models::Device) -> Result<Box<dyn DeviceImpl>, DeviceError> {
        Ok(match config {
            models::Device::Dummy(dummy) => Box::new(dummy::DummyDevice::new(dummy)?),
            models::Device::Ws2812Spi(ws2812spi) => {
                Box::new(ws2812spi::Ws2812SpiDevice::new(ws2812spi)?)
            }
            models::Device::File(file) => Box::new(file::FileDevice::new(file)?),
            other => {
                return Err(DeviceError::NotSupported(other.into()));
            }
        })
    }

    #[instrument(skip(config))]
    pub async fn new(name: &str, config: models::Device) -> Result<Self, DeviceError> {
        let led_count = config.hardware_led_count();
        let inner = Self::build_inner(config)?;

        Ok(Self {
            name: name.to_owned(),
            inner,
            led_data: vec![Default::default(); led_count],
            notified_inconsistent_led_data: false,
        })
    }

    #[instrument(skip(led_data))]
    pub async fn set_led_data(&mut self, led_data: &[models::Color]) -> Result<(), DeviceError> {
        // Store the LED data for updates
        let led_count = led_data.len();
        let hw_led_count = self.led_data.len();

        if led_count == hw_led_count {
            self.led_data.copy_from_slice(led_data);
            self.notified_inconsistent_led_data = false;
        } else if led_count > hw_led_count {
            // Too much data in led_data
            // Take only the slice that fits
            self.led_data.copy_from_slice(&led_data[..hw_led_count]);

            if !self.notified_inconsistent_led_data {
                self.notified_inconsistent_led_data = true;
                warn!(
                    "too much LED data for device: {} extra",
                    led_count - hw_led_count
                );
            }
        } else {
            // Not enough data
            // Take the given data
            self.led_data[..led_count].copy_from_slice(led_data);
            // And pad with zeros
            self.led_data[led_count..].fill(Default::default());

            if !self.notified_inconsistent_led_data {
                self.notified_inconsistent_led_data = true;
                warn!(
                    "not enough LED data for device: {} missing",
                    hw_led_count - led_count
                );
            }
        }

        // Notify device of new write: some devices write immediately
        self.inner.set_led_data(&self.led_data).await
    }

    #[instrument]
    pub async fn update(&mut self) -> Result<(), DeviceError> {
        self.inner.update().await
    }
}

impl std::fmt::Debug for Device {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Device").field("name", &self.name).finish()
    }
}