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 OccupancySensorType {
18 Pir = 0,
20 Ultrasonic = 1,
22 Pirandultrasonic = 2,
24 Physicalcontact = 3,
26}
27
28impl OccupancySensorType {
29 pub fn from_u8(value: u8) -> Option<Self> {
31 match value {
32 0 => Some(OccupancySensorType::Pir),
33 1 => Some(OccupancySensorType::Ultrasonic),
34 2 => Some(OccupancySensorType::Pirandultrasonic),
35 3 => Some(OccupancySensorType::Physicalcontact),
36 _ => None,
37 }
38 }
39
40 pub fn to_u8(self) -> u8 {
42 self as u8
43 }
44}
45
46impl From<OccupancySensorType> for u8 {
47 fn from(val: OccupancySensorType) -> Self {
48 val as u8
49 }
50}
51
52pub type Occupancy = u8;
56
57pub mod occupancy {
59 pub const OCCUPIED: u8 = 0x01;
61}
62
63pub type OccupancySensorTypeBitmap = u8;
65
66pub mod occupancysensortype {
68 pub const PIR: u8 = 0x01;
70 pub const ULTRASONIC: u8 = 0x02;
72 pub const PHYSICAL_CONTACT: u8 = 0x04;
74}
75
76#[derive(Debug, serde::Serialize)]
79pub struct HoldTimeLimits {
80 pub hold_time_min: Option<u16>,
81 pub hold_time_max: Option<u16>,
82 pub hold_time_default: Option<u16>,
83}
84
85pub fn decode_occupancy(inp: &tlv::TlvItemValue) -> anyhow::Result<Occupancy> {
89 if let tlv::TlvItemValue::Int(v) = inp {
90 Ok(*v as u8)
91 } else {
92 Err(anyhow::anyhow!("Expected Integer"))
93 }
94}
95
96pub fn decode_occupancy_sensor_type(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancySensorType> {
98 if let tlv::TlvItemValue::Int(v) = inp {
99 OccupancySensorType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
100 } else {
101 Err(anyhow::anyhow!("Expected Integer"))
102 }
103}
104
105pub fn decode_occupancy_sensor_type_bitmap(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancySensorTypeBitmap> {
107 if let tlv::TlvItemValue::Int(v) = inp {
108 Ok(*v as u8)
109 } else {
110 Err(anyhow::anyhow!("Expected Integer"))
111 }
112}
113
114pub fn decode_hold_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
116 if let tlv::TlvItemValue::Int(v) = inp {
117 Ok(*v as u16)
118 } else {
119 Err(anyhow::anyhow!("Expected UInt16"))
120 }
121}
122
123pub fn decode_hold_time_limits(inp: &tlv::TlvItemValue) -> anyhow::Result<HoldTimeLimits> {
125 if let tlv::TlvItemValue::List(_fields) = inp {
126 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
128 Ok(HoldTimeLimits {
129 hold_time_min: item.get_int(&[0]).map(|v| v as u16),
130 hold_time_max: item.get_int(&[1]).map(|v| v as u16),
131 hold_time_default: item.get_int(&[2]).map(|v| v as u16),
132 })
133 } else {
134 Err(anyhow::anyhow!("Expected struct fields"))
135 }
136}
137
138pub fn decode_pir_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
140 if let tlv::TlvItemValue::Int(v) = inp {
141 Ok(*v as u16)
142 } else {
143 Err(anyhow::anyhow!("Expected UInt16"))
144 }
145}
146
147pub fn decode_pir_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
149 if let tlv::TlvItemValue::Int(v) = inp {
150 Ok(*v as u16)
151 } else {
152 Err(anyhow::anyhow!("Expected UInt16"))
153 }
154}
155
156pub fn decode_pir_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
158 if let tlv::TlvItemValue::Int(v) = inp {
159 Ok(*v as u8)
160 } else {
161 Err(anyhow::anyhow!("Expected UInt8"))
162 }
163}
164
165pub fn decode_ultrasonic_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
167 if let tlv::TlvItemValue::Int(v) = inp {
168 Ok(*v as u16)
169 } else {
170 Err(anyhow::anyhow!("Expected UInt16"))
171 }
172}
173
174pub fn decode_ultrasonic_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
176 if let tlv::TlvItemValue::Int(v) = inp {
177 Ok(*v as u16)
178 } else {
179 Err(anyhow::anyhow!("Expected UInt16"))
180 }
181}
182
183pub fn decode_ultrasonic_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
185 if let tlv::TlvItemValue::Int(v) = inp {
186 Ok(*v as u8)
187 } else {
188 Err(anyhow::anyhow!("Expected UInt8"))
189 }
190}
191
192pub fn decode_physical_contact_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
194 if let tlv::TlvItemValue::Int(v) = inp {
195 Ok(*v as u16)
196 } else {
197 Err(anyhow::anyhow!("Expected UInt16"))
198 }
199}
200
201pub fn decode_physical_contact_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
203 if let tlv::TlvItemValue::Int(v) = inp {
204 Ok(*v as u16)
205 } else {
206 Err(anyhow::anyhow!("Expected UInt16"))
207 }
208}
209
210pub fn decode_physical_contact_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
212 if let tlv::TlvItemValue::Int(v) = inp {
213 Ok(*v as u8)
214 } else {
215 Err(anyhow::anyhow!("Expected UInt8"))
216 }
217}
218
219
220pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
232 if cluster_id != 0x0406 {
234 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0406, got {}\"}}", cluster_id);
235 }
236
237 match attribute_id {
238 0x0000 => {
239 match decode_occupancy(tlv_value) {
240 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
241 Err(e) => format!("{{\"error\": \"{}\"}}", e),
242 }
243 }
244 0x0001 => {
245 match decode_occupancy_sensor_type(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 0x0002 => {
251 match decode_occupancy_sensor_type_bitmap(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 0x0003 => {
257 match decode_hold_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 0x0004 => {
263 match decode_hold_time_limits(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 0x0010 => {
269 match decode_pir_occupied_to_unoccupied_delay(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 0x0011 => {
275 match decode_pir_unoccupied_to_occupied_delay(tlv_value) {
276 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
277 Err(e) => format!("{{\"error\": \"{}\"}}", e),
278 }
279 }
280 0x0012 => {
281 match decode_pir_unoccupied_to_occupied_threshold(tlv_value) {
282 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
283 Err(e) => format!("{{\"error\": \"{}\"}}", e),
284 }
285 }
286 0x0020 => {
287 match decode_ultrasonic_occupied_to_unoccupied_delay(tlv_value) {
288 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
289 Err(e) => format!("{{\"error\": \"{}\"}}", e),
290 }
291 }
292 0x0021 => {
293 match decode_ultrasonic_unoccupied_to_occupied_delay(tlv_value) {
294 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
295 Err(e) => format!("{{\"error\": \"{}\"}}", e),
296 }
297 }
298 0x0022 => {
299 match decode_ultrasonic_unoccupied_to_occupied_threshold(tlv_value) {
300 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
301 Err(e) => format!("{{\"error\": \"{}\"}}", e),
302 }
303 }
304 0x0030 => {
305 match decode_physical_contact_occupied_to_unoccupied_delay(tlv_value) {
306 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
307 Err(e) => format!("{{\"error\": \"{}\"}}", e),
308 }
309 }
310 0x0031 => {
311 match decode_physical_contact_unoccupied_to_occupied_delay(tlv_value) {
312 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
313 Err(e) => format!("{{\"error\": \"{}\"}}", e),
314 }
315 }
316 0x0032 => {
317 match decode_physical_contact_unoccupied_to_occupied_threshold(tlv_value) {
318 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
319 Err(e) => format!("{{\"error\": \"{}\"}}", e),
320 }
321 }
322 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
323 }
324}
325
326pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
331 vec![
332 (0x0000, "Occupancy"),
333 (0x0001, "OccupancySensorType"),
334 (0x0002, "OccupancySensorTypeBitmap"),
335 (0x0003, "HoldTime"),
336 (0x0004, "HoldTimeLimits"),
337 (0x0010, "PIROccupiedToUnoccupiedDelay"),
338 (0x0011, "PIRUnoccupiedToOccupiedDelay"),
339 (0x0012, "PIRUnoccupiedToOccupiedThreshold"),
340 (0x0020, "UltrasonicOccupiedToUnoccupiedDelay"),
341 (0x0021, "UltrasonicUnoccupiedToOccupiedDelay"),
342 (0x0022, "UltrasonicUnoccupiedToOccupiedThreshold"),
343 (0x0030, "PhysicalContactOccupiedToUnoccupiedDelay"),
344 (0x0031, "PhysicalContactUnoccupiedToOccupiedDelay"),
345 (0x0032, "PhysicalContactUnoccupiedToOccupiedThreshold"),
346 ]
347}
348
349pub async fn read_occupancy(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Occupancy> {
353 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_OCCUPANCY).await?;
354 decode_occupancy(&tlv)
355}
356
357pub async fn read_occupancy_sensor_type(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<OccupancySensorType> {
359 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_OCCUPANCYSENSORTYPE).await?;
360 decode_occupancy_sensor_type(&tlv)
361}
362
363pub async fn read_occupancy_sensor_type_bitmap(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<OccupancySensorTypeBitmap> {
365 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_OCCUPANCYSENSORTYPEBITMAP).await?;
366 decode_occupancy_sensor_type_bitmap(&tlv)
367}
368
369pub async fn read_hold_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
371 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_HOLDTIME).await?;
372 decode_hold_time(&tlv)
373}
374
375pub async fn read_hold_time_limits(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<HoldTimeLimits> {
377 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_HOLDTIMELIMITS).await?;
378 decode_hold_time_limits(&tlv)
379}
380
381pub async fn read_pir_occupied_to_unoccupied_delay(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
383 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_PIROCCUPIEDTOUNOCCUPIEDDELAY).await?;
384 decode_pir_occupied_to_unoccupied_delay(&tlv)
385}
386
387pub async fn read_pir_unoccupied_to_occupied_delay(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
389 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_PIRUNOCCUPIEDTOOCCUPIEDDELAY).await?;
390 decode_pir_unoccupied_to_occupied_delay(&tlv)
391}
392
393pub async fn read_pir_unoccupied_to_occupied_threshold(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
395 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_PIRUNOCCUPIEDTOOCCUPIEDTHRESHOLD).await?;
396 decode_pir_unoccupied_to_occupied_threshold(&tlv)
397}
398
399pub async fn read_ultrasonic_occupied_to_unoccupied_delay(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
401 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_ULTRASONICOCCUPIEDTOUNOCCUPIEDDELAY).await?;
402 decode_ultrasonic_occupied_to_unoccupied_delay(&tlv)
403}
404
405pub async fn read_ultrasonic_unoccupied_to_occupied_delay(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
407 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_ULTRASONICUNOCCUPIEDTOOCCUPIEDDELAY).await?;
408 decode_ultrasonic_unoccupied_to_occupied_delay(&tlv)
409}
410
411pub async fn read_ultrasonic_unoccupied_to_occupied_threshold(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
413 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_ULTRASONICUNOCCUPIEDTOOCCUPIEDTHRESHOLD).await?;
414 decode_ultrasonic_unoccupied_to_occupied_threshold(&tlv)
415}
416
417pub async fn read_physical_contact_occupied_to_unoccupied_delay(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
419 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_PHYSICALCONTACTOCCUPIEDTOUNOCCUPIEDDELAY).await?;
420 decode_physical_contact_occupied_to_unoccupied_delay(&tlv)
421}
422
423pub async fn read_physical_contact_unoccupied_to_occupied_delay(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
425 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_PHYSICALCONTACTUNOCCUPIEDTOOCCUPIEDDELAY).await?;
426 decode_physical_contact_unoccupied_to_occupied_delay(&tlv)
427}
428
429pub async fn read_physical_contact_unoccupied_to_occupied_threshold(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
431 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_PHYSICALCONTACTUNOCCUPIEDTOOCCUPIEDTHRESHOLD).await?;
432 decode_physical_contact_unoccupied_to_occupied_threshold(&tlv)
433}
434
435#[derive(Debug, serde::Serialize)]
436pub struct OccupancyChangedEvent {
437 pub occupancy: Option<Occupancy>,
438}
439
440pub fn decode_occupancy_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancyChangedEvent> {
444 if let tlv::TlvItemValue::List(_fields) = inp {
445 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
446 Ok(OccupancyChangedEvent {
447 occupancy: item.get_int(&[0]).map(|v| v as u8),
448 })
449 } else {
450 Err(anyhow::anyhow!("Expected struct fields"))
451 }
452}
453