matc/clusters/codec/
content_control.rs

1//! Matter TLV encoders and decoders for Content Control Cluster
2//! Cluster ID: 0x050F
3//!
4//! This file is automatically generated from ContentControl.xml
5
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Enum definitions
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum StatusCode {
18    /// Provided PIN Code does not match the current PIN code.
19    Invalidpincode = 2,
20    /// Provided Rating is out of scope of the corresponding Rating list.
21    Invalidrating = 3,
22    /// Provided Channel(s) is invalid.
23    Invalidchannel = 4,
24    /// Provided Channel(s) already exists.
25    Channelalreadyexist = 5,
26    /// Provided Channel(s) doesn't exist in BlockChannelList attribute.
27    Channelnotexist = 6,
28    /// Provided Application(s) is not identified.
29    Unidentifiableapplication = 7,
30    /// Provided Application(s) already exists.
31    Applicationalreadyexist = 8,
32    /// Provided Application(s) doesn't exist in BlockApplicationList attribute.
33    Applicationnotexist = 9,
34    /// Provided time Window already exists in BlockContentTimeWindow attribute.
35    Timewindowalreadyexist = 10,
36    /// Provided time window doesn't exist in BlockContentTimeWindow attribute.
37    Timewindownotexist = 11,
38}
39
40impl StatusCode {
41    /// Convert from u8 value
42    pub fn from_u8(value: u8) -> Option<Self> {
43        match value {
44            2 => Some(StatusCode::Invalidpincode),
45            3 => Some(StatusCode::Invalidrating),
46            4 => Some(StatusCode::Invalidchannel),
47            5 => Some(StatusCode::Channelalreadyexist),
48            6 => Some(StatusCode::Channelnotexist),
49            7 => Some(StatusCode::Unidentifiableapplication),
50            8 => Some(StatusCode::Applicationalreadyexist),
51            9 => Some(StatusCode::Applicationnotexist),
52            10 => Some(StatusCode::Timewindowalreadyexist),
53            11 => Some(StatusCode::Timewindownotexist),
54            _ => None,
55        }
56    }
57
58    /// Convert to u8 value
59    pub fn to_u8(self) -> u8 {
60        self as u8
61    }
62}
63
64impl From<StatusCode> for u8 {
65    fn from(val: StatusCode) -> Self {
66        val as u8
67    }
68}
69
70// Bitmap definitions
71
72/// DayOfWeek bitmap type
73pub type DayOfWeek = u8;
74
75/// Constants for DayOfWeek
76pub mod dayofweek {
77    /// Sunday
78    pub const SUNDAY: u8 = 0x01;
79    /// Monday
80    pub const MONDAY: u8 = 0x02;
81    /// Tuesday
82    pub const TUESDAY: u8 = 0x04;
83    /// Wednesday
84    pub const WEDNESDAY: u8 = 0x08;
85    /// Thursday
86    pub const THURSDAY: u8 = 0x10;
87    /// Friday
88    pub const FRIDAY: u8 = 0x20;
89    /// Saturday
90    pub const SATURDAY: u8 = 0x40;
91}
92
93// Struct definitions
94
95#[derive(Debug, serde::Serialize)]
96pub struct AppInfo {
97    pub catalog_vendor_id: Option<u16>,
98    pub application_id: Option<String>,
99}
100
101#[derive(Debug, serde::Serialize)]
102pub struct BlockChannel {
103    pub block_channel_index: Option<u16>,
104    pub major_number: Option<u16>,
105    pub minor_number: Option<u16>,
106    pub identifier: Option<String>,
107}
108
109#[derive(Debug, serde::Serialize)]
110pub struct RatingName {
111    pub rating_name: Option<String>,
112    pub rating_name_desc: Option<String>,
113}
114
115#[derive(Debug, serde::Serialize)]
116pub struct TimePeriod {
117    pub start_hour: Option<u8>,
118    pub start_minute: Option<u8>,
119    pub end_hour: Option<u8>,
120    pub end_minute: Option<u8>,
121}
122
123#[derive(Debug, serde::Serialize)]
124pub struct TimeWindow {
125    pub time_window_index: Option<u16>,
126    pub day_of_week: Option<DayOfWeek>,
127    pub time_period: Option<Vec<TimePeriod>>,
128}
129
130// Command encoders
131
132/// Encode UpdatePIN command (0x00)
133pub fn encode_update_pin(old_pin: String, new_pin: String) -> anyhow::Result<Vec<u8>> {
134    let tlv = tlv::TlvItemEnc {
135        tag: 0,
136        value: tlv::TlvItemValueEnc::StructInvisible(vec![
137        (0, tlv::TlvItemValueEnc::String(old_pin)).into(),
138        (1, tlv::TlvItemValueEnc::String(new_pin)).into(),
139        ]),
140    };
141    Ok(tlv.encode()?)
142}
143
144/// Encode AddBonusTime command (0x05)
145pub fn encode_add_bonus_time(pin_code: String, bonus_time: u32) -> anyhow::Result<Vec<u8>> {
146    let tlv = tlv::TlvItemEnc {
147        tag: 0,
148        value: tlv::TlvItemValueEnc::StructInvisible(vec![
149        (0, tlv::TlvItemValueEnc::String(pin_code)).into(),
150        (1, tlv::TlvItemValueEnc::UInt32(bonus_time)).into(),
151        ]),
152    };
153    Ok(tlv.encode()?)
154}
155
156/// Encode SetScreenDailyTime command (0x06)
157pub fn encode_set_screen_daily_time(screen_time: u32) -> anyhow::Result<Vec<u8>> {
158    let tlv = tlv::TlvItemEnc {
159        tag: 0,
160        value: tlv::TlvItemValueEnc::StructInvisible(vec![
161        (0, tlv::TlvItemValueEnc::UInt32(screen_time)).into(),
162        ]),
163    };
164    Ok(tlv.encode()?)
165}
166
167/// Encode SetOnDemandRatingThreshold command (0x09)
168pub fn encode_set_on_demand_rating_threshold(rating: String) -> anyhow::Result<Vec<u8>> {
169    let tlv = tlv::TlvItemEnc {
170        tag: 0,
171        value: tlv::TlvItemValueEnc::StructInvisible(vec![
172        (0, tlv::TlvItemValueEnc::String(rating)).into(),
173        ]),
174    };
175    Ok(tlv.encode()?)
176}
177
178/// Encode SetScheduledContentRatingThreshold command (0x0A)
179pub fn encode_set_scheduled_content_rating_threshold(rating: String) -> anyhow::Result<Vec<u8>> {
180    let tlv = tlv::TlvItemEnc {
181        tag: 0,
182        value: tlv::TlvItemValueEnc::StructInvisible(vec![
183        (0, tlv::TlvItemValueEnc::String(rating)).into(),
184        ]),
185    };
186    Ok(tlv.encode()?)
187}
188
189/// Encode AddBlockChannels command (0x0B)
190pub fn encode_add_block_channels(channels: Vec<BlockChannel>) -> anyhow::Result<Vec<u8>> {
191    let tlv = tlv::TlvItemEnc {
192        tag: 0,
193        value: tlv::TlvItemValueEnc::StructInvisible(vec![
194        (0, tlv::TlvItemValueEnc::Array(channels.into_iter().map(|v| {
195                    let mut fields = Vec::new();
196                    if let Some(x) = v.block_channel_index { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
197                    if let Some(x) = v.major_number { fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
198                    if let Some(x) = v.minor_number { fields.push((2, tlv::TlvItemValueEnc::UInt16(x)).into()); }
199                    if let Some(x) = v.identifier { fields.push((3, tlv::TlvItemValueEnc::String(x.clone())).into()); }
200                    (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
201                }).collect())).into(),
202        ]),
203    };
204    Ok(tlv.encode()?)
205}
206
207/// Encode RemoveBlockChannels command (0x0C)
208pub fn encode_remove_block_channels(channel_indexes: Vec<u16>) -> anyhow::Result<Vec<u8>> {
209    let tlv = tlv::TlvItemEnc {
210        tag: 0,
211        value: tlv::TlvItemValueEnc::StructInvisible(vec![
212        (0, tlv::TlvItemValueEnc::StructAnon(channel_indexes.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt16(v)).into()).collect())).into(),
213        ]),
214    };
215    Ok(tlv.encode()?)
216}
217
218/// Encode AddBlockApplications command (0x0D)
219pub fn encode_add_block_applications(applications: Vec<AppInfo>) -> anyhow::Result<Vec<u8>> {
220    let tlv = tlv::TlvItemEnc {
221        tag: 0,
222        value: tlv::TlvItemValueEnc::StructInvisible(vec![
223        (0, tlv::TlvItemValueEnc::Array(applications.into_iter().map(|v| {
224                    let mut fields = Vec::new();
225                    if let Some(x) = v.catalog_vendor_id { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
226                    if let Some(x) = v.application_id { fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
227                    (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
228                }).collect())).into(),
229        ]),
230    };
231    Ok(tlv.encode()?)
232}
233
234/// Encode RemoveBlockApplications command (0x0E)
235pub fn encode_remove_block_applications(applications: Vec<AppInfo>) -> anyhow::Result<Vec<u8>> {
236    let tlv = tlv::TlvItemEnc {
237        tag: 0,
238        value: tlv::TlvItemValueEnc::StructInvisible(vec![
239        (0, tlv::TlvItemValueEnc::Array(applications.into_iter().map(|v| {
240                    let mut fields = Vec::new();
241                    if let Some(x) = v.catalog_vendor_id { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
242                    if let Some(x) = v.application_id { fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
243                    (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
244                }).collect())).into(),
245        ]),
246    };
247    Ok(tlv.encode()?)
248}
249
250/// Encode SetBlockContentTimeWindow command (0x0F)
251pub fn encode_set_block_content_time_window(time_window: TimeWindow) -> anyhow::Result<Vec<u8>> {
252            // Encode struct TimeWindowStruct
253            let mut time_window_fields = Vec::new();
254            if let Some(x) = time_window.time_window_index { time_window_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
255            if let Some(x) = time_window.day_of_week { time_window_fields.push((1, tlv::TlvItemValueEnc::UInt8(x)).into()); }
256            if let Some(listv) = time_window.time_period {
257                let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
258                    let mut nested_fields = Vec::new();
259                        if let Some(x) = inner.start_hour { nested_fields.push((0, tlv::TlvItemValueEnc::UInt8(x)).into()); }
260                        if let Some(x) = inner.start_minute { nested_fields.push((1, tlv::TlvItemValueEnc::UInt8(x)).into()); }
261                        if let Some(x) = inner.end_hour { nested_fields.push((2, tlv::TlvItemValueEnc::UInt8(x)).into()); }
262                        if let Some(x) = inner.end_minute { nested_fields.push((3, tlv::TlvItemValueEnc::UInt8(x)).into()); }
263                    (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
264                }).collect();
265                time_window_fields.push((2, tlv::TlvItemValueEnc::Array(inner_vec)).into());
266            }
267    let tlv = tlv::TlvItemEnc {
268        tag: 0,
269        value: tlv::TlvItemValueEnc::StructInvisible(vec![
270        (0, tlv::TlvItemValueEnc::StructInvisible(time_window_fields)).into(),
271        ]),
272    };
273    Ok(tlv.encode()?)
274}
275
276/// Encode RemoveBlockContentTimeWindow command (0x10)
277pub fn encode_remove_block_content_time_window(time_window_indexes: Vec<u16>) -> anyhow::Result<Vec<u8>> {
278    let tlv = tlv::TlvItemEnc {
279        tag: 0,
280        value: tlv::TlvItemValueEnc::StructInvisible(vec![
281        (0, tlv::TlvItemValueEnc::StructAnon(time_window_indexes.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt16(v)).into()).collect())).into(),
282        ]),
283    };
284    Ok(tlv.encode()?)
285}
286
287// Attribute decoders
288
289/// Decode Enabled attribute (0x0000)
290pub fn decode_enabled(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
291    if let tlv::TlvItemValue::Bool(v) = inp {
292        Ok(*v)
293    } else {
294        Err(anyhow::anyhow!("Expected Bool"))
295    }
296}
297
298/// Decode OnDemandRatings attribute (0x0001)
299pub fn decode_on_demand_ratings(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<RatingName>> {
300    let mut res = Vec::new();
301    if let tlv::TlvItemValue::List(v) = inp {
302        for item in v {
303            res.push(RatingName {
304                rating_name: item.get_string_owned(&[0]),
305                rating_name_desc: item.get_string_owned(&[1]),
306            });
307        }
308    }
309    Ok(res)
310}
311
312/// Decode OnDemandRatingThreshold attribute (0x0002)
313pub fn decode_on_demand_rating_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<String> {
314    if let tlv::TlvItemValue::String(v) = inp {
315        Ok(v.clone())
316    } else {
317        Err(anyhow::anyhow!("Expected String"))
318    }
319}
320
321/// Decode ScheduledContentRatings attribute (0x0003)
322pub fn decode_scheduled_content_ratings(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<RatingName>> {
323    let mut res = Vec::new();
324    if let tlv::TlvItemValue::List(v) = inp {
325        for item in v {
326            res.push(RatingName {
327                rating_name: item.get_string_owned(&[0]),
328                rating_name_desc: item.get_string_owned(&[1]),
329            });
330        }
331    }
332    Ok(res)
333}
334
335/// Decode ScheduledContentRatingThreshold attribute (0x0004)
336pub fn decode_scheduled_content_rating_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<String> {
337    if let tlv::TlvItemValue::String(v) = inp {
338        Ok(v.clone())
339    } else {
340        Err(anyhow::anyhow!("Expected String"))
341    }
342}
343
344/// Decode ScreenDailyTime attribute (0x0005)
345pub fn decode_screen_daily_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
346    if let tlv::TlvItemValue::Int(v) = inp {
347        Ok(*v as u32)
348    } else {
349        Err(anyhow::anyhow!("Expected UInt32"))
350    }
351}
352
353/// Decode RemainingScreenTime attribute (0x0006)
354pub fn decode_remaining_screen_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
355    if let tlv::TlvItemValue::Int(v) = inp {
356        Ok(*v as u32)
357    } else {
358        Err(anyhow::anyhow!("Expected UInt32"))
359    }
360}
361
362/// Decode BlockUnrated attribute (0x0007)
363pub fn decode_block_unrated(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
364    if let tlv::TlvItemValue::Bool(v) = inp {
365        Ok(*v)
366    } else {
367        Err(anyhow::anyhow!("Expected Bool"))
368    }
369}
370
371/// Decode BlockChannelList attribute (0x0008)
372pub fn decode_block_channel_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<BlockChannel>> {
373    let mut res = Vec::new();
374    if let tlv::TlvItemValue::List(v) = inp {
375        for item in v {
376            res.push(BlockChannel {
377                block_channel_index: item.get_int(&[0]).map(|v| v as u16),
378                major_number: item.get_int(&[1]).map(|v| v as u16),
379                minor_number: item.get_int(&[2]).map(|v| v as u16),
380                identifier: item.get_string_owned(&[3]),
381            });
382        }
383    }
384    Ok(res)
385}
386
387/// Decode BlockApplicationList attribute (0x0009)
388pub fn decode_block_application_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<AppInfo>> {
389    let mut res = Vec::new();
390    if let tlv::TlvItemValue::List(v) = inp {
391        for item in v {
392            res.push(AppInfo {
393                catalog_vendor_id: item.get_int(&[0]).map(|v| v as u16),
394                application_id: item.get_string_owned(&[1]),
395            });
396        }
397    }
398    Ok(res)
399}
400
401/// Decode BlockContentTimeWindow attribute (0x000A)
402pub fn decode_block_content_time_window(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<TimeWindow>> {
403    let mut res = Vec::new();
404    if let tlv::TlvItemValue::List(v) = inp {
405        for item in v {
406            res.push(TimeWindow {
407                time_window_index: item.get_int(&[0]).map(|v| v as u16),
408                day_of_week: item.get_int(&[1]).map(|v| v as u8),
409                time_period: {
410                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
411                        let mut items = Vec::new();
412                        for list_item in l {
413                            items.push(TimePeriod {
414                start_hour: list_item.get_int(&[0]).map(|v| v as u8),
415                start_minute: list_item.get_int(&[1]).map(|v| v as u8),
416                end_hour: list_item.get_int(&[2]).map(|v| v as u8),
417                end_minute: list_item.get_int(&[3]).map(|v| v as u8),
418                            });
419                        }
420                        Some(items)
421                    } else {
422                        None
423                    }
424                },
425            });
426        }
427    }
428    Ok(res)
429}
430
431
432// JSON dispatcher function
433
434/// Decode attribute value and return as JSON string
435///
436/// # Parameters
437/// * `cluster_id` - The cluster identifier
438/// * `attribute_id` - The attribute identifier
439/// * `tlv_value` - The TLV value to decode
440///
441/// # Returns
442/// JSON string representation of the decoded value or error
443pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
444    // Verify this is the correct cluster
445    if cluster_id != 0x050F {
446        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x050F, got {}\"}}", cluster_id);
447    }
448
449    match attribute_id {
450        0x0000 => {
451            match decode_enabled(tlv_value) {
452                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
453                Err(e) => format!("{{\"error\": \"{}\"}}", e),
454            }
455        }
456        0x0001 => {
457            match decode_on_demand_ratings(tlv_value) {
458                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
459                Err(e) => format!("{{\"error\": \"{}\"}}", e),
460            }
461        }
462        0x0002 => {
463            match decode_on_demand_rating_threshold(tlv_value) {
464                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
465                Err(e) => format!("{{\"error\": \"{}\"}}", e),
466            }
467        }
468        0x0003 => {
469            match decode_scheduled_content_ratings(tlv_value) {
470                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
471                Err(e) => format!("{{\"error\": \"{}\"}}", e),
472            }
473        }
474        0x0004 => {
475            match decode_scheduled_content_rating_threshold(tlv_value) {
476                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
477                Err(e) => format!("{{\"error\": \"{}\"}}", e),
478            }
479        }
480        0x0005 => {
481            match decode_screen_daily_time(tlv_value) {
482                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
483                Err(e) => format!("{{\"error\": \"{}\"}}", e),
484            }
485        }
486        0x0006 => {
487            match decode_remaining_screen_time(tlv_value) {
488                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
489                Err(e) => format!("{{\"error\": \"{}\"}}", e),
490            }
491        }
492        0x0007 => {
493            match decode_block_unrated(tlv_value) {
494                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
495                Err(e) => format!("{{\"error\": \"{}\"}}", e),
496            }
497        }
498        0x0008 => {
499            match decode_block_channel_list(tlv_value) {
500                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
501                Err(e) => format!("{{\"error\": \"{}\"}}", e),
502            }
503        }
504        0x0009 => {
505            match decode_block_application_list(tlv_value) {
506                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
507                Err(e) => format!("{{\"error\": \"{}\"}}", e),
508            }
509        }
510        0x000A => {
511            match decode_block_content_time_window(tlv_value) {
512                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
513                Err(e) => format!("{{\"error\": \"{}\"}}", e),
514            }
515        }
516        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
517    }
518}
519
520/// Get list of all attributes supported by this cluster
521///
522/// # Returns
523/// Vector of tuples containing (attribute_id, attribute_name)
524pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
525    vec![
526        (0x0000, "Enabled"),
527        (0x0001, "OnDemandRatings"),
528        (0x0002, "OnDemandRatingThreshold"),
529        (0x0003, "ScheduledContentRatings"),
530        (0x0004, "ScheduledContentRatingThreshold"),
531        (0x0005, "ScreenDailyTime"),
532        (0x0006, "RemainingScreenTime"),
533        (0x0007, "BlockUnrated"),
534        (0x0008, "BlockChannelList"),
535        (0x0009, "BlockApplicationList"),
536        (0x000A, "BlockContentTimeWindow"),
537    ]
538}
539
540// Command listing
541
542pub fn get_command_list() -> Vec<(u32, &'static str)> {
543    vec![
544        (0x00, "UpdatePIN"),
545        (0x01, "ResetPIN"),
546        (0x03, "Enable"),
547        (0x04, "Disable"),
548        (0x05, "AddBonusTime"),
549        (0x06, "SetScreenDailyTime"),
550        (0x07, "BlockUnratedContent"),
551        (0x08, "UnblockUnratedContent"),
552        (0x09, "SetOnDemandRatingThreshold"),
553        (0x0A, "SetScheduledContentRatingThreshold"),
554        (0x0B, "AddBlockChannels"),
555        (0x0C, "RemoveBlockChannels"),
556        (0x0D, "AddBlockApplications"),
557        (0x0E, "RemoveBlockApplications"),
558        (0x0F, "SetBlockContentTimeWindow"),
559        (0x10, "RemoveBlockContentTimeWindow"),
560    ]
561}
562
563pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
564    match cmd_id {
565        0x00 => Some("UpdatePIN"),
566        0x01 => Some("ResetPIN"),
567        0x03 => Some("Enable"),
568        0x04 => Some("Disable"),
569        0x05 => Some("AddBonusTime"),
570        0x06 => Some("SetScreenDailyTime"),
571        0x07 => Some("BlockUnratedContent"),
572        0x08 => Some("UnblockUnratedContent"),
573        0x09 => Some("SetOnDemandRatingThreshold"),
574        0x0A => Some("SetScheduledContentRatingThreshold"),
575        0x0B => Some("AddBlockChannels"),
576        0x0C => Some("RemoveBlockChannels"),
577        0x0D => Some("AddBlockApplications"),
578        0x0E => Some("RemoveBlockApplications"),
579        0x0F => Some("SetBlockContentTimeWindow"),
580        0x10 => Some("RemoveBlockContentTimeWindow"),
581        _ => None,
582    }
583}
584
585pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
586    match cmd_id {
587        0x00 => Some(vec![
588            crate::clusters::codec::CommandField { tag: 0, name: "old_pin", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
589            crate::clusters::codec::CommandField { tag: 1, name: "new_pin", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
590        ]),
591        0x01 => Some(vec![]),
592        0x03 => Some(vec![]),
593        0x04 => Some(vec![]),
594        0x05 => Some(vec![
595            crate::clusters::codec::CommandField { tag: 0, name: "pin_code", kind: crate::clusters::codec::FieldKind::String, optional: true, nullable: false },
596            crate::clusters::codec::CommandField { tag: 1, name: "bonus_time", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
597        ]),
598        0x06 => Some(vec![
599            crate::clusters::codec::CommandField { tag: 0, name: "screen_time", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
600        ]),
601        0x07 => Some(vec![]),
602        0x08 => Some(vec![]),
603        0x09 => Some(vec![
604            crate::clusters::codec::CommandField { tag: 0, name: "rating", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
605        ]),
606        0x0A => Some(vec![
607            crate::clusters::codec::CommandField { tag: 0, name: "rating", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
608        ]),
609        0x0B => Some(vec![
610            crate::clusters::codec::CommandField { tag: 0, name: "channels", kind: crate::clusters::codec::FieldKind::List { entry_type: "BlockChannelStruct" }, optional: false, nullable: false },
611        ]),
612        0x0C => Some(vec![
613            crate::clusters::codec::CommandField { tag: 0, name: "channel_indexes", kind: crate::clusters::codec::FieldKind::List { entry_type: "uint16" }, optional: false, nullable: false },
614        ]),
615        0x0D => Some(vec![
616            crate::clusters::codec::CommandField { tag: 0, name: "applications", kind: crate::clusters::codec::FieldKind::List { entry_type: "AppInfoStruct" }, optional: false, nullable: false },
617        ]),
618        0x0E => Some(vec![
619            crate::clusters::codec::CommandField { tag: 0, name: "applications", kind: crate::clusters::codec::FieldKind::List { entry_type: "AppInfoStruct" }, optional: false, nullable: false },
620        ]),
621        0x0F => Some(vec![
622            crate::clusters::codec::CommandField { tag: 0, name: "time_window", kind: crate::clusters::codec::FieldKind::Struct { name: "TimeWindowStruct" }, optional: false, nullable: false },
623        ]),
624        0x10 => Some(vec![
625            crate::clusters::codec::CommandField { tag: 0, name: "time_window_indexes", kind: crate::clusters::codec::FieldKind::List { entry_type: "uint16" }, optional: false, nullable: false },
626        ]),
627        _ => None,
628    }
629}
630
631pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
632    match cmd_id {
633        0x00 => {
634        let old_pin = crate::clusters::codec::json_util::get_string(args, "old_pin")?;
635        let new_pin = crate::clusters::codec::json_util::get_string(args, "new_pin")?;
636        encode_update_pin(old_pin, new_pin)
637        }
638        0x01 => Ok(vec![]),
639        0x03 => Ok(vec![]),
640        0x04 => Ok(vec![]),
641        0x05 => {
642        let pin_code = crate::clusters::codec::json_util::get_string(args, "pin_code")?;
643        let bonus_time = crate::clusters::codec::json_util::get_u32(args, "bonus_time")?;
644        encode_add_bonus_time(pin_code, bonus_time)
645        }
646        0x06 => {
647        let screen_time = crate::clusters::codec::json_util::get_u32(args, "screen_time")?;
648        encode_set_screen_daily_time(screen_time)
649        }
650        0x07 => Ok(vec![]),
651        0x08 => Ok(vec![]),
652        0x09 => {
653        let rating = crate::clusters::codec::json_util::get_string(args, "rating")?;
654        encode_set_on_demand_rating_threshold(rating)
655        }
656        0x0A => {
657        let rating = crate::clusters::codec::json_util::get_string(args, "rating")?;
658        encode_set_scheduled_content_rating_threshold(rating)
659        }
660        0x0B => Err(anyhow::anyhow!("command \"AddBlockChannels\" has complex args: use raw mode")),
661        0x0C => Err(anyhow::anyhow!("command \"RemoveBlockChannels\" has complex args: use raw mode")),
662        0x0D => Err(anyhow::anyhow!("command \"AddBlockApplications\" has complex args: use raw mode")),
663        0x0E => Err(anyhow::anyhow!("command \"RemoveBlockApplications\" has complex args: use raw mode")),
664        0x0F => Err(anyhow::anyhow!("command \"SetBlockContentTimeWindow\" has complex args: use raw mode")),
665        0x10 => Err(anyhow::anyhow!("command \"RemoveBlockContentTimeWindow\" has complex args: use raw mode")),
666        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
667    }
668}
669
670#[derive(Debug, serde::Serialize)]
671pub struct ResetPINResponse {
672    pub pin_code: Option<String>,
673}
674
675// Command response decoders
676
677/// Decode ResetPINResponse command response (02)
678pub fn decode_reset_pin_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ResetPINResponse> {
679    if let tlv::TlvItemValue::List(_fields) = inp {
680        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
681        Ok(ResetPINResponse {
682                pin_code: item.get_string_owned(&[0]),
683        })
684    } else {
685        Err(anyhow::anyhow!("Expected struct fields"))
686    }
687}
688
689// Typed facade (invokes + reads)
690
691/// Invoke `UpdatePIN` command on cluster `Content Control`.
692pub async fn update_pin(conn: &crate::controller::Connection, endpoint: u16, old_pin: String, new_pin: String) -> anyhow::Result<()> {
693    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_UPDATEPIN, &encode_update_pin(old_pin, new_pin)?).await?;
694    Ok(())
695}
696
697/// Invoke `ResetPIN` command on cluster `Content Control`.
698pub async fn reset_pin(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ResetPINResponse> {
699    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_RESETPIN, &[]).await?;
700    decode_reset_pin_response(&tlv)
701}
702
703/// Invoke `Enable` command on cluster `Content Control`.
704pub async fn enable(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
705    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_ENABLE, &[]).await?;
706    Ok(())
707}
708
709/// Invoke `Disable` command on cluster `Content Control`.
710pub async fn disable(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
711    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_DISABLE, &[]).await?;
712    Ok(())
713}
714
715/// Invoke `AddBonusTime` command on cluster `Content Control`.
716pub async fn add_bonus_time(conn: &crate::controller::Connection, endpoint: u16, pin_code: String, bonus_time: u32) -> anyhow::Result<()> {
717    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_ADDBONUSTIME, &encode_add_bonus_time(pin_code, bonus_time)?).await?;
718    Ok(())
719}
720
721/// Invoke `SetScreenDailyTime` command on cluster `Content Control`.
722pub async fn set_screen_daily_time(conn: &crate::controller::Connection, endpoint: u16, screen_time: u32) -> anyhow::Result<()> {
723    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_SETSCREENDAILYTIME, &encode_set_screen_daily_time(screen_time)?).await?;
724    Ok(())
725}
726
727/// Invoke `BlockUnratedContent` command on cluster `Content Control`.
728pub async fn block_unrated_content(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
729    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_BLOCKUNRATEDCONTENT, &[]).await?;
730    Ok(())
731}
732
733/// Invoke `UnblockUnratedContent` command on cluster `Content Control`.
734pub async fn unblock_unrated_content(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
735    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_UNBLOCKUNRATEDCONTENT, &[]).await?;
736    Ok(())
737}
738
739/// Invoke `SetOnDemandRatingThreshold` command on cluster `Content Control`.
740pub async fn set_on_demand_rating_threshold(conn: &crate::controller::Connection, endpoint: u16, rating: String) -> anyhow::Result<()> {
741    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_SETONDEMANDRATINGTHRESHOLD, &encode_set_on_demand_rating_threshold(rating)?).await?;
742    Ok(())
743}
744
745/// Invoke `SetScheduledContentRatingThreshold` command on cluster `Content Control`.
746pub async fn set_scheduled_content_rating_threshold(conn: &crate::controller::Connection, endpoint: u16, rating: String) -> anyhow::Result<()> {
747    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_SETSCHEDULEDCONTENTRATINGTHRESHOLD, &encode_set_scheduled_content_rating_threshold(rating)?).await?;
748    Ok(())
749}
750
751/// Invoke `AddBlockChannels` command on cluster `Content Control`.
752pub async fn add_block_channels(conn: &crate::controller::Connection, endpoint: u16, channels: Vec<BlockChannel>) -> anyhow::Result<()> {
753    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_ADDBLOCKCHANNELS, &encode_add_block_channels(channels)?).await?;
754    Ok(())
755}
756
757/// Invoke `RemoveBlockChannels` command on cluster `Content Control`.
758pub async fn remove_block_channels(conn: &crate::controller::Connection, endpoint: u16, channel_indexes: Vec<u16>) -> anyhow::Result<()> {
759    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_REMOVEBLOCKCHANNELS, &encode_remove_block_channels(channel_indexes)?).await?;
760    Ok(())
761}
762
763/// Invoke `AddBlockApplications` command on cluster `Content Control`.
764pub async fn add_block_applications(conn: &crate::controller::Connection, endpoint: u16, applications: Vec<AppInfo>) -> anyhow::Result<()> {
765    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_ADDBLOCKAPPLICATIONS, &encode_add_block_applications(applications)?).await?;
766    Ok(())
767}
768
769/// Invoke `RemoveBlockApplications` command on cluster `Content Control`.
770pub async fn remove_block_applications(conn: &crate::controller::Connection, endpoint: u16, applications: Vec<AppInfo>) -> anyhow::Result<()> {
771    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_REMOVEBLOCKAPPLICATIONS, &encode_remove_block_applications(applications)?).await?;
772    Ok(())
773}
774
775/// Invoke `SetBlockContentTimeWindow` command on cluster `Content Control`.
776pub async fn set_block_content_time_window(conn: &crate::controller::Connection, endpoint: u16, time_window: TimeWindow) -> anyhow::Result<()> {
777    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_SETBLOCKCONTENTTIMEWINDOW, &encode_set_block_content_time_window(time_window)?).await?;
778    Ok(())
779}
780
781/// Invoke `RemoveBlockContentTimeWindow` command on cluster `Content Control`.
782pub async fn remove_block_content_time_window(conn: &crate::controller::Connection, endpoint: u16, time_window_indexes: Vec<u16>) -> anyhow::Result<()> {
783    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_CMD_ID_REMOVEBLOCKCONTENTTIMEWINDOW, &encode_remove_block_content_time_window(time_window_indexes)?).await?;
784    Ok(())
785}
786
787/// Read `Enabled` attribute from cluster `Content Control`.
788pub async fn read_enabled(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
789    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_ATTR_ID_ENABLED).await?;
790    decode_enabled(&tlv)
791}
792
793/// Read `OnDemandRatings` attribute from cluster `Content Control`.
794pub async fn read_on_demand_ratings(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<RatingName>> {
795    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_ATTR_ID_ONDEMANDRATINGS).await?;
796    decode_on_demand_ratings(&tlv)
797}
798
799/// Read `OnDemandRatingThreshold` attribute from cluster `Content Control`.
800pub async fn read_on_demand_rating_threshold(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<String> {
801    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_ATTR_ID_ONDEMANDRATINGTHRESHOLD).await?;
802    decode_on_demand_rating_threshold(&tlv)
803}
804
805/// Read `ScheduledContentRatings` attribute from cluster `Content Control`.
806pub async fn read_scheduled_content_ratings(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<RatingName>> {
807    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_ATTR_ID_SCHEDULEDCONTENTRATINGS).await?;
808    decode_scheduled_content_ratings(&tlv)
809}
810
811/// Read `ScheduledContentRatingThreshold` attribute from cluster `Content Control`.
812pub async fn read_scheduled_content_rating_threshold(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<String> {
813    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_ATTR_ID_SCHEDULEDCONTENTRATINGTHRESHOLD).await?;
814    decode_scheduled_content_rating_threshold(&tlv)
815}
816
817/// Read `ScreenDailyTime` attribute from cluster `Content Control`.
818pub async fn read_screen_daily_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u32> {
819    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_ATTR_ID_SCREENDAILYTIME).await?;
820    decode_screen_daily_time(&tlv)
821}
822
823/// Read `RemainingScreenTime` attribute from cluster `Content Control`.
824pub async fn read_remaining_screen_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u32> {
825    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_ATTR_ID_REMAININGSCREENTIME).await?;
826    decode_remaining_screen_time(&tlv)
827}
828
829/// Read `BlockUnrated` attribute from cluster `Content Control`.
830pub async fn read_block_unrated(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
831    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_ATTR_ID_BLOCKUNRATED).await?;
832    decode_block_unrated(&tlv)
833}
834
835/// Read `BlockChannelList` attribute from cluster `Content Control`.
836pub async fn read_block_channel_list(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<BlockChannel>> {
837    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_ATTR_ID_BLOCKCHANNELLIST).await?;
838    decode_block_channel_list(&tlv)
839}
840
841/// Read `BlockApplicationList` attribute from cluster `Content Control`.
842pub async fn read_block_application_list(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<AppInfo>> {
843    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_ATTR_ID_BLOCKAPPLICATIONLIST).await?;
844    decode_block_application_list(&tlv)
845}
846
847/// Read `BlockContentTimeWindow` attribute from cluster `Content Control`.
848pub async fn read_block_content_time_window(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<TimeWindow>> {
849    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CONTENT_CONTROL, crate::clusters::defs::CLUSTER_CONTENT_CONTROL_ATTR_ID_BLOCKCONTENTTIMEWINDOW).await?;
850    decode_block_content_time_window(&tlv)
851}
852