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 OccupancySensorType {
16 Pir = 0,
18 Ultrasonic = 1,
20 Pirandultrasonic = 2,
22 Physicalcontact = 3,
24}
25
26impl OccupancySensorType {
27 pub fn from_u8(value: u8) -> Option<Self> {
29 match value {
30 0 => Some(OccupancySensorType::Pir),
31 1 => Some(OccupancySensorType::Ultrasonic),
32 2 => Some(OccupancySensorType::Pirandultrasonic),
33 3 => Some(OccupancySensorType::Physicalcontact),
34 _ => None,
35 }
36 }
37
38 pub fn to_u8(self) -> u8 {
40 self as u8
41 }
42}
43
44impl From<OccupancySensorType> for u8 {
45 fn from(val: OccupancySensorType) -> Self {
46 val as u8
47 }
48}
49
50pub type Occupancy = u8;
54
55pub mod occupancy {
57 pub const OCCUPIED: u8 = 0x01;
59}
60
61pub type OccupancySensorTypeBitmap = u8;
63
64pub mod occupancysensortype {
66 pub const PIR: u8 = 0x01;
68 pub const ULTRASONIC: u8 = 0x02;
70 pub const PHYSICAL_CONTACT: u8 = 0x04;
72}
73
74#[derive(Debug, serde::Serialize)]
77pub struct HoldTimeLimits {
78 pub hold_time_min: Option<u16>,
79 pub hold_time_max: Option<u16>,
80 pub hold_time_default: Option<u16>,
81}
82
83pub fn decode_occupancy(inp: &tlv::TlvItemValue) -> anyhow::Result<Occupancy> {
87 if let tlv::TlvItemValue::Int(v) = inp {
88 Ok(*v as u8)
89 } else {
90 Err(anyhow::anyhow!("Expected Integer"))
91 }
92}
93
94pub fn decode_occupancy_sensor_type(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancySensorType> {
96 if let tlv::TlvItemValue::Int(v) = inp {
97 OccupancySensorType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
98 } else {
99 Err(anyhow::anyhow!("Expected Integer"))
100 }
101}
102
103pub fn decode_occupancy_sensor_type_bitmap(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancySensorTypeBitmap> {
105 if let tlv::TlvItemValue::Int(v) = inp {
106 Ok(*v as u8)
107 } else {
108 Err(anyhow::anyhow!("Expected Integer"))
109 }
110}
111
112pub fn decode_hold_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
114 if let tlv::TlvItemValue::Int(v) = inp {
115 Ok(*v as u16)
116 } else {
117 Err(anyhow::anyhow!("Expected UInt16"))
118 }
119}
120
121pub fn decode_hold_time_limits(inp: &tlv::TlvItemValue) -> anyhow::Result<HoldTimeLimits> {
123 if let tlv::TlvItemValue::List(_fields) = inp {
124 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
126 Ok(HoldTimeLimits {
127 hold_time_min: item.get_int(&[0]).map(|v| v as u16),
128 hold_time_max: item.get_int(&[1]).map(|v| v as u16),
129 hold_time_default: item.get_int(&[2]).map(|v| v as u16),
130 })
131 } else {
132 Err(anyhow::anyhow!("Expected struct fields"))
133 }
134}
135
136pub fn decode_pir_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
138 if let tlv::TlvItemValue::Int(v) = inp {
139 Ok(*v as u16)
140 } else {
141 Err(anyhow::anyhow!("Expected UInt16"))
142 }
143}
144
145pub fn decode_pir_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
147 if let tlv::TlvItemValue::Int(v) = inp {
148 Ok(*v as u16)
149 } else {
150 Err(anyhow::anyhow!("Expected UInt16"))
151 }
152}
153
154pub fn decode_pir_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
156 if let tlv::TlvItemValue::Int(v) = inp {
157 Ok(*v as u8)
158 } else {
159 Err(anyhow::anyhow!("Expected UInt8"))
160 }
161}
162
163pub fn decode_ultrasonic_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
165 if let tlv::TlvItemValue::Int(v) = inp {
166 Ok(*v as u16)
167 } else {
168 Err(anyhow::anyhow!("Expected UInt16"))
169 }
170}
171
172pub fn decode_ultrasonic_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
174 if let tlv::TlvItemValue::Int(v) = inp {
175 Ok(*v as u16)
176 } else {
177 Err(anyhow::anyhow!("Expected UInt16"))
178 }
179}
180
181pub fn decode_ultrasonic_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
183 if let tlv::TlvItemValue::Int(v) = inp {
184 Ok(*v as u8)
185 } else {
186 Err(anyhow::anyhow!("Expected UInt8"))
187 }
188}
189
190pub fn decode_physical_contact_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
192 if let tlv::TlvItemValue::Int(v) = inp {
193 Ok(*v as u16)
194 } else {
195 Err(anyhow::anyhow!("Expected UInt16"))
196 }
197}
198
199pub fn decode_physical_contact_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
201 if let tlv::TlvItemValue::Int(v) = inp {
202 Ok(*v as u16)
203 } else {
204 Err(anyhow::anyhow!("Expected UInt16"))
205 }
206}
207
208pub fn decode_physical_contact_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
210 if let tlv::TlvItemValue::Int(v) = inp {
211 Ok(*v as u8)
212 } else {
213 Err(anyhow::anyhow!("Expected UInt8"))
214 }
215}
216
217
218pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
230 if cluster_id != 0x0406 {
232 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0406, got {}\"}}", cluster_id);
233 }
234
235 match attribute_id {
236 0x0000 => {
237 match decode_occupancy(tlv_value) {
238 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
239 Err(e) => format!("{{\"error\": \"{}\"}}", e),
240 }
241 }
242 0x0001 => {
243 match decode_occupancy_sensor_type(tlv_value) {
244 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
245 Err(e) => format!("{{\"error\": \"{}\"}}", e),
246 }
247 }
248 0x0002 => {
249 match decode_occupancy_sensor_type_bitmap(tlv_value) {
250 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
251 Err(e) => format!("{{\"error\": \"{}\"}}", e),
252 }
253 }
254 0x0003 => {
255 match decode_hold_time(tlv_value) {
256 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
257 Err(e) => format!("{{\"error\": \"{}\"}}", e),
258 }
259 }
260 0x0004 => {
261 match decode_hold_time_limits(tlv_value) {
262 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
263 Err(e) => format!("{{\"error\": \"{}\"}}", e),
264 }
265 }
266 0x0010 => {
267 match decode_pir_occupied_to_unoccupied_delay(tlv_value) {
268 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
269 Err(e) => format!("{{\"error\": \"{}\"}}", e),
270 }
271 }
272 0x0011 => {
273 match decode_pir_unoccupied_to_occupied_delay(tlv_value) {
274 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
275 Err(e) => format!("{{\"error\": \"{}\"}}", e),
276 }
277 }
278 0x0012 => {
279 match decode_pir_unoccupied_to_occupied_threshold(tlv_value) {
280 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
281 Err(e) => format!("{{\"error\": \"{}\"}}", e),
282 }
283 }
284 0x0020 => {
285 match decode_ultrasonic_occupied_to_unoccupied_delay(tlv_value) {
286 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
287 Err(e) => format!("{{\"error\": \"{}\"}}", e),
288 }
289 }
290 0x0021 => {
291 match decode_ultrasonic_unoccupied_to_occupied_delay(tlv_value) {
292 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
293 Err(e) => format!("{{\"error\": \"{}\"}}", e),
294 }
295 }
296 0x0022 => {
297 match decode_ultrasonic_unoccupied_to_occupied_threshold(tlv_value) {
298 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
299 Err(e) => format!("{{\"error\": \"{}\"}}", e),
300 }
301 }
302 0x0030 => {
303 match decode_physical_contact_occupied_to_unoccupied_delay(tlv_value) {
304 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
305 Err(e) => format!("{{\"error\": \"{}\"}}", e),
306 }
307 }
308 0x0031 => {
309 match decode_physical_contact_unoccupied_to_occupied_delay(tlv_value) {
310 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
311 Err(e) => format!("{{\"error\": \"{}\"}}", e),
312 }
313 }
314 0x0032 => {
315 match decode_physical_contact_unoccupied_to_occupied_threshold(tlv_value) {
316 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
317 Err(e) => format!("{{\"error\": \"{}\"}}", e),
318 }
319 }
320 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
321 }
322}
323
324pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
329 vec![
330 (0x0000, "Occupancy"),
331 (0x0001, "OccupancySensorType"),
332 (0x0002, "OccupancySensorTypeBitmap"),
333 (0x0003, "HoldTime"),
334 (0x0004, "HoldTimeLimits"),
335 (0x0010, "PIROccupiedToUnoccupiedDelay"),
336 (0x0011, "PIRUnoccupiedToOccupiedDelay"),
337 (0x0012, "PIRUnoccupiedToOccupiedThreshold"),
338 (0x0020, "UltrasonicOccupiedToUnoccupiedDelay"),
339 (0x0021, "UltrasonicUnoccupiedToOccupiedDelay"),
340 (0x0022, "UltrasonicUnoccupiedToOccupiedThreshold"),
341 (0x0030, "PhysicalContactOccupiedToUnoccupiedDelay"),
342 (0x0031, "PhysicalContactUnoccupiedToOccupiedDelay"),
343 (0x0032, "PhysicalContactUnoccupiedToOccupiedThreshold"),
344 ]
345}
346
347#[derive(Debug, serde::Serialize)]
348pub struct OccupancyChangedEvent {
349 pub occupancy: Option<Occupancy>,
350}
351
352pub fn decode_occupancy_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancyChangedEvent> {
356 if let tlv::TlvItemValue::List(_fields) = inp {
357 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
358 Ok(OccupancyChangedEvent {
359 occupancy: item.get_int(&[0]).map(|v| v as u8),
360 })
361 } else {
362 Err(anyhow::anyhow!("Expected struct fields"))
363 }
364}
365