1#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum DelayedAllOffEffectVariant {
18 Delayedofffastfade = 0,
20 Nofade = 1,
22 Delayedoffslowfade = 2,
24}
25
26impl DelayedAllOffEffectVariant {
27 pub fn from_u8(value: u8) -> Option<Self> {
29 match value {
30 0 => Some(DelayedAllOffEffectVariant::Delayedofffastfade),
31 1 => Some(DelayedAllOffEffectVariant::Nofade),
32 2 => Some(DelayedAllOffEffectVariant::Delayedoffslowfade),
33 _ => None,
34 }
35 }
36
37 pub fn to_u8(self) -> u8 {
39 self as u8
40 }
41}
42
43impl From<DelayedAllOffEffectVariant> for u8 {
44 fn from(val: DelayedAllOffEffectVariant) -> Self {
45 val as u8
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
50#[repr(u8)]
51pub enum DyingLightEffectVariant {
52 Dyinglightfadeoff = 0,
54}
55
56impl DyingLightEffectVariant {
57 pub fn from_u8(value: u8) -> Option<Self> {
59 match value {
60 0 => Some(DyingLightEffectVariant::Dyinglightfadeoff),
61 _ => None,
62 }
63 }
64
65 pub fn to_u8(self) -> u8 {
67 self as u8
68 }
69}
70
71impl From<DyingLightEffectVariant> for u8 {
72 fn from(val: DyingLightEffectVariant) -> Self {
73 val as u8
74 }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
78#[repr(u8)]
79pub enum EffectIdentifier {
80 Delayedalloff = 0,
82 Dyinglight = 1,
84}
85
86impl EffectIdentifier {
87 pub fn from_u8(value: u8) -> Option<Self> {
89 match value {
90 0 => Some(EffectIdentifier::Delayedalloff),
91 1 => Some(EffectIdentifier::Dyinglight),
92 _ => None,
93 }
94 }
95
96 pub fn to_u8(self) -> u8 {
98 self as u8
99 }
100}
101
102impl From<EffectIdentifier> for u8 {
103 fn from(val: EffectIdentifier) -> Self {
104 val as u8
105 }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
109#[repr(u8)]
110pub enum StartUpOnOff {
111 Off = 0,
113 On = 1,
115 Toggle = 2,
117}
118
119impl StartUpOnOff {
120 pub fn from_u8(value: u8) -> Option<Self> {
122 match value {
123 0 => Some(StartUpOnOff::Off),
124 1 => Some(StartUpOnOff::On),
125 2 => Some(StartUpOnOff::Toggle),
126 _ => None,
127 }
128 }
129
130 pub fn to_u8(self) -> u8 {
132 self as u8
133 }
134}
135
136impl From<StartUpOnOff> for u8 {
137 fn from(val: StartUpOnOff) -> Self {
138 val as u8
139 }
140}
141
142pub type OnOffControl = u8;
146
147pub mod onoffcontrol {
149 pub const ACCEPT_ONLY_WHEN_ON: u8 = 0x01;
151}
152
153pub fn encode_off_with_effect(effect_identifier: EffectIdentifier, effect_variant: u8) -> anyhow::Result<Vec<u8>> {
157 let tlv = tlv::TlvItemEnc {
158 tag: 0,
159 value: tlv::TlvItemValueEnc::StructInvisible(vec![
160 (0, tlv::TlvItemValueEnc::UInt8(effect_identifier.to_u8())).into(),
161 (1, tlv::TlvItemValueEnc::UInt8(effect_variant)).into(),
162 ]),
163 };
164 Ok(tlv.encode()?)
165}
166
167pub fn encode_on_with_timed_off(on_off_control: OnOffControl, on_time: u16, off_wait_time: u16) -> anyhow::Result<Vec<u8>> {
169 let tlv = tlv::TlvItemEnc {
170 tag: 0,
171 value: tlv::TlvItemValueEnc::StructInvisible(vec![
172 (0, tlv::TlvItemValueEnc::UInt8(on_off_control)).into(),
173 (1, tlv::TlvItemValueEnc::UInt16(on_time)).into(),
174 (2, tlv::TlvItemValueEnc::UInt16(off_wait_time)).into(),
175 ]),
176 };
177 Ok(tlv.encode()?)
178}
179
180pub fn decode_on_off(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
184 if let tlv::TlvItemValue::Bool(v) = inp {
185 Ok(*v)
186 } else {
187 Err(anyhow::anyhow!("Expected Bool"))
188 }
189}
190
191pub fn decode_global_scene_control(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
193 if let tlv::TlvItemValue::Bool(v) = inp {
194 Ok(*v)
195 } else {
196 Err(anyhow::anyhow!("Expected Bool"))
197 }
198}
199
200pub fn decode_on_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
202 if let tlv::TlvItemValue::Int(v) = inp {
203 Ok(*v as u16)
204 } else {
205 Err(anyhow::anyhow!("Expected UInt16"))
206 }
207}
208
209pub fn decode_off_wait_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
211 if let tlv::TlvItemValue::Int(v) = inp {
212 Ok(*v as u16)
213 } else {
214 Err(anyhow::anyhow!("Expected UInt16"))
215 }
216}
217
218pub fn decode_start_up_on_off(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<StartUpOnOff>> {
220 if let tlv::TlvItemValue::Int(v) = inp {
221 Ok(StartUpOnOff::from_u8(*v as u8))
222 } else {
223 Ok(None)
224 }
225}
226
227
228pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
240 if cluster_id != 0x0006 {
242 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0006, got {}\"}}", cluster_id);
243 }
244
245 match attribute_id {
246 0x0000 => {
247 match decode_on_off(tlv_value) {
248 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
249 Err(e) => format!("{{\"error\": \"{}\"}}", e),
250 }
251 }
252 0x4000 => {
253 match decode_global_scene_control(tlv_value) {
254 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
255 Err(e) => format!("{{\"error\": \"{}\"}}", e),
256 }
257 }
258 0x4001 => {
259 match decode_on_time(tlv_value) {
260 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
261 Err(e) => format!("{{\"error\": \"{}\"}}", e),
262 }
263 }
264 0x4002 => {
265 match decode_off_wait_time(tlv_value) {
266 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
267 Err(e) => format!("{{\"error\": \"{}\"}}", e),
268 }
269 }
270 0x4003 => {
271 match decode_start_up_on_off(tlv_value) {
272 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
273 Err(e) => format!("{{\"error\": \"{}\"}}", e),
274 }
275 }
276 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
277 }
278}
279
280pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
285 vec![
286 (0x0000, "OnOff"),
287 (0x4000, "GlobalSceneControl"),
288 (0x4001, "OnTime"),
289 (0x4002, "OffWaitTime"),
290 (0x4003, "StartUpOnOff"),
291 ]
292}
293
294pub fn get_command_list() -> Vec<(u32, &'static str)> {
297 vec![
298 (0x00, "Off"),
299 (0x01, "On"),
300 (0x02, "Toggle"),
301 (0x40, "OffWithEffect"),
302 (0x41, "OnWithRecallGlobalScene"),
303 (0x42, "OnWithTimedOff"),
304 ]
305}
306
307pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
308 match cmd_id {
309 0x00 => Some("Off"),
310 0x01 => Some("On"),
311 0x02 => Some("Toggle"),
312 0x40 => Some("OffWithEffect"),
313 0x41 => Some("OnWithRecallGlobalScene"),
314 0x42 => Some("OnWithTimedOff"),
315 _ => None,
316 }
317}
318
319pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
320 match cmd_id {
321 0x00 => Some(vec![]),
322 0x01 => Some(vec![]),
323 0x02 => Some(vec![]),
324 0x40 => Some(vec![
325 crate::clusters::codec::CommandField { tag: 0, name: "effect_identifier", kind: crate::clusters::codec::FieldKind::Enum { name: "EffectIdentifier", variants: &[(0, "Delayedalloff"), (1, "Dyinglight")] }, optional: false, nullable: false },
326 crate::clusters::codec::CommandField { tag: 1, name: "effect_variant", kind: crate::clusters::codec::FieldKind::U8, optional: false, nullable: false },
327 ]),
328 0x41 => Some(vec![]),
329 0x42 => Some(vec![
330 crate::clusters::codec::CommandField { tag: 0, name: "on_off_control", kind: crate::clusters::codec::FieldKind::Bitmap { name: "OnOffControl", bits: &[(1, "ACCEPT_ONLY_WHEN_ON")] }, optional: false, nullable: false },
331 crate::clusters::codec::CommandField { tag: 1, name: "on_time", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
332 crate::clusters::codec::CommandField { tag: 2, name: "off_wait_time", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
333 ]),
334 _ => None,
335 }
336}
337
338pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
339 match cmd_id {
340 0x00 => Ok(vec![]),
341 0x01 => Ok(vec![]),
342 0x02 => Ok(vec![]),
343 0x40 => {
344 let effect_identifier = {
345 let n = crate::clusters::codec::json_util::get_u64(args, "effect_identifier")?;
346 EffectIdentifier::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid EffectIdentifier: {}", n))?
347 };
348 let effect_variant = crate::clusters::codec::json_util::get_u8(args, "effect_variant")?;
349 encode_off_with_effect(effect_identifier, effect_variant)
350 }
351 0x41 => Ok(vec![]),
352 0x42 => {
353 let on_off_control = crate::clusters::codec::json_util::get_u8(args, "on_off_control")?;
354 let on_time = crate::clusters::codec::json_util::get_u16(args, "on_time")?;
355 let off_wait_time = crate::clusters::codec::json_util::get_u16(args, "off_wait_time")?;
356 encode_on_with_timed_off(on_off_control, on_time, off_wait_time)
357 }
358 _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
359 }
360}
361
362pub async fn off(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
366 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_CMD_ID_OFF, &[]).await?;
367 Ok(())
368}
369
370pub async fn on(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
372 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_CMD_ID_ON, &[]).await?;
373 Ok(())
374}
375
376pub async fn toggle(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
378 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_CMD_ID_TOGGLE, &[]).await?;
379 Ok(())
380}
381
382pub async fn off_with_effect(conn: &crate::controller::Connection, endpoint: u16, effect_identifier: EffectIdentifier, effect_variant: u8) -> anyhow::Result<()> {
384 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_CMD_ID_OFFWITHEFFECT, &encode_off_with_effect(effect_identifier, effect_variant)?).await?;
385 Ok(())
386}
387
388pub async fn on_with_recall_global_scene(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
390 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_CMD_ID_ONWITHRECALLGLOBALSCENE, &[]).await?;
391 Ok(())
392}
393
394pub async fn on_with_timed_off(conn: &crate::controller::Connection, endpoint: u16, on_off_control: OnOffControl, on_time: u16, off_wait_time: u16) -> anyhow::Result<()> {
396 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_CMD_ID_ONWITHTIMEDOFF, &encode_on_with_timed_off(on_off_control, on_time, off_wait_time)?).await?;
397 Ok(())
398}
399
400pub async fn read_on_off(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
402 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_ATTR_ID_ONOFF).await?;
403 decode_on_off(&tlv)
404}
405
406pub async fn read_global_scene_control(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
408 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_ATTR_ID_GLOBALSCENECONTROL).await?;
409 decode_global_scene_control(&tlv)
410}
411
412pub async fn read_on_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
414 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_ATTR_ID_ONTIME).await?;
415 decode_on_time(&tlv)
416}
417
418pub async fn read_off_wait_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
420 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_ATTR_ID_OFFWAITTIME).await?;
421 decode_off_wait_time(&tlv)
422}
423
424pub async fn read_start_up_on_off(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<StartUpOnOff>> {
426 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_ATTR_ID_STARTUPONOFF).await?;
427 decode_start_up_on_off(&tlv)
428}
429