hyperion/api/boblight/
message.rs

1use std::{convert::TryFrom, str::FromStr};
2
3use thiserror::Error;
4
5use crate::models::{Color, Led};
6
7#[derive(Debug, Error)]
8pub enum DecodeError {
9    #[error("invalid request")]
10    InvalidRequest,
11    #[error("invalid get")]
12    InvalidGet,
13    #[error("invalid set")]
14    InvalidSet,
15    #[error("invalid light param")]
16    InvalidLightParam,
17    #[error("invalid priority")]
18    InvalidPriority,
19    #[error("invalid index")]
20    InvalidIndex,
21    #[error("invalid color")]
22    InvalidColor,
23    #[error("not enough data")]
24    NotEnoughData,
25}
26
27#[derive(Debug)]
28pub enum GetArg {
29    Version,
30    Lights,
31}
32
33impl TryFrom<&[&str]> for GetArg {
34    type Error = DecodeError;
35
36    fn try_from(value: &[&str]) -> Result<Self, Self::Error> {
37        match value.first().copied() {
38            Some("version") => Ok(Self::Version),
39            Some("lights") => Ok(Self::Lights),
40            _ => Err(DecodeError::InvalidGet),
41        }
42    }
43}
44
45#[derive(Debug)]
46pub struct LightParam {
47    pub index: usize,
48    pub data: LightParamData,
49}
50
51impl TryFrom<&[&str]> for LightParam {
52    type Error = DecodeError;
53
54    fn try_from(value: &[&str]) -> Result<Self, Self::Error> {
55        let index = value
56            .first()
57            .and_then(|s| s.parse().ok())
58            .ok_or(DecodeError::InvalidIndex)?;
59
60        if value.len() <= 1 {
61            return Err(DecodeError::NotEnoughData);
62        }
63
64        Ok(Self {
65            index,
66            data: LightParamData::try_from(&value[1..])?,
67        })
68    }
69}
70
71#[derive(Debug)]
72pub enum LightParamData {
73    Color(Color),
74    Speed,
75    Interpolation,
76    Use,
77    SingleChange,
78}
79
80impl TryFrom<&[&str]> for LightParamData {
81    type Error = DecodeError;
82
83    fn try_from(value: &[&str]) -> Result<Self, Self::Error> {
84        match value.first().copied() {
85            Some("color") => match value.get(1).copied() {
86                Some("rgb") => {
87                    let r = value
88                        .get(2)
89                        .and_then(|s| s.parse().ok())
90                        .ok_or(DecodeError::InvalidColor)?;
91                    let g = value
92                        .get(3)
93                        .and_then(|s| s.parse().ok())
94                        .ok_or(DecodeError::InvalidColor)?;
95                    let b = value
96                        .get(4)
97                        .and_then(|s| s.parse().ok())
98                        .ok_or(DecodeError::InvalidColor)?;
99
100                    Ok(Self::Color(Color::new(r, g, b)))
101                }
102                _ => Err(DecodeError::InvalidColor),
103            },
104            Some("speed") => Ok(Self::Speed),
105            Some("interpolation") => Ok(Self::Interpolation),
106            Some("use") => Ok(Self::Use),
107            Some("singlechange") => Ok(Self::SingleChange),
108            _ => Err(DecodeError::InvalidLightParam),
109        }
110    }
111}
112
113#[derive(Debug)]
114pub enum SetArg {
115    Light(LightParam),
116    Priority(i32),
117}
118
119impl TryFrom<&[&str]> for SetArg {
120    type Error = DecodeError;
121
122    fn try_from(value: &[&str]) -> Result<Self, Self::Error> {
123        match value.first().copied() {
124            Some("light") => {
125                if value.len() <= 1 {
126                    return Err(DecodeError::NotEnoughData);
127                }
128
129                Ok(Self::Light(LightParam::try_from(&value[1..])?))
130            }
131            Some("priority") => {
132                let priority = value
133                    .get(1)
134                    .and_then(|s| s.parse().ok())
135                    .ok_or(DecodeError::InvalidPriority)?;
136
137                Ok(Self::Priority(priority))
138            }
139            _ => Err(DecodeError::InvalidSet),
140        }
141    }
142}
143
144#[derive(Debug)]
145pub enum BoblightRequest {
146    Hello,
147    Ping,
148    Get(GetArg),
149    Set(SetArg),
150    Sync,
151}
152
153impl FromStr for BoblightRequest {
154    type Err = DecodeError;
155
156    fn from_str(s: &str) -> Result<Self, Self::Err> {
157        let spans: Vec<_> = s
158            .trim()
159            .split(' ')
160            .filter(|s| !s.trim().is_empty())
161            .collect();
162
163        match spans.first().copied() {
164            Some("hello") => Ok(Self::Hello),
165            Some("ping") => Ok(Self::Ping),
166            Some("get") => {
167                if spans.len() <= 1 {
168                    return Err(DecodeError::NotEnoughData);
169                }
170
171                Ok(Self::Get(GetArg::try_from(&spans[1..])?))
172            }
173            Some("set") => {
174                if spans.len() <= 1 {
175                    return Err(DecodeError::NotEnoughData);
176                }
177
178                Ok(Self::Set(SetArg::try_from(&spans[1..])?))
179            }
180            Some("sync") => Ok(Self::Sync),
181            _ => Err(DecodeError::InvalidRequest),
182        }
183    }
184}
185
186#[derive(Debug)]
187pub enum BoblightResponse {
188    Hello,
189    Ping,
190    Version,
191    Lights { leds: Vec<Led> },
192}
193
194impl std::fmt::Display for BoblightResponse {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        match self {
197            BoblightResponse::Hello => write!(f, "hello"),
198            BoblightResponse::Ping => write!(f, "ping 1"),
199            BoblightResponse::Version => write!(f, "version 5"),
200            BoblightResponse::Lights { leds } => {
201                let n = leds.len();
202                if n > 0 {
203                    writeln!(f, "lights {}", n)?;
204
205                    for (i, led) in leds.iter().enumerate() {
206                        write!(
207                            f,
208                            "light {:03} scan {} {} {} {}",
209                            i,
210                            led.hmin * 100.,
211                            led.hmax * 100.,
212                            led.vmin * 100.,
213                            led.vmax * 100.
214                        )?;
215
216                        if i < n - 1 {
217                            writeln!(f)?;
218                        }
219                    }
220
221                    Ok(())
222                } else {
223                    write!(f, "lights 0")
224                }
225            }
226        }
227    }
228}