tanuki_common/capabilities/
light.rs1use alloc::{vec, vec::Vec};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{Property, property};
6
7pub trait LightProperty: Property {}
8
9#[property(LightProperty, State, key = "state")]
10pub struct LightState {
11 pub on: bool,
13 #[serde(skip_serializing_if = "Option::is_none")]
15 pub brightness: Option<f32>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub color: Option<Color>,
18}
19
20#[property(LightProperty, Command, key = "command")]
21pub struct LightCommand {
22 pub on: bool,
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub brightness: Option<f32>,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub color: Option<Color>,
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31#[serde(untagged)]
32#[serde(deny_unknown_fields)]
33pub enum Color {
34 Rgbww { r: u8, g: u8, b: u8, cw: u8, ww: u8 },
36 Rgbw { r: u8, g: u8, b: u8, w: u8 },
38 Rgb { r: u8, g: u8, b: u8 },
40 Hs { h: f32, s: f32 },
42 Xy { x: f32, y: f32 },
44}
45
46impl Color {
47 pub fn to_hass(&self) -> Vec<f32> {
49 match *self {
50 Color::Rgbww { r, g, b, cw, ww } => {
51 vec![r as f32, g as f32, b as f32, cw as f32, ww as f32]
52 }
53 Color::Rgbw { r, g, b, w } => vec![r as f32, g as f32, b as f32, w as f32],
54 Color::Rgb { r, g, b } => vec![r as f32, g as f32, b as f32],
55 Color::Hs { h, s } => vec![h, s],
56 Color::Xy { x, y } => vec![x, y],
57 }
58 }
59
60 pub fn hass_service_data_key(&self) -> &'static str {
61 match *self {
62 Color::Rgbww { .. } => "rgbww_color",
63 Color::Rgbw { .. } => "rgbw_color",
64 Color::Rgb { .. } => "rgb_color",
65 Color::Hs { .. } => "hs_color",
66 Color::Xy { .. } => "xy_color",
67 }
68 }
69
70 pub fn from_slice(mode: ColorMode, data: &[f32]) -> Option<Self> {
71 match (mode, data) {
72 (ColorMode::Rgbww, &[r, g, b, cw, ww]) => Some(Color::Rgbww {
73 r: r as u8,
74 g: g as u8,
75 b: b as u8,
76 cw: cw as u8,
77 ww: ww as u8,
78 }),
79 (ColorMode::Rgbww, _) => None,
80 (ColorMode::Rgbw, &[r, g, b, w]) => Some(Color::Rgbw {
81 r: r as u8,
82 g: g as u8,
83 b: b as u8,
84 w: w as u8,
85 }),
86 (ColorMode::Rgbw, _) => None,
87 (ColorMode::Rgb, &[r, g, b]) => Some(Color::Rgb { r: r as u8, g: g as u8, b: b as u8 }),
88 (ColorMode::Rgb, _) => None,
89 (ColorMode::Hs, &[h, s]) => Some(Color::Hs { h, s }),
90 (ColorMode::Hs, _) => None,
91 (ColorMode::Xy, &[x, y]) => Some(Color::Xy { x, y }),
92 (ColorMode::Xy, _) => None,
93 (ColorMode::ColorTemp, _) => None,
94 (ColorMode::Brightness, _) => None,
95 (ColorMode::OnOff, _) => None,
96 }
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
101#[serde(rename_all = "snake_case")]
102pub enum ColorMode {
103 Rgbww,
104 Rgbw,
105 Rgb,
106 Hs,
107 Xy,
108 ColorTemp,
109 Brightness,
110 #[serde(alias = "onoff")]
111 OnOff,
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn parse_colors() {
120 assert_eq!(
121 serde_json::from_value::<Color>(
122 serde_json::json!({ "r": 255, "g": 0, "b": 128, "cw": 32, "ww": 16 })
123 )
124 .unwrap(),
125 Color::Rgbww { r: 255, g: 0, b: 128, cw: 32, ww: 16 }
126 );
127
128 assert_eq!(
129 serde_json::from_value::<Color>(
130 serde_json::json!({ "r": 255, "g": 0, "b": 128, "w": 64 })
131 )
132 .unwrap(),
133 Color::Rgbw { r: 255, g: 0, b: 128, w: 64 }
134 );
135
136 assert_eq!(
137 serde_json::from_value::<Color>(serde_json::json!({ "r": 255, "g": 0, "b": 128 }))
138 .unwrap(),
139 Color::Rgb { r: 255, g: 0, b: 128 }
140 );
141
142 assert_eq!(
143 serde_json::from_value::<Color>(serde_json::json!({ "h": 180.0, "s": 0.5 })).unwrap(),
144 Color::Hs { h: 180.0, s: 0.5 }
145 );
146
147 assert_eq!(
148 serde_json::from_value::<Color>(serde_json::json!({ "x": 0.3, "y": 0.6 })).unwrap(),
149 Color::Xy { x: 0.3, y: 0.6 }
150 );
151 }
152}