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 EndProductType {
16 Rollershade = 0,
18 Romanshade = 1,
20 Balloonshade = 2,
22 Wovenwood = 3,
24 Pleatedshade = 4,
26 Cellularshade = 5,
28 Layeredshade = 6,
30 Layeredshade2d = 7,
32 Sheershade = 8,
34 Tiltonlyinteriorblind = 9,
36 Interiorblind = 10,
38 Verticalblindstripcurtain = 11,
40 Interiorvenetianblind = 12,
42 Exteriorvenetianblind = 13,
44 Lateralleftcurtain = 14,
46 Lateralrightcurtain = 15,
48 Centralcurtain = 16,
50 Rollershutter = 17,
52 Exteriorverticalscreen = 18,
54 Awningterracepatio = 19,
56 Awningverticalscreen = 20,
58 Tiltonlypergola = 21,
60 Swingingshutter = 22,
62 Slidingshutter = 23,
64 Unknown = 255,
66}
67
68impl EndProductType {
69 pub fn from_u8(value: u8) -> Option<Self> {
71 match value {
72 0 => Some(EndProductType::Rollershade),
73 1 => Some(EndProductType::Romanshade),
74 2 => Some(EndProductType::Balloonshade),
75 3 => Some(EndProductType::Wovenwood),
76 4 => Some(EndProductType::Pleatedshade),
77 5 => Some(EndProductType::Cellularshade),
78 6 => Some(EndProductType::Layeredshade),
79 7 => Some(EndProductType::Layeredshade2d),
80 8 => Some(EndProductType::Sheershade),
81 9 => Some(EndProductType::Tiltonlyinteriorblind),
82 10 => Some(EndProductType::Interiorblind),
83 11 => Some(EndProductType::Verticalblindstripcurtain),
84 12 => Some(EndProductType::Interiorvenetianblind),
85 13 => Some(EndProductType::Exteriorvenetianblind),
86 14 => Some(EndProductType::Lateralleftcurtain),
87 15 => Some(EndProductType::Lateralrightcurtain),
88 16 => Some(EndProductType::Centralcurtain),
89 17 => Some(EndProductType::Rollershutter),
90 18 => Some(EndProductType::Exteriorverticalscreen),
91 19 => Some(EndProductType::Awningterracepatio),
92 20 => Some(EndProductType::Awningverticalscreen),
93 21 => Some(EndProductType::Tiltonlypergola),
94 22 => Some(EndProductType::Swingingshutter),
95 23 => Some(EndProductType::Slidingshutter),
96 255 => Some(EndProductType::Unknown),
97 _ => None,
98 }
99 }
100
101 pub fn to_u8(self) -> u8 {
103 self as u8
104 }
105}
106
107impl From<EndProductType> for u8 {
108 fn from(val: EndProductType) -> Self {
109 val as u8
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
114#[repr(u8)]
115pub enum Type {
116 Rollershade = 0,
118 Rollershade2motor = 1,
120 Rollershadeexterior = 2,
122 Rollershadeexterior2motor = 3,
124 Drapery = 4,
126 Awning = 5,
128 Shutter = 6,
130 Tiltblindtiltonly = 7,
132 Tiltblindliftandtilt = 8,
134 Projectorscreen = 9,
136 Unknown = 255,
138}
139
140impl Type {
141 pub fn from_u8(value: u8) -> Option<Self> {
143 match value {
144 0 => Some(Type::Rollershade),
145 1 => Some(Type::Rollershade2motor),
146 2 => Some(Type::Rollershadeexterior),
147 3 => Some(Type::Rollershadeexterior2motor),
148 4 => Some(Type::Drapery),
149 5 => Some(Type::Awning),
150 6 => Some(Type::Shutter),
151 7 => Some(Type::Tiltblindtiltonly),
152 8 => Some(Type::Tiltblindliftandtilt),
153 9 => Some(Type::Projectorscreen),
154 255 => Some(Type::Unknown),
155 _ => None,
156 }
157 }
158
159 pub fn to_u8(self) -> u8 {
161 self as u8
162 }
163}
164
165impl From<Type> for u8 {
166 fn from(val: Type) -> Self {
167 val as u8
168 }
169}
170
171pub type ConfigStatus = u8;
175
176pub mod configstatus {
178 pub const OPERATIONAL: u8 = 0x01;
180 pub const ONLINE_RESERVED: u8 = 0x02;
181 pub const LIFT_MOVEMENT_REVERSED: u8 = 0x04;
183 pub const LIFT_POSITION_AWARE: u8 = 0x08;
185 pub const TILT_POSITION_AWARE: u8 = 0x10;
187 pub const LIFT_ENCODER_CONTROLLED: u8 = 0x20;
189 pub const TILT_ENCODER_CONTROLLED: u8 = 0x40;
191}
192
193pub type Mode = u8;
195
196pub mod mode {
198 pub const MOTOR_DIRECTION_REVERSED: u8 = 0x01;
200 pub const CALIBRATION_MODE: u8 = 0x02;
202 pub const MAINTENANCE_MODE: u8 = 0x04;
204 pub const LED_FEEDBACK: u8 = 0x08;
206}
207
208pub type OperationalStatus = u8;
210
211pub type SafetyStatus = u16;
213
214pub mod safetystatus {
216 pub const REMOTE_LOCKOUT: u16 = 0x01;
218 pub const TAMPER_DETECTION: u16 = 0x02;
220 pub const FAILED_COMMUNICATION: u16 = 0x04;
222 pub const POSITION_FAILURE: u16 = 0x08;
224 pub const THERMAL_PROTECTION: u16 = 0x10;
226 pub const OBSTACLE_DETECTED: u16 = 0x20;
228 pub const POWER: u16 = 0x40;
230 pub const STOP_INPUT: u16 = 0x80;
232 pub const MOTOR_JAMMED: u16 = 0x100;
234 pub const HARDWARE_FAILURE: u16 = 0x200;
236 pub const MANUAL_OPERATION: u16 = 0x400;
238 pub const PROTECTION: u16 = 0x800;
240}
241
242pub fn encode_go_to_lift_percentage(lift_percent100ths_value: u8) -> anyhow::Result<Vec<u8>> {
246 let tlv = tlv::TlvItemEnc {
247 tag: 0,
248 value: tlv::TlvItemValueEnc::StructInvisible(vec![
249 (0, tlv::TlvItemValueEnc::UInt8(lift_percent100ths_value)).into(),
250 ]),
251 };
252 Ok(tlv.encode()?)
253}
254
255pub fn encode_go_to_tilt_percentage(tilt_percent100ths_value: u8) -> anyhow::Result<Vec<u8>> {
257 let tlv = tlv::TlvItemEnc {
258 tag: 0,
259 value: tlv::TlvItemValueEnc::StructInvisible(vec![
260 (0, tlv::TlvItemValueEnc::UInt8(tilt_percent100ths_value)).into(),
261 ]),
262 };
263 Ok(tlv.encode()?)
264}
265
266pub fn decode_type_(inp: &tlv::TlvItemValue) -> anyhow::Result<Type> {
270 if let tlv::TlvItemValue::Int(v) = inp {
271 Type::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
272 } else {
273 Err(anyhow::anyhow!("Expected Integer"))
274 }
275}
276
277pub fn decode_number_of_actuations_lift(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
279 if let tlv::TlvItemValue::Int(v) = inp {
280 Ok(*v as u16)
281 } else {
282 Err(anyhow::anyhow!("Expected UInt16"))
283 }
284}
285
286pub fn decode_number_of_actuations_tilt(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
288 if let tlv::TlvItemValue::Int(v) = inp {
289 Ok(*v as u16)
290 } else {
291 Err(anyhow::anyhow!("Expected UInt16"))
292 }
293}
294
295pub fn decode_config_status(inp: &tlv::TlvItemValue) -> anyhow::Result<ConfigStatus> {
297 if let tlv::TlvItemValue::Int(v) = inp {
298 Ok(*v as u8)
299 } else {
300 Err(anyhow::anyhow!("Expected Integer"))
301 }
302}
303
304pub fn decode_current_position_lift_percentage(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
306 if let tlv::TlvItemValue::Int(v) = inp {
307 Ok(Some(*v as u8))
308 } else {
309 Ok(None)
310 }
311}
312
313pub fn decode_current_position_tilt_percentage(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
315 if let tlv::TlvItemValue::Int(v) = inp {
316 Ok(Some(*v as u8))
317 } else {
318 Ok(None)
319 }
320}
321
322pub fn decode_operational_status(inp: &tlv::TlvItemValue) -> anyhow::Result<OperationalStatus> {
324 if let tlv::TlvItemValue::Int(v) = inp {
325 Ok(*v as u8)
326 } else {
327 Err(anyhow::anyhow!("Expected Integer"))
328 }
329}
330
331pub fn decode_target_position_lift_percent100ths(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
333 if let tlv::TlvItemValue::Int(v) = inp {
334 Ok(Some(*v as u8))
335 } else {
336 Ok(None)
337 }
338}
339
340pub fn decode_target_position_tilt_percent100ths(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
342 if let tlv::TlvItemValue::Int(v) = inp {
343 Ok(Some(*v as u8))
344 } else {
345 Ok(None)
346 }
347}
348
349pub fn decode_end_product_type(inp: &tlv::TlvItemValue) -> anyhow::Result<EndProductType> {
351 if let tlv::TlvItemValue::Int(v) = inp {
352 EndProductType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
353 } else {
354 Err(anyhow::anyhow!("Expected Integer"))
355 }
356}
357
358pub fn decode_current_position_lift_percent100ths(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
360 if let tlv::TlvItemValue::Int(v) = inp {
361 Ok(Some(*v as u8))
362 } else {
363 Ok(None)
364 }
365}
366
367pub fn decode_current_position_tilt_percent100ths(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
369 if let tlv::TlvItemValue::Int(v) = inp {
370 Ok(Some(*v as u8))
371 } else {
372 Ok(None)
373 }
374}
375
376pub fn decode_mode(inp: &tlv::TlvItemValue) -> anyhow::Result<Mode> {
378 if let tlv::TlvItemValue::Int(v) = inp {
379 Ok(*v as u8)
380 } else {
381 Err(anyhow::anyhow!("Expected Integer"))
382 }
383}
384
385pub fn decode_safety_status(inp: &tlv::TlvItemValue) -> anyhow::Result<SafetyStatus> {
387 if let tlv::TlvItemValue::Int(v) = inp {
388 Ok(*v as u16)
389 } else {
390 Err(anyhow::anyhow!("Expected Integer"))
391 }
392}
393
394
395pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
407 if cluster_id != 0x0102 {
409 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0102, got {}\"}}", cluster_id);
410 }
411
412 match attribute_id {
413 0x0000 => {
414 match decode_type_(tlv_value) {
415 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
416 Err(e) => format!("{{\"error\": \"{}\"}}", e),
417 }
418 }
419 0x0005 => {
420 match decode_number_of_actuations_lift(tlv_value) {
421 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
422 Err(e) => format!("{{\"error\": \"{}\"}}", e),
423 }
424 }
425 0x0006 => {
426 match decode_number_of_actuations_tilt(tlv_value) {
427 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
428 Err(e) => format!("{{\"error\": \"{}\"}}", e),
429 }
430 }
431 0x0007 => {
432 match decode_config_status(tlv_value) {
433 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
434 Err(e) => format!("{{\"error\": \"{}\"}}", e),
435 }
436 }
437 0x0008 => {
438 match decode_current_position_lift_percentage(tlv_value) {
439 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
440 Err(e) => format!("{{\"error\": \"{}\"}}", e),
441 }
442 }
443 0x0009 => {
444 match decode_current_position_tilt_percentage(tlv_value) {
445 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
446 Err(e) => format!("{{\"error\": \"{}\"}}", e),
447 }
448 }
449 0x000A => {
450 match decode_operational_status(tlv_value) {
451 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
452 Err(e) => format!("{{\"error\": \"{}\"}}", e),
453 }
454 }
455 0x000B => {
456 match decode_target_position_lift_percent100ths(tlv_value) {
457 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
458 Err(e) => format!("{{\"error\": \"{}\"}}", e),
459 }
460 }
461 0x000C => {
462 match decode_target_position_tilt_percent100ths(tlv_value) {
463 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
464 Err(e) => format!("{{\"error\": \"{}\"}}", e),
465 }
466 }
467 0x000D => {
468 match decode_end_product_type(tlv_value) {
469 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
470 Err(e) => format!("{{\"error\": \"{}\"}}", e),
471 }
472 }
473 0x000E => {
474 match decode_current_position_lift_percent100ths(tlv_value) {
475 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
476 Err(e) => format!("{{\"error\": \"{}\"}}", e),
477 }
478 }
479 0x000F => {
480 match decode_current_position_tilt_percent100ths(tlv_value) {
481 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
482 Err(e) => format!("{{\"error\": \"{}\"}}", e),
483 }
484 }
485 0x0017 => {
486 match decode_mode(tlv_value) {
487 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
488 Err(e) => format!("{{\"error\": \"{}\"}}", e),
489 }
490 }
491 0x001A => {
492 match decode_safety_status(tlv_value) {
493 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
494 Err(e) => format!("{{\"error\": \"{}\"}}", e),
495 }
496 }
497 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
498 }
499}
500
501pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
506 vec![
507 (0x0000, "Type"),
508 (0x0005, "NumberOfActuationsLift"),
509 (0x0006, "NumberOfActuationsTilt"),
510 (0x0007, "ConfigStatus"),
511 (0x0008, "CurrentPositionLiftPercentage"),
512 (0x0009, "CurrentPositionTiltPercentage"),
513 (0x000A, "OperationalStatus"),
514 (0x000B, "TargetPositionLiftPercent100ths"),
515 (0x000C, "TargetPositionTiltPercent100ths"),
516 (0x000D, "EndProductType"),
517 (0x000E, "CurrentPositionLiftPercent100ths"),
518 (0x000F, "CurrentPositionTiltPercent100ths"),
519 (0x0017, "Mode"),
520 (0x001A, "SafetyStatus"),
521 ]
522}
523