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 StatusCode {
18 Invalidpincode = 2,
20 Invalidrating = 3,
22 Invalidchannel = 4,
24 Channelalreadyexist = 5,
26 Channelnotexist = 6,
28 Unidentifiableapplication = 7,
30 Applicationalreadyexist = 8,
32 Applicationnotexist = 9,
34 Timewindowalreadyexist = 10,
36 Timewindownotexist = 11,
38}
39
40impl StatusCode {
41 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 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
70pub type DayOfWeek = u8;
74
75pub mod dayofweek {
77 pub const SUNDAY: u8 = 0x01;
79 pub const MONDAY: u8 = 0x02;
81 pub const TUESDAY: u8 = 0x04;
83 pub const WEDNESDAY: u8 = 0x08;
85 pub const THURSDAY: u8 = 0x10;
87 pub const FRIDAY: u8 = 0x20;
89 pub const SATURDAY: u8 = 0x40;
91}
92
93#[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
130pub 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
144pub 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
156pub 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
167pub 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
178pub 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
189pub 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
207pub 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
218pub 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
234pub 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
250pub fn encode_set_block_content_time_window(time_window: TimeWindow) -> anyhow::Result<Vec<u8>> {
252 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
276pub 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
287pub 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
298pub 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
312pub 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
321pub 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
335pub 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
344pub 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
353pub 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
362pub 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
371pub 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
387pub 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
401pub 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
432pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
444 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
520pub 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
540pub 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
675pub 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
689pub 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
697pub 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
703pub 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
709pub 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
715pub 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
721pub 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
727pub 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
733pub 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
739pub 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
745pub 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
751pub 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
757pub 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
763pub 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
769pub 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
775pub 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
781pub 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
787pub 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
793pub 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
799pub 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
805pub 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
811pub 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
817pub 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
823pub 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
829pub 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
835pub 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
841pub 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
847pub 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