hyperion/api/
boblight.rs

1use std::sync::Arc;
2
3use thiserror::Error;
4
5use crate::{
6    global::{InputMessage, InputMessageData, InputSourceHandle, Message, PriorityGuard},
7    instance::{InstanceHandle, InstanceHandleError},
8    models::Color,
9};
10
11pub mod message;
12use message::{BoblightRequest, BoblightResponse};
13
14#[derive(Debug, Error)]
15pub enum BoblightApiError {
16    #[error("error broadcasting update: {0}")]
17    Broadcast(#[from] tokio::sync::mpsc::error::SendError<InputMessage>),
18    #[error("missing command data in protobuf frame")]
19    MissingCommand,
20    #[error("invalid instance")]
21    InvalidInstance(#[from] InstanceHandleError),
22}
23
24pub struct ClientConnection {
25    handle: InputSourceHandle<InputMessage>,
26    priority_guard: PriorityGuard,
27    led_colors: Vec<Color>,
28    priority: i32,
29    instance: InstanceHandle,
30}
31
32impl ClientConnection {
33    pub fn new(
34        handle: InputSourceHandle<InputMessage>,
35        led_count: usize,
36        instance: InstanceHandle,
37    ) -> Self {
38        let priority_guard = PriorityGuard::new_mpsc(instance.input_channel().clone(), &handle);
39
40        Self {
41            handle,
42            priority_guard,
43            led_colors: vec![Color::default(); led_count],
44            priority: 128,
45            instance,
46        }
47    }
48
49    async fn set_priority(&mut self, priority: i32) {
50        let new_priority = if !(128..254).contains(&priority) {
51            self.instance
52                .current_priorities()
53                .await
54                .map(|priorities| {
55                    let mut used_priorities = priorities
56                        .iter()
57                        .map(|p| p.priority)
58                        .skip_while(|p| *p <= 128)
59                        .peekable();
60
61                    for i in 128..255 {
62                        loop {
63                            match used_priorities.peek().cloned() {
64                                Some(used) if used == i => {
65                                    // Current value is used, look at the next one
66                                    used_priorities.next();
67                                    break;
68                                }
69                                Some(used) if used < i => {
70                                    used_priorities.next();
71                                    continue;
72                                }
73                                _ => {
74                                    return i;
75                                }
76                            }
77                        }
78                    }
79
80                    128
81                })
82                .unwrap_or(128)
83        } else {
84            priority
85        };
86
87        self.priority = new_priority;
88        self.priority_guard.set_priority(Some(new_priority));
89    }
90
91    async fn sync(&self) -> Result<(), BoblightApiError> {
92        Ok(self
93            .instance
94            .send(InputMessage::new(
95                self.handle.id(),
96                crate::component::ComponentName::BoblightServer,
97                InputMessageData::LedColors {
98                    priority: self.priority,
99                    duration: None,
100                    led_colors: Arc::new(self.led_colors.clone()),
101                },
102            ))
103            .await?)
104    }
105
106    #[instrument(skip(request))]
107    pub async fn handle_request(
108        &mut self,
109        request: BoblightRequest,
110    ) -> Result<Option<BoblightResponse>, BoblightApiError> {
111        match request {
112            BoblightRequest::Hello => Ok(Some(BoblightResponse::Hello)),
113            BoblightRequest::Ping => Ok(Some(BoblightResponse::Ping)),
114            BoblightRequest::Get(get) => match get {
115                message::GetArg::Version => Ok(Some(BoblightResponse::Version)),
116                message::GetArg::Lights => Ok(Some(BoblightResponse::Lights {
117                    leds: self.instance.config().await?.leds.leds.clone(),
118                })),
119            },
120            BoblightRequest::Set(set) => {
121                match set {
122                    message::SetArg::Light(message::LightParam { index, data }) => {
123                        if let message::LightParamData::Color(color) = data {
124                            if let Some(color_mut) = self.led_colors.get_mut(index) {
125                                *color_mut = color;
126
127                                if index == self.led_colors.len() - 1 {
128                                    self.sync().await?;
129                                }
130                            }
131                        }
132                    }
133                    message::SetArg::Priority(priority) => {
134                        self.set_priority(priority).await;
135                    }
136                }
137
138                Ok(None)
139            }
140            BoblightRequest::Sync => {
141                self.sync().await?;
142
143                Ok(None)
144            }
145        }
146    }
147}
148
149impl std::fmt::Debug for ClientConnection {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        f.debug_struct("ClientConnection")
152            .field("instance", &self.instance.id())
153            .field("source", &format!("{}", &*self.handle))
154            .finish()
155    }
156}