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 AirflowDirection {
18 Forward = 0,
20 Reverse = 1,
22}
23
24impl AirflowDirection {
25 pub fn from_u8(value: u8) -> Option<Self> {
27 match value {
28 0 => Some(AirflowDirection::Forward),
29 1 => Some(AirflowDirection::Reverse),
30 _ => None,
31 }
32 }
33
34 pub fn to_u8(self) -> u8 {
36 self as u8
37 }
38}
39
40impl From<AirflowDirection> for u8 {
41 fn from(val: AirflowDirection) -> Self {
42 val as u8
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
47#[repr(u8)]
48pub enum FanMode {
49 Off = 0,
51 Low = 1,
53 Medium = 2,
55 High = 3,
57 On = 4,
58 Auto = 5,
60 Smart = 6,
62}
63
64impl FanMode {
65 pub fn from_u8(value: u8) -> Option<Self> {
67 match value {
68 0 => Some(FanMode::Off),
69 1 => Some(FanMode::Low),
70 2 => Some(FanMode::Medium),
71 3 => Some(FanMode::High),
72 4 => Some(FanMode::On),
73 5 => Some(FanMode::Auto),
74 6 => Some(FanMode::Smart),
75 _ => None,
76 }
77 }
78
79 pub fn to_u8(self) -> u8 {
81 self as u8
82 }
83}
84
85impl From<FanMode> for u8 {
86 fn from(val: FanMode) -> Self {
87 val as u8
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
92#[repr(u8)]
93pub enum FanModeSequence {
94 Offlowmedhigh = 0,
96 Offlowhigh = 1,
98 Offlowmedhighauto = 2,
100 Offlowhighauto = 3,
102 Offhighauto = 4,
104 Offhigh = 5,
106}
107
108impl FanModeSequence {
109 pub fn from_u8(value: u8) -> Option<Self> {
111 match value {
112 0 => Some(FanModeSequence::Offlowmedhigh),
113 1 => Some(FanModeSequence::Offlowhigh),
114 2 => Some(FanModeSequence::Offlowmedhighauto),
115 3 => Some(FanModeSequence::Offlowhighauto),
116 4 => Some(FanModeSequence::Offhighauto),
117 5 => Some(FanModeSequence::Offhigh),
118 _ => None,
119 }
120 }
121
122 pub fn to_u8(self) -> u8 {
124 self as u8
125 }
126}
127
128impl From<FanModeSequence> for u8 {
129 fn from(val: FanModeSequence) -> Self {
130 val as u8
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
135#[repr(u8)]
136pub enum StepDirection {
137 Increase = 0,
139 Decrease = 1,
141}
142
143impl StepDirection {
144 pub fn from_u8(value: u8) -> Option<Self> {
146 match value {
147 0 => Some(StepDirection::Increase),
148 1 => Some(StepDirection::Decrease),
149 _ => None,
150 }
151 }
152
153 pub fn to_u8(self) -> u8 {
155 self as u8
156 }
157}
158
159impl From<StepDirection> for u8 {
160 fn from(val: StepDirection) -> Self {
161 val as u8
162 }
163}
164
165pub type Rock = u8;
169
170pub mod rock {
172 pub const ROCK_LEFT_RIGHT: u8 = 0x01;
174 pub const ROCK_UP_DOWN: u8 = 0x02;
176 pub const ROCK_ROUND: u8 = 0x04;
178}
179
180pub type Wind = u8;
182
183pub mod wind {
185 pub const SLEEP_WIND: u8 = 0x01;
187 pub const NATURAL_WIND: u8 = 0x02;
189}
190
191pub fn encode_step(direction: StepDirection, wrap: bool, lowest_off: bool) -> anyhow::Result<Vec<u8>> {
195 let tlv = tlv::TlvItemEnc {
196 tag: 0,
197 value: tlv::TlvItemValueEnc::StructInvisible(vec![
198 (0, tlv::TlvItemValueEnc::UInt8(direction.to_u8())).into(),
199 (1, tlv::TlvItemValueEnc::Bool(wrap)).into(),
200 (2, tlv::TlvItemValueEnc::Bool(lowest_off)).into(),
201 ]),
202 };
203 Ok(tlv.encode()?)
204}
205
206pub fn decode_fan_mode(inp: &tlv::TlvItemValue) -> anyhow::Result<FanMode> {
210 if let tlv::TlvItemValue::Int(v) = inp {
211 FanMode::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
212 } else {
213 Err(anyhow::anyhow!("Expected Integer"))
214 }
215}
216
217pub fn decode_fan_mode_sequence(inp: &tlv::TlvItemValue) -> anyhow::Result<FanModeSequence> {
219 if let tlv::TlvItemValue::Int(v) = inp {
220 FanModeSequence::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
221 } else {
222 Err(anyhow::anyhow!("Expected Integer"))
223 }
224}
225
226pub fn decode_percent_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
228 if let tlv::TlvItemValue::Int(v) = inp {
229 Ok(Some(*v as u8))
230 } else {
231 Ok(None)
232 }
233}
234
235pub fn decode_percent_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
237 if let tlv::TlvItemValue::Int(v) = inp {
238 Ok(*v as u8)
239 } else {
240 Err(anyhow::anyhow!("Expected UInt8"))
241 }
242}
243
244pub fn decode_speed_max(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
246 if let tlv::TlvItemValue::Int(v) = inp {
247 Ok(*v as u8)
248 } else {
249 Err(anyhow::anyhow!("Expected UInt8"))
250 }
251}
252
253pub fn decode_speed_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
255 if let tlv::TlvItemValue::Int(v) = inp {
256 Ok(Some(*v as u8))
257 } else {
258 Ok(None)
259 }
260}
261
262pub fn decode_speed_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
264 if let tlv::TlvItemValue::Int(v) = inp {
265 Ok(*v as u8)
266 } else {
267 Err(anyhow::anyhow!("Expected UInt8"))
268 }
269}
270
271pub fn decode_rock_support(inp: &tlv::TlvItemValue) -> anyhow::Result<Rock> {
273 if let tlv::TlvItemValue::Int(v) = inp {
274 Ok(*v as u8)
275 } else {
276 Err(anyhow::anyhow!("Expected Integer"))
277 }
278}
279
280pub fn decode_rock_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Rock> {
282 if let tlv::TlvItemValue::Int(v) = inp {
283 Ok(*v as u8)
284 } else {
285 Err(anyhow::anyhow!("Expected Integer"))
286 }
287}
288
289pub fn decode_wind_support(inp: &tlv::TlvItemValue) -> anyhow::Result<Wind> {
291 if let tlv::TlvItemValue::Int(v) = inp {
292 Ok(*v as u8)
293 } else {
294 Err(anyhow::anyhow!("Expected Integer"))
295 }
296}
297
298pub fn decode_wind_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Wind> {
300 if let tlv::TlvItemValue::Int(v) = inp {
301 Ok(*v as u8)
302 } else {
303 Err(anyhow::anyhow!("Expected Integer"))
304 }
305}
306
307pub fn decode_airflow_direction(inp: &tlv::TlvItemValue) -> anyhow::Result<AirflowDirection> {
309 if let tlv::TlvItemValue::Int(v) = inp {
310 AirflowDirection::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
311 } else {
312 Err(anyhow::anyhow!("Expected Integer"))
313 }
314}
315
316
317pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
329 if cluster_id != 0x0202 {
331 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0202, got {}\"}}", cluster_id);
332 }
333
334 match attribute_id {
335 0x0000 => {
336 match decode_fan_mode(tlv_value) {
337 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
338 Err(e) => format!("{{\"error\": \"{}\"}}", e),
339 }
340 }
341 0x0001 => {
342 match decode_fan_mode_sequence(tlv_value) {
343 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
344 Err(e) => format!("{{\"error\": \"{}\"}}", e),
345 }
346 }
347 0x0002 => {
348 match decode_percent_setting(tlv_value) {
349 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
350 Err(e) => format!("{{\"error\": \"{}\"}}", e),
351 }
352 }
353 0x0003 => {
354 match decode_percent_current(tlv_value) {
355 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
356 Err(e) => format!("{{\"error\": \"{}\"}}", e),
357 }
358 }
359 0x0004 => {
360 match decode_speed_max(tlv_value) {
361 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
362 Err(e) => format!("{{\"error\": \"{}\"}}", e),
363 }
364 }
365 0x0005 => {
366 match decode_speed_setting(tlv_value) {
367 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
368 Err(e) => format!("{{\"error\": \"{}\"}}", e),
369 }
370 }
371 0x0006 => {
372 match decode_speed_current(tlv_value) {
373 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
374 Err(e) => format!("{{\"error\": \"{}\"}}", e),
375 }
376 }
377 0x0007 => {
378 match decode_rock_support(tlv_value) {
379 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
380 Err(e) => format!("{{\"error\": \"{}\"}}", e),
381 }
382 }
383 0x0008 => {
384 match decode_rock_setting(tlv_value) {
385 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
386 Err(e) => format!("{{\"error\": \"{}\"}}", e),
387 }
388 }
389 0x0009 => {
390 match decode_wind_support(tlv_value) {
391 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
392 Err(e) => format!("{{\"error\": \"{}\"}}", e),
393 }
394 }
395 0x000A => {
396 match decode_wind_setting(tlv_value) {
397 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
398 Err(e) => format!("{{\"error\": \"{}\"}}", e),
399 }
400 }
401 0x000B => {
402 match decode_airflow_direction(tlv_value) {
403 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
404 Err(e) => format!("{{\"error\": \"{}\"}}", e),
405 }
406 }
407 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
408 }
409}
410
411pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
416 vec![
417 (0x0000, "FanMode"),
418 (0x0001, "FanModeSequence"),
419 (0x0002, "PercentSetting"),
420 (0x0003, "PercentCurrent"),
421 (0x0004, "SpeedMax"),
422 (0x0005, "SpeedSetting"),
423 (0x0006, "SpeedCurrent"),
424 (0x0007, "RockSupport"),
425 (0x0008, "RockSetting"),
426 (0x0009, "WindSupport"),
427 (0x000A, "WindSetting"),
428 (0x000B, "AirflowDirection"),
429 ]
430}
431
432pub fn get_command_list() -> Vec<(u32, &'static str)> {
435 vec![
436 (0x00, "Step"),
437 ]
438}
439
440pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
441 match cmd_id {
442 0x00 => Some("Step"),
443 _ => None,
444 }
445}
446
447pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
448 match cmd_id {
449 0x00 => Some(vec![
450 crate::clusters::codec::CommandField { tag: 0, name: "direction", kind: crate::clusters::codec::FieldKind::Enum { name: "StepDirection", variants: &[(0, "Increase"), (1, "Decrease")] }, optional: false, nullable: false },
451 crate::clusters::codec::CommandField { tag: 1, name: "wrap", kind: crate::clusters::codec::FieldKind::Bool, optional: true, nullable: false },
452 crate::clusters::codec::CommandField { tag: 2, name: "lowest_off", kind: crate::clusters::codec::FieldKind::Bool, optional: true, nullable: false },
453 ]),
454 _ => None,
455 }
456}
457
458pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
459 match cmd_id {
460 0x00 => {
461 let direction = {
462 let n = crate::clusters::codec::json_util::get_u64(args, "direction")?;
463 StepDirection::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid StepDirection: {}", n))?
464 };
465 let wrap = crate::clusters::codec::json_util::get_bool(args, "wrap")?;
466 let lowest_off = crate::clusters::codec::json_util::get_bool(args, "lowest_off")?;
467 encode_step(direction, wrap, lowest_off)
468 }
469 _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
470 }
471}
472
473pub async fn step(conn: &crate::controller::Connection, endpoint: u16, direction: StepDirection, wrap: bool, lowest_off: bool) -> anyhow::Result<()> {
477 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_CMD_ID_STEP, &encode_step(direction, wrap, lowest_off)?).await?;
478 Ok(())
479}
480
481pub async fn read_fan_mode(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<FanMode> {
483 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_FANMODE).await?;
484 decode_fan_mode(&tlv)
485}
486
487pub async fn read_fan_mode_sequence(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<FanModeSequence> {
489 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_FANMODESEQUENCE).await?;
490 decode_fan_mode_sequence(&tlv)
491}
492
493pub async fn read_percent_setting(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u8>> {
495 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_PERCENTSETTING).await?;
496 decode_percent_setting(&tlv)
497}
498
499pub async fn read_percent_current(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
501 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_PERCENTCURRENT).await?;
502 decode_percent_current(&tlv)
503}
504
505pub async fn read_speed_max(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
507 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_SPEEDMAX).await?;
508 decode_speed_max(&tlv)
509}
510
511pub async fn read_speed_setting(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u8>> {
513 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_SPEEDSETTING).await?;
514 decode_speed_setting(&tlv)
515}
516
517pub async fn read_speed_current(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
519 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_SPEEDCURRENT).await?;
520 decode_speed_current(&tlv)
521}
522
523pub async fn read_rock_support(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Rock> {
525 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_ROCKSUPPORT).await?;
526 decode_rock_support(&tlv)
527}
528
529pub async fn read_rock_setting(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Rock> {
531 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_ROCKSETTING).await?;
532 decode_rock_setting(&tlv)
533}
534
535pub async fn read_wind_support(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Wind> {
537 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_WINDSUPPORT).await?;
538 decode_wind_support(&tlv)
539}
540
541pub async fn read_wind_setting(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Wind> {
543 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_WINDSETTING).await?;
544 decode_wind_setting(&tlv)
545}
546
547pub async fn read_airflow_direction(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<AirflowDirection> {
549 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_AIRFLOWDIRECTION).await?;
550 decode_airflow_direction(&tlv)
551}
552