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 AirflowDirection {
16 Forward = 0,
18 Reverse = 1,
20}
21
22impl AirflowDirection {
23 pub fn from_u8(value: u8) -> Option<Self> {
25 match value {
26 0 => Some(AirflowDirection::Forward),
27 1 => Some(AirflowDirection::Reverse),
28 _ => None,
29 }
30 }
31
32 pub fn to_u8(self) -> u8 {
34 self as u8
35 }
36}
37
38impl From<AirflowDirection> for u8 {
39 fn from(val: AirflowDirection) -> Self {
40 val as u8
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
45#[repr(u8)]
46pub enum FanMode {
47 Off = 0,
49 Low = 1,
51 Medium = 2,
53 High = 3,
55 On = 4,
56 Auto = 5,
58 Smart = 6,
60}
61
62impl FanMode {
63 pub fn from_u8(value: u8) -> Option<Self> {
65 match value {
66 0 => Some(FanMode::Off),
67 1 => Some(FanMode::Low),
68 2 => Some(FanMode::Medium),
69 3 => Some(FanMode::High),
70 4 => Some(FanMode::On),
71 5 => Some(FanMode::Auto),
72 6 => Some(FanMode::Smart),
73 _ => None,
74 }
75 }
76
77 pub fn to_u8(self) -> u8 {
79 self as u8
80 }
81}
82
83impl From<FanMode> for u8 {
84 fn from(val: FanMode) -> Self {
85 val as u8
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
90#[repr(u8)]
91pub enum FanModeSequence {
92 Offlowmedhigh = 0,
94 Offlowhigh = 1,
96 Offlowmedhighauto = 2,
98 Offlowhighauto = 3,
100 Offhighauto = 4,
102 Offhigh = 5,
104}
105
106impl FanModeSequence {
107 pub fn from_u8(value: u8) -> Option<Self> {
109 match value {
110 0 => Some(FanModeSequence::Offlowmedhigh),
111 1 => Some(FanModeSequence::Offlowhigh),
112 2 => Some(FanModeSequence::Offlowmedhighauto),
113 3 => Some(FanModeSequence::Offlowhighauto),
114 4 => Some(FanModeSequence::Offhighauto),
115 5 => Some(FanModeSequence::Offhigh),
116 _ => None,
117 }
118 }
119
120 pub fn to_u8(self) -> u8 {
122 self as u8
123 }
124}
125
126impl From<FanModeSequence> for u8 {
127 fn from(val: FanModeSequence) -> Self {
128 val as u8
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
133#[repr(u8)]
134pub enum StepDirection {
135 Increase = 0,
137 Decrease = 1,
139}
140
141impl StepDirection {
142 pub fn from_u8(value: u8) -> Option<Self> {
144 match value {
145 0 => Some(StepDirection::Increase),
146 1 => Some(StepDirection::Decrease),
147 _ => None,
148 }
149 }
150
151 pub fn to_u8(self) -> u8 {
153 self as u8
154 }
155}
156
157impl From<StepDirection> for u8 {
158 fn from(val: StepDirection) -> Self {
159 val as u8
160 }
161}
162
163pub type Rock = u8;
167
168pub mod rock {
170 pub const ROCK_LEFT_RIGHT: u8 = 0x01;
172 pub const ROCK_UP_DOWN: u8 = 0x02;
174 pub const ROCK_ROUND: u8 = 0x04;
176}
177
178pub type Wind = u8;
180
181pub mod wind {
183 pub const SLEEP_WIND: u8 = 0x01;
185 pub const NATURAL_WIND: u8 = 0x02;
187}
188
189pub fn encode_step(direction: StepDirection, wrap: bool, lowest_off: bool) -> anyhow::Result<Vec<u8>> {
193 let tlv = tlv::TlvItemEnc {
194 tag: 0,
195 value: tlv::TlvItemValueEnc::StructInvisible(vec![
196 (0, tlv::TlvItemValueEnc::UInt8(direction.to_u8())).into(),
197 (1, tlv::TlvItemValueEnc::Bool(wrap)).into(),
198 (2, tlv::TlvItemValueEnc::Bool(lowest_off)).into(),
199 ]),
200 };
201 Ok(tlv.encode()?)
202}
203
204pub fn decode_fan_mode(inp: &tlv::TlvItemValue) -> anyhow::Result<FanMode> {
208 if let tlv::TlvItemValue::Int(v) = inp {
209 FanMode::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
210 } else {
211 Err(anyhow::anyhow!("Expected Integer"))
212 }
213}
214
215pub fn decode_fan_mode_sequence(inp: &tlv::TlvItemValue) -> anyhow::Result<FanModeSequence> {
217 if let tlv::TlvItemValue::Int(v) = inp {
218 FanModeSequence::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
219 } else {
220 Err(anyhow::anyhow!("Expected Integer"))
221 }
222}
223
224pub fn decode_percent_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
226 if let tlv::TlvItemValue::Int(v) = inp {
227 Ok(Some(*v as u8))
228 } else {
229 Ok(None)
230 }
231}
232
233pub fn decode_percent_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
235 if let tlv::TlvItemValue::Int(v) = inp {
236 Ok(*v as u8)
237 } else {
238 Err(anyhow::anyhow!("Expected UInt8"))
239 }
240}
241
242pub fn decode_speed_max(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
244 if let tlv::TlvItemValue::Int(v) = inp {
245 Ok(*v as u8)
246 } else {
247 Err(anyhow::anyhow!("Expected UInt8"))
248 }
249}
250
251pub fn decode_speed_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
253 if let tlv::TlvItemValue::Int(v) = inp {
254 Ok(Some(*v as u8))
255 } else {
256 Ok(None)
257 }
258}
259
260pub fn decode_speed_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
262 if let tlv::TlvItemValue::Int(v) = inp {
263 Ok(*v as u8)
264 } else {
265 Err(anyhow::anyhow!("Expected UInt8"))
266 }
267}
268
269pub fn decode_rock_support(inp: &tlv::TlvItemValue) -> anyhow::Result<Rock> {
271 if let tlv::TlvItemValue::Int(v) = inp {
272 Ok(*v as u8)
273 } else {
274 Err(anyhow::anyhow!("Expected Integer"))
275 }
276}
277
278pub fn decode_rock_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Rock> {
280 if let tlv::TlvItemValue::Int(v) = inp {
281 Ok(*v as u8)
282 } else {
283 Err(anyhow::anyhow!("Expected Integer"))
284 }
285}
286
287pub fn decode_wind_support(inp: &tlv::TlvItemValue) -> anyhow::Result<Wind> {
289 if let tlv::TlvItemValue::Int(v) = inp {
290 Ok(*v as u8)
291 } else {
292 Err(anyhow::anyhow!("Expected Integer"))
293 }
294}
295
296pub fn decode_wind_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Wind> {
298 if let tlv::TlvItemValue::Int(v) = inp {
299 Ok(*v as u8)
300 } else {
301 Err(anyhow::anyhow!("Expected Integer"))
302 }
303}
304
305pub fn decode_airflow_direction(inp: &tlv::TlvItemValue) -> anyhow::Result<AirflowDirection> {
307 if let tlv::TlvItemValue::Int(v) = inp {
308 AirflowDirection::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
309 } else {
310 Err(anyhow::anyhow!("Expected Integer"))
311 }
312}
313
314
315pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
327 if cluster_id != 0x0202 {
329 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0202, got {}\"}}", cluster_id);
330 }
331
332 match attribute_id {
333 0x0000 => {
334 match decode_fan_mode(tlv_value) {
335 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
336 Err(e) => format!("{{\"error\": \"{}\"}}", e),
337 }
338 }
339 0x0001 => {
340 match decode_fan_mode_sequence(tlv_value) {
341 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
342 Err(e) => format!("{{\"error\": \"{}\"}}", e),
343 }
344 }
345 0x0002 => {
346 match decode_percent_setting(tlv_value) {
347 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
348 Err(e) => format!("{{\"error\": \"{}\"}}", e),
349 }
350 }
351 0x0003 => {
352 match decode_percent_current(tlv_value) {
353 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
354 Err(e) => format!("{{\"error\": \"{}\"}}", e),
355 }
356 }
357 0x0004 => {
358 match decode_speed_max(tlv_value) {
359 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
360 Err(e) => format!("{{\"error\": \"{}\"}}", e),
361 }
362 }
363 0x0005 => {
364 match decode_speed_setting(tlv_value) {
365 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
366 Err(e) => format!("{{\"error\": \"{}\"}}", e),
367 }
368 }
369 0x0006 => {
370 match decode_speed_current(tlv_value) {
371 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
372 Err(e) => format!("{{\"error\": \"{}\"}}", e),
373 }
374 }
375 0x0007 => {
376 match decode_rock_support(tlv_value) {
377 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
378 Err(e) => format!("{{\"error\": \"{}\"}}", e),
379 }
380 }
381 0x0008 => {
382 match decode_rock_setting(tlv_value) {
383 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
384 Err(e) => format!("{{\"error\": \"{}\"}}", e),
385 }
386 }
387 0x0009 => {
388 match decode_wind_support(tlv_value) {
389 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
390 Err(e) => format!("{{\"error\": \"{}\"}}", e),
391 }
392 }
393 0x000A => {
394 match decode_wind_setting(tlv_value) {
395 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
396 Err(e) => format!("{{\"error\": \"{}\"}}", e),
397 }
398 }
399 0x000B => {
400 match decode_airflow_direction(tlv_value) {
401 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
402 Err(e) => format!("{{\"error\": \"{}\"}}", e),
403 }
404 }
405 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
406 }
407}
408
409pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
414 vec![
415 (0x0000, "FanMode"),
416 (0x0001, "FanModeSequence"),
417 (0x0002, "PercentSetting"),
418 (0x0003, "PercentCurrent"),
419 (0x0004, "SpeedMax"),
420 (0x0005, "SpeedSetting"),
421 (0x0006, "SpeedCurrent"),
422 (0x0007, "RockSupport"),
423 (0x0008, "RockSetting"),
424 (0x0009, "WindSupport"),
425 (0x000A, "WindSetting"),
426 (0x000B, "AirflowDirection"),
427 ]
428}
429