1#![allow(clippy::needless_update)]
2
3use crate::resource::{self, Adjust, Alert, ColorMode, Effect};
4use crate::Color;
5use derive_setters::Setters;
6use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
7
8#[derive(Clone, Debug, PartialEq, Deserialize)]
10pub struct Light {
11 #[serde(skip)]
13 pub id: String,
14 pub name: String,
16 #[serde(rename = "type")]
18 pub kind: String,
19 pub state: State,
21 #[serde(rename = "modelid")]
23 pub model_id: String,
24 #[serde(rename = "uniqueid")]
26 pub unique_id: String,
27 #[serde(rename = "productid")]
29 pub product_id: Option<String>,
30 #[serde(rename = "productname")]
32 pub product_name: Option<String>,
33 #[serde(rename = "manufacturername")]
35 pub manufacturer_name: Option<String>,
36 #[serde(rename = "swversion")]
38 pub software_version: String,
39 #[cfg(not(feature = "old-api"))]
41 #[serde(rename = "swupdate")]
42 pub software_update: SoftwareUpdate,
43 #[cfg(not(feature = "old-api"))]
45 pub config: Config,
46 #[cfg(not(feature = "old-api"))]
48 pub capabilities: Capabilities,
49}
50
51impl Light {
52 pub(crate) fn with_id(self, id: String) -> Self {
53 Self { id, ..self }
54 }
55}
56
57impl resource::Resource for Light {}
58
59#[derive(Clone, Debug, PartialEq, Deserialize)]
61pub struct State {
62 pub on: Option<bool>,
64 #[serde(rename = "bri")]
68 pub brightness: Option<u8>,
69 pub hue: Option<u16>,
73 #[serde(rename = "sat")]
77 pub saturation: Option<u8>,
78 #[serde(rename = "xy")]
80 pub color_space_coordinates: Option<(f32, f32)>,
81 #[serde(rename = "ct")]
83 pub color_temperature: Option<u16>,
84 pub alert: Option<Alert>,
86 pub effect: Option<Effect>,
88 #[serde(rename = "colormode")]
90 pub color_mode: Option<ColorMode>,
91 pub reachable: bool,
93}
94
95#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
97pub struct SoftwareUpdate {
98 pub state: SoftwareUpdateState,
100 #[serde(rename = "lastinstall")]
102 pub last_install: Option<chrono::NaiveDateTime>,
103}
104
105#[non_exhaustive]
111#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Deserialize)]
112#[serde(rename_all = "lowercase")]
113pub enum SoftwareUpdateState {
114 Error,
116 Installing,
118 NoUpdates,
120 NotUpdatable,
122 Transferring,
124 ReadyToInstall,
126 }
128
129#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
131pub struct Config {
132 #[serde(rename = "archetype")]
134 pub arche_type: String,
135 pub function: String,
137 pub direction: String,
139 pub startup: Option<StartupConfig>,
141}
142
143#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
145pub struct StartupConfig {
146 pub mode: String,
148 pub configured: bool,
150}
151
152#[derive(Clone, Debug, PartialEq, Deserialize)]
154pub struct Capabilities {
155 pub certified: bool,
157 pub control: ControlCapabilities,
159 pub streaming: StreamingCapabilities,
161}
162
163#[derive(Clone, Debug, PartialEq, Deserialize)]
165pub struct ControlCapabilities {
166 #[serde(rename = "mindimlevel")]
168 pub min_dimlevel: Option<usize>,
169 #[serde(rename = "maxlumen")]
171 pub max_lumen: Option<usize>,
172 #[serde(rename = "colorgamut")]
174 pub color_gamut: Option<Vec<(f32, f32)>>,
175 #[serde(rename = "colorgamuttype")]
177 pub color_gamut_type: Option<String>,
178 #[serde(rename = "ct")]
180 pub color_temperature: Option<ColorTemperatureCapabilities>,
181}
182
183#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
185pub struct ColorTemperatureCapabilities {
186 pub min: usize,
188 pub max: usize,
190}
191
192#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
194pub struct StreamingCapabilities {
195 pub renderer: bool,
197 pub proxy: bool,
199}
200
201#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Setters)]
203#[setters(strip_option, prefix = "with_")]
204pub struct AttributeModifier {
205 #[serde(skip_serializing_if = "Option::is_none")]
207 pub name: Option<String>,
208}
209
210impl AttributeModifier {
211 pub fn new() -> Self {
213 Self::default()
214 }
215}
216
217impl resource::Modifier for AttributeModifier {
218 type Id = String;
219 fn url_suffix(id: Self::Id) -> String {
220 format!("lights/{}", id)
221 }
222}
223
224#[derive(Clone, Debug, Default, PartialEq, Serialize, Setters)]
234#[setters(strip_option, prefix = "with_")]
235pub struct StaticStateModifier {
236 #[serde(skip_serializing_if = "Option::is_none")]
238 pub on: Option<bool>,
239 #[serde(skip_serializing_if = "Option::is_none", rename = "bri")]
241 pub brightness: Option<u8>,
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub hue: Option<u16>,
245 #[serde(skip_serializing_if = "Option::is_none", rename = "sat")]
247 pub saturation: Option<u8>,
248 #[serde(skip_serializing_if = "Option::is_none", rename = "xy")]
250 pub color_space_coordinates: Option<(f32, f32)>,
251 #[serde(skip_serializing_if = "Option::is_none", rename = "ct")]
253 pub color_temperature: Option<u16>,
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub effect: Option<Effect>,
257 #[serde(skip_serializing_if = "Option::is_none", rename = "transitiontime")]
261 pub transition_time: Option<u16>,
262}
263
264impl StaticStateModifier {
265 pub fn new() -> Self {
267 Self::default()
268 }
269
270 pub fn with_color(self, value: Color) -> Self {
275 let mut modifier = Self {
276 color_space_coordinates: Some(value.space_coordinates),
277 ..self
278 };
279 if let Some(brightness) = value.brightness {
280 modifier.brightness = Some(brightness);
281 }
282 modifier
283 }
284}
285
286impl resource::Modifier for StaticStateModifier {
287 type Id = String;
288 fn url_suffix(id: Self::Id) -> String {
289 format!("lights/{}/state", id)
290 }
291}
292
293#[derive(Clone, Debug, Default, PartialEq, Setters)]
295#[setters(strip_option, prefix = "with_")]
296pub struct StateModifier {
297 pub on: Option<bool>,
299 pub brightness: Option<Adjust<u8>>,
301 pub hue: Option<Adjust<u16>>,
303 pub saturation: Option<Adjust<u8>>,
305 pub color_space_coordinates: Option<Adjust<(f32, f32)>>,
307 pub color_temperature: Option<Adjust<u16>>,
309 pub alert: Option<Alert>,
311 pub effect: Option<Effect>,
313 pub transition_time: Option<u16>,
317}
318
319impl StateModifier {
320 pub fn new() -> Self {
322 Self::default()
323 }
324
325 pub fn with_color(self, value: Color) -> Self {
330 let mut modifier = Self {
331 color_space_coordinates: Some(Adjust::Override(value.space_coordinates)),
332 ..self
333 };
334 if let Some(brightness) = value.brightness {
335 modifier.brightness = Some(Adjust::Override(brightness));
336 }
337 modifier
338 }
339}
340
341impl resource::Modifier for StateModifier {
342 type Id = String;
343 fn url_suffix(id: Self::Id) -> String {
344 format!("lights/{}/state", id)
345 }
346}
347
348impl Serialize for StateModifier {
349 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
350 where
351 S: Serializer,
352 {
353 custom_serialize! {
354 serializer, "StateModifier";
355 on => (&self.on),
356 bri => (&self.brightness, to_override),
357 bri_inc => (&self.brightness, to_increment, i16),
358 hue => (&self.hue, to_override),
359 hue_inc => (&self.hue, to_increment, i32),
360 sat => (&self.saturation, to_override),
361 sat_inc => (&self.saturation, to_increment, i16),
362 xy => (&self.color_space_coordinates, to_override),
363 xy_inc => (&self.color_space_coordinates, to_increment_tuple, f32),
364 ct => (&self.color_temperature, to_override),
365 ct_inc => (&self.color_temperature, to_increment, i32),
366 alert => (&self.alert),
367 effect => (&self.effect),
368 transitiontime => (&self.transition_time),
369 }
370 }
371}
372
373#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Setters)]
375#[setters(strip_option, prefix = "with_")]
376pub struct Scanner {
377 #[serde(skip_serializing_if = "Option::is_none", rename = "deviceid")]
379 pub device_ids: Option<Vec<String>>,
380}
381
382impl Scanner {
383 pub fn new() -> Self {
385 Self::default()
386 }
387}
388
389impl resource::Scanner for Scanner {
390 fn url_suffix() -> String {
391 "lights".to_owned()
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use serde_json::json;
399
400 #[test]
401 fn serialize_attribute_modifier() {
402 let modifier = AttributeModifier::new();
403 let modifier_json = serde_json::to_value(modifier).unwrap();
404 let expected_json = json!({});
405 assert_eq!(modifier_json, expected_json);
406
407 let modifier = AttributeModifier {
408 name: Some("test".into()),
409 };
410 let modifier_json = serde_json::to_value(modifier).unwrap();
411 let expected_json = json!({"name": "test"});
412 assert_eq!(modifier_json, expected_json);
413 }
414
415 #[test]
416 fn serialize_static_state_modifier() {
417 let modifier = StaticStateModifier::new();
418 let modifier_json = serde_json::to_value(modifier).unwrap();
419 let expected_json = json!({});
420 assert_eq!(modifier_json, expected_json);
421
422 let modifier = StaticStateModifier {
423 on: Some(true),
424 brightness: Some(1),
425 hue: Some(2),
426 saturation: Some(3),
427 color_space_coordinates: None,
428 color_temperature: Some(4),
429 effect: Some(Effect::Colorloop),
430 transition_time: Some(4),
431 };
432 let modifier_json = serde_json::to_value(modifier).unwrap();
433 let expected_json = json!({
434 "on": true,
435 "bri": 1,
436 "hue": 2,
437 "sat": 3,
438 "ct": 4,
439 "effect": "colorloop",
440 "transitiontime": 4,
441 });
442 assert_eq!(modifier_json, expected_json);
443
444 let modifier = StaticStateModifier::new()
445 .with_brightness(1)
446 .with_color(Color::from_rgb(0, 0, 0));
447 let modifier_json = serde_json::to_value(modifier).unwrap();
448 let expected_json = json!({
449 "bri": 0,
450 "xy": [0.0, 0.0]
451 });
452 assert_eq!(modifier_json, expected_json);
453 }
454
455 #[test]
456 fn serialize_state_modifier() {
457 let modifier = StateModifier::new();
458 let modifier_json = serde_json::to_value(modifier).unwrap();
459 let expected_json = json!({});
460 assert_eq!(modifier_json, expected_json);
461
462 let modifier = StateModifier {
463 on: Some(true),
464 brightness: Some(Adjust::Increment(1)),
465 hue: Some(Adjust::Override(2)),
466 saturation: Some(Adjust::Decrement(3)),
467 color_space_coordinates: None,
468 color_temperature: Some(Adjust::Override(4)),
469 alert: Some(Alert::None),
470 effect: Some(Effect::Colorloop),
471 transition_time: Some(4),
472 };
473 let modifier_json = serde_json::to_value(modifier).unwrap();
474 let expected_json = json!({
475 "on": true,
476 "bri_inc": 1,
477 "hue": 2,
478 "sat_inc": -3,
479 "ct": 4,
480 "alert": "none",
481 "effect": "colorloop",
482 "transitiontime": 4,
483 });
484 assert_eq!(modifier_json, expected_json);
485
486 let modifier = StateModifier::new()
487 .with_brightness(Adjust::Increment(1))
488 .with_color(Color::from_rgb(0, 0, 0));
489 let modifier_json = serde_json::to_value(modifier).unwrap();
490 let expected_json = json!({
491 "bri": 0,
492 "xy": [0.0, 0.0]
493 });
494 assert_eq!(modifier_json, expected_json);
495 }
496
497 #[test]
498 fn serialize_scanner() {
499 let scanner = Scanner::new();
500 let scanner_json = serde_json::to_value(scanner).unwrap();
501 let expected_json = json!({});
502 assert_eq!(scanner_json, expected_json);
503
504 let scanner = Scanner {
505 device_ids: Some(vec!["1".into()]),
506 };
507 let scanner_json = serde_json::to_value(scanner).unwrap();
508 let expected_json = json!({
509 "deviceid": ["1"]
510 });
511 assert_eq!(scanner_json, expected_json);
512 }
513}