hyperion/instance/
device.rs1use async_trait::async_trait;
2use thiserror::Error;
3
4use crate::models::{self, DeviceConfig};
5
6mod common;
7
8mod 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 async fn set_led_data(&mut self, led_data: &[models::Color]) -> Result<(), DeviceError>;
33
34 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 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 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 self.led_data[..led_count].copy_from_slice(led_data);
99 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 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}