1use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14#[repr(u8)]
15pub enum DelayedAllOffEffectVariant {
16 Delayedofffastfade = 0,
18 Nofade = 1,
20 Delayedoffslowfade = 2,
22}
23
24impl DelayedAllOffEffectVariant {
25 pub fn from_u8(value: u8) -> Option<Self> {
27 match value {
28 0 => Some(DelayedAllOffEffectVariant::Delayedofffastfade),
29 1 => Some(DelayedAllOffEffectVariant::Nofade),
30 2 => Some(DelayedAllOffEffectVariant::Delayedoffslowfade),
31 _ => None,
32 }
33 }
34
35 pub fn to_u8(self) -> u8 {
37 self as u8
38 }
39}
40
41impl From<DelayedAllOffEffectVariant> for u8 {
42 fn from(val: DelayedAllOffEffectVariant) -> Self {
43 val as u8
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
48#[repr(u8)]
49pub enum DyingLightEffectVariant {
50 Dyinglightfadeoff = 0,
52}
53
54impl DyingLightEffectVariant {
55 pub fn from_u8(value: u8) -> Option<Self> {
57 match value {
58 0 => Some(DyingLightEffectVariant::Dyinglightfadeoff),
59 _ => None,
60 }
61 }
62
63 pub fn to_u8(self) -> u8 {
65 self as u8
66 }
67}
68
69impl From<DyingLightEffectVariant> for u8 {
70 fn from(val: DyingLightEffectVariant) -> Self {
71 val as u8
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
76#[repr(u8)]
77pub enum EffectIdentifier {
78 Delayedalloff = 0,
80 Dyinglight = 1,
82}
83
84impl EffectIdentifier {
85 pub fn from_u8(value: u8) -> Option<Self> {
87 match value {
88 0 => Some(EffectIdentifier::Delayedalloff),
89 1 => Some(EffectIdentifier::Dyinglight),
90 _ => None,
91 }
92 }
93
94 pub fn to_u8(self) -> u8 {
96 self as u8
97 }
98}
99
100impl From<EffectIdentifier> for u8 {
101 fn from(val: EffectIdentifier) -> Self {
102 val as u8
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
107#[repr(u8)]
108pub enum StartUpOnOff {
109 Off = 0,
111 On = 1,
113 Toggle = 2,
115}
116
117impl StartUpOnOff {
118 pub fn from_u8(value: u8) -> Option<Self> {
120 match value {
121 0 => Some(StartUpOnOff::Off),
122 1 => Some(StartUpOnOff::On),
123 2 => Some(StartUpOnOff::Toggle),
124 _ => None,
125 }
126 }
127
128 pub fn to_u8(self) -> u8 {
130 self as u8
131 }
132}
133
134impl From<StartUpOnOff> for u8 {
135 fn from(val: StartUpOnOff) -> Self {
136 val as u8
137 }
138}
139
140pub type OnOffControl = u8;
144
145pub mod onoffcontrol {
147 pub const ACCEPT_ONLY_WHEN_ON: u8 = 0x01;
149}
150
151pub fn encode_off_with_effect(effect_identifier: EffectIdentifier, effect_variant: u8) -> anyhow::Result<Vec<u8>> {
155 let tlv = tlv::TlvItemEnc {
156 tag: 0,
157 value: tlv::TlvItemValueEnc::StructInvisible(vec![
158 (0, tlv::TlvItemValueEnc::UInt8(effect_identifier.to_u8())).into(),
159 (1, tlv::TlvItemValueEnc::UInt8(effect_variant)).into(),
160 ]),
161 };
162 Ok(tlv.encode()?)
163}
164
165pub fn encode_on_with_timed_off(on_off_control: OnOffControl, on_time: u16, off_wait_time: u16) -> anyhow::Result<Vec<u8>> {
167 let tlv = tlv::TlvItemEnc {
168 tag: 0,
169 value: tlv::TlvItemValueEnc::StructInvisible(vec![
170 (0, tlv::TlvItemValueEnc::UInt8(on_off_control)).into(),
171 (1, tlv::TlvItemValueEnc::UInt16(on_time)).into(),
172 (2, tlv::TlvItemValueEnc::UInt16(off_wait_time)).into(),
173 ]),
174 };
175 Ok(tlv.encode()?)
176}
177
178pub fn decode_on_off(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
182 if let tlv::TlvItemValue::Bool(v) = inp {
183 Ok(*v)
184 } else {
185 Err(anyhow::anyhow!("Expected Bool"))
186 }
187}
188
189pub fn decode_global_scene_control(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
191 if let tlv::TlvItemValue::Bool(v) = inp {
192 Ok(*v)
193 } else {
194 Err(anyhow::anyhow!("Expected Bool"))
195 }
196}
197
198pub fn decode_on_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
200 if let tlv::TlvItemValue::Int(v) = inp {
201 Ok(*v as u16)
202 } else {
203 Err(anyhow::anyhow!("Expected UInt16"))
204 }
205}
206
207pub fn decode_off_wait_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
209 if let tlv::TlvItemValue::Int(v) = inp {
210 Ok(*v as u16)
211 } else {
212 Err(anyhow::anyhow!("Expected UInt16"))
213 }
214}
215
216pub fn decode_start_up_on_off(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<StartUpOnOff>> {
218 if let tlv::TlvItemValue::Int(v) = inp {
219 Ok(StartUpOnOff::from_u8(*v as u8))
220 } else {
221 Ok(None)
222 }
223}
224
225
226pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
238 if cluster_id != 0x0006 {
240 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0006, got {}\"}}", cluster_id);
241 }
242
243 match attribute_id {
244 0x0000 => {
245 match decode_on_off(tlv_value) {
246 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
247 Err(e) => format!("{{\"error\": \"{}\"}}", e),
248 }
249 }
250 0x4000 => {
251 match decode_global_scene_control(tlv_value) {
252 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
253 Err(e) => format!("{{\"error\": \"{}\"}}", e),
254 }
255 }
256 0x4001 => {
257 match decode_on_time(tlv_value) {
258 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
259 Err(e) => format!("{{\"error\": \"{}\"}}", e),
260 }
261 }
262 0x4002 => {
263 match decode_off_wait_time(tlv_value) {
264 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
265 Err(e) => format!("{{\"error\": \"{}\"}}", e),
266 }
267 }
268 0x4003 => {
269 match decode_start_up_on_off(tlv_value) {
270 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
271 Err(e) => format!("{{\"error\": \"{}\"}}", e),
272 }
273 }
274 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
275 }
276}
277
278pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
283 vec![
284 (0x0000, "OnOff"),
285 (0x4000, "GlobalSceneControl"),
286 (0x4001, "OnTime"),
287 (0x4002, "OffWaitTime"),
288 (0x4003, "StartUpOnOff"),
289 ]
290}
291