hyperion/api/boblight/
message.rs1use 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}