1use core::str::FromStr;
21
22use crate::dm::{Cluster, Dataver, InvokeContext, ReadContext, WriteContext};
23use crate::error::{Error, ErrorCode};
24use crate::fabric::MAX_FABRICS;
25use crate::persist::{KvBlobStore, Persist, BASIC_INFO_KEY};
26use crate::tlv::{FromTLV, Nullable, TLVBuilderParent, TLVElement, ToTLV, Utf8StrBuilder};
27use crate::transport::exchange::Exchange;
28use crate::transport::session::MAX_SESSIONS;
29use crate::utils::bitflags::bitflags;
30use crate::utils::init::{init, Init};
31use crate::{except, with};
32
33pub use crate::dm::clusters::decl::basic_information::*;
34pub use crate::dm::clusters::decl::general_commissioning::RegulatoryLocationTypeEnum;
35
36pub const DEFAULT_MATTER_SPEC_VERSION: u32 = 0x01050100;
40
41pub const DEFAULT_DATA_MODEL_REVISION: u16 = 19;
45
46pub const DEFAULT_MAX_PATHS_PER_INVOKE: u16 = 5;
51
52bitflags! {
53 #[repr(transparent)]
54 #[derive(Default)]
55 #[cfg_attr(not(feature = "defmt"), derive(Debug, Copy, Clone, Eq, PartialEq, Hash))]
56 pub struct PairingHintFlags: u32 {
57 const POWER_CYCLE = 0x0000_0001;
62 const DEV_MANUFACTURER_URL = 0x0000_0002;
69 const ADMINISTRATOR = 0x0000_0004;
73 const SETTINGS_MENU = 0x0000_0008;
76 const CUSTOM_INSTRUCTION = 0x0000_0010;
80 const DEVICE_MANUAL = 0x0000_0020;
85 const PRESS_RESET_BUTTON = 0x0000_0040;
87 const PRESS_RESET_BUTTON_WITH_POWER = 0x0000_0080;
89 const PRESS_RESET_BUTTON_FOR_N_SECONDS = 0x0000_0100;
92 const PRESS_RESET_BUTTON_UNTIL_LIGHT_BLINKS = 0x0000_0200;
95 const PRESS_RESET_BUTTON_FOR_N_SECONDS_WITH_POWER = 0x0000_0400;
98 const PRESS_RESET_BUTTON_UNTIL_LIGHT_BLINKS_WITH_POWER = 0x0000_0800;
102 const PRESS_RESET_BUTTON_N_TIMES = 0x0000_1000;
105 const PRESS_SETUP_BUTTON = 0x0000_2000;
107 const PRESS_SETUP_BUTTON_WITH_POWER = 0x0000_4000;
109 const PRESS_SETUP_BUTTON_FOR_N_SECONDS = 0x0000_8000;
112 const PRESS_SETUP_BUTTON_UNTIL_LIGHT_BLINKS = 0x0001_0000;
115 const PRESS_SETUP_BUTTON_FOR_N_SECONDS_WITH_POWER = 0x0002_0000;
118 const PRESS_SETUP_BUTTON_UNTIL_LIGHT_BLINKS_WITH_POWER = 0x0004_0000;
122 const PRESS_SETUP_BUTTON_N_TIMES = 0x0008_0000;
125 }
126}
127
128#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
136#[cfg_attr(feature = "defmt", derive(defmt::Format))]
137pub struct BasicInfoConfig<'a> {
138 pub vendor_name: &'a str,
140 pub vid: u16,
142 pub product_name: &'a str,
144 pub pid: u16,
146 pub hw_ver: u16,
148 pub hw_ver_str: &'a str,
150 pub sw_ver: u32,
152 pub sw_ver_str: &'a str,
154 pub manufacturing_date: &'a str,
156 pub part_number: &'a str,
158 pub product_url: &'a str,
160 pub product_label: &'a str,
162 pub serial_no: &'a str,
164 pub unique_id: &'a str,
166 pub capability_minima: CapabilityMinima,
168 pub product_appearance: ProductAppearance,
170 pub specification_version: u32,
172 pub data_model_revision: u16,
174 pub max_paths_per_invoke: u16,
176 pub device_name: &'a str,
180 pub device_type: Option<u16>,
184 pub pairing_hint: PairingHintFlags,
188 pub pairing_instruction: &'a str,
192 pub sai: Option<u32>,
200 pub sii: Option<u32>,
208 pub tcp_supported: bool,
215}
216
217impl BasicInfoConfig<'_> {
218 pub const fn new() -> Self {
219 Self {
220 vid: 0,
221 pid: 0,
222 hw_ver: 0,
223 hw_ver_str: "",
224 sw_ver: 0,
225 sw_ver_str: "",
226 serial_no: "",
227 product_name: "",
228 vendor_name: "",
229 manufacturing_date: "",
230 part_number: "",
231 product_url: "",
232 product_label: "",
233 unique_id: "",
234 capability_minima: CapabilityMinima::new(),
235 product_appearance: ProductAppearance::new(),
236 specification_version: DEFAULT_MATTER_SPEC_VERSION,
237 data_model_revision: DEFAULT_DATA_MODEL_REVISION,
238 max_paths_per_invoke: DEFAULT_MAX_PATHS_PER_INVOKE,
239 device_name: "",
240 device_type: None,
241 pairing_hint: PairingHintFlags::empty(),
242 pairing_instruction: "",
243 sai: None,
244 sii: None,
245 tcp_supported: false,
246 }
247 }
248}
249
250impl Default for BasicInfoConfig<'_> {
251 fn default() -> Self {
252 Self::new()
253 }
254}
255
256#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
258#[cfg_attr(feature = "defmt", derive(defmt::Format))]
259pub struct CapabilityMinima {
260 pub case_sessions_per_fabric: u16,
262 pub subscriptions_per_fabric: u16,
264}
265
266const SUBSCRIPTIONS_PER_FABRIC: u16 = 3;
271
272impl CapabilityMinima {
273 pub const fn new() -> Self {
277 Self {
278 case_sessions_per_fabric: (MAX_SESSIONS / MAX_FABRICS) as _,
279 subscriptions_per_fabric: SUBSCRIPTIONS_PER_FABRIC,
280 }
281 }
282}
283
284impl Default for CapabilityMinima {
285 fn default() -> Self {
286 Self::new()
287 }
288}
289
290#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
292#[cfg_attr(feature = "defmt", derive(defmt::Format))]
293pub struct ProductAppearance {
294 pub finish: ProductFinishEnum,
296 pub color: Option<ColorEnum>,
298}
299
300impl ProductAppearance {
301 pub const fn new() -> Self {
304 Self {
305 finish: ProductFinishEnum::Other,
306 color: None,
307 }
308 }
309}
310
311impl Default for ProductAppearance {
312 fn default() -> Self {
313 Self::new()
314 }
315}
316
317#[derive(Debug, Clone, Eq, PartialEq, Hash, ToTLV, FromTLV)]
319#[cfg_attr(feature = "defmt", derive(defmt::Format))]
320pub struct BasicInfoSettings {
321 pub node_label: heapless::String<32>, pub location: Option<heapless::String<2>>, pub location_type: RegulatoryLocationTypeEnum,
324 pub local_config_disabled: bool,
325 pub configuration_version: u32,
332}
333
334impl BasicInfoSettings {
335 pub const fn new() -> Self {
337 Self {
338 node_label: heapless::String::new(),
339 location: None,
340 location_type: RegulatoryLocationTypeEnum::IndoorOutdoor,
341 local_config_disabled: false,
342 configuration_version: 1,
345 }
346 }
347
348 pub fn init() -> impl Init<Self> {
350 init!(Self {
351 node_label: heapless::String::new(),
352 location: None,
353 location_type: RegulatoryLocationTypeEnum::IndoorOutdoor,
354 local_config_disabled: false,
355 configuration_version: 1,
356 })
357 }
358
359 pub fn reset(&mut self) {
364 self.node_label.clear();
365 self.location = None;
366 self.local_config_disabled = false;
367 self.configuration_version = 1;
368 }
369
370 pub fn bump_configuration_version(&mut self) -> u32 {
381 self.configuration_version = self.configuration_version.saturating_add(1);
382 self.configuration_version
383 }
384
385 pub fn set_location(&mut self, location: &str) {
386 if location == "XX" {
387 self.location = None;
388 } else {
389 self.location = Some(unwrap!(heapless::String::<2>::from_str(location)));
390 }
391 }
392
393 pub fn reset_persist<S: KvBlobStore>(
399 &mut self,
400 mut store: S,
401 buf: &mut [u8],
402 ) -> Result<(), Error> {
403 self.reset();
404
405 store.remove(BASIC_INFO_KEY, buf)?;
406
407 info!("Removed basic info settings from storage");
408
409 Ok(())
410 }
411
412 pub fn load(&mut self, data: &[u8]) -> Result<(), Error> {
414 let info = Self::from_tlv(&TLVElement::new(data))?;
415
416 self.node_label = info.node_label;
417 self.location = info.location;
418 self.location_type = info.location_type;
419 self.local_config_disabled = info.local_config_disabled;
420 self.configuration_version = info.configuration_version;
421
422 Ok(())
423 }
424
425 pub fn load_persist<S: KvBlobStore>(
431 &mut self,
432 mut store: S,
433 buf: &mut [u8],
434 ) -> Result<(), Error> {
435 self.reset();
436
437 if let Some(data) = store.load(BASIC_INFO_KEY, buf)? {
438 self.load(data)?;
439
440 info!("Loaded basic info settings from storage");
441 }
442
443 Ok(())
444 }
445}
446
447impl Default for BasicInfoSettings {
448 fn default() -> Self {
449 Self::new()
450 }
451}
452
453#[derive(Clone, Debug)]
455#[cfg_attr(feature = "defmt", derive(defmt::Format))]
456pub struct BasicInfoHandler(Dataver);
457
458impl BasicInfoHandler {
459 pub fn new(dataver: Dataver) -> Self {
461 Self(dataver)
462 }
463
464 pub const fn adapt(self) -> HandlerAdaptor<Self> {
466 HandlerAdaptor(self)
467 }
468
469 fn config<'a>(exchange: &'a Exchange) -> &'a BasicInfoConfig<'a> {
470 exchange.matter().dev_det()
471 }
472
473 fn with_settings<F, R>(exchange: &Exchange, f: F) -> Result<R, Error>
474 where
475 F: FnOnce(&mut BasicInfoSettings) -> Result<R, Error>,
476 {
477 exchange.with_state(|state| f(&mut state.basic_info_settings))
478 }
479}
480
481impl ClusterHandler for BasicInfoHandler {
482 const CLUSTER: Cluster<'static> = FULL_CLUSTER
483 .with_attrs(except!(
495 AttributeId::Reachable | AttributeId::ConfigurationVersion
496 ))
497 .with_cmds(with!());
498
499 fn dataver(&self) -> u32 {
500 self.0.get()
501 }
502
503 fn dataver_changed(&self) {
504 self.0.changed();
505 }
506
507 fn data_model_revision(&self, ctx: impl ReadContext) -> Result<u16, Error> {
508 Ok(Self::config(ctx.exchange()).data_model_revision)
509 }
510
511 fn vendor_id(&self, ctx: impl ReadContext) -> Result<u16, Error> {
512 Ok(Self::config(ctx.exchange()).vid)
513 }
514
515 fn vendor_name<P: TLVBuilderParent>(
516 &self,
517 ctx: impl ReadContext,
518 out: Utf8StrBuilder<P>,
519 ) -> Result<P, Error> {
520 out.set(Self::config(ctx.exchange()).vendor_name)
521 }
522
523 fn product_id(&self, ctx: impl ReadContext) -> Result<u16, Error> {
524 Ok(Self::config(ctx.exchange()).pid)
525 }
526
527 fn product_name<P: TLVBuilderParent>(
528 &self,
529 ctx: impl ReadContext,
530 out: Utf8StrBuilder<P>,
531 ) -> Result<P, Error> {
532 out.set(Self::config(ctx.exchange()).product_name)
533 }
534
535 fn hardware_version(&self, ctx: impl ReadContext) -> Result<u16, Error> {
536 Ok(Self::config(ctx.exchange()).hw_ver)
537 }
538
539 fn hardware_version_string<P: TLVBuilderParent>(
540 &self,
541 ctx: impl ReadContext,
542 out: Utf8StrBuilder<P>,
543 ) -> Result<P, Error> {
544 out.set(Self::config(ctx.exchange()).hw_ver_str)
545 }
546
547 fn software_version(&self, ctx: impl ReadContext) -> Result<u32, Error> {
548 Ok(Self::config(ctx.exchange()).sw_ver)
549 }
550
551 fn software_version_string<P: TLVBuilderParent>(
552 &self,
553 ctx: impl ReadContext,
554 out: Utf8StrBuilder<P>,
555 ) -> Result<P, Error> {
556 out.set(Self::config(ctx.exchange()).sw_ver_str)
557 }
558
559 fn node_label<P: TLVBuilderParent>(
560 &self,
561 ctx: impl ReadContext,
562 out: Utf8StrBuilder<P>,
563 ) -> Result<P, Error> {
564 Self::with_settings(ctx.exchange(), |settings| {
565 out.set(settings.node_label.as_str())
566 })
567 }
568
569 fn set_node_label(&self, ctx: impl WriteContext, label: &str) -> Result<(), Error> {
570 if label.len() > 32 {
571 return Err(ErrorCode::ConstraintError.into());
572 }
573
574 let mut persist = Persist::new(ctx.kv());
575
576 Self::with_settings(ctx.exchange(), |settings| {
577 settings.node_label.clear();
578 settings
579 .node_label
580 .push_str(label)
581 .map_err(|_| ErrorCode::ConstraintError)?;
582
583 persist.store_tlv(BASIC_INFO_KEY, &*settings)
584 })?;
585
586 persist.run()
587 }
588
589 fn location<P: TLVBuilderParent>(
590 &self,
591 ctx: impl ReadContext,
592 out: Utf8StrBuilder<P>,
593 ) -> Result<P, Error> {
594 Self::with_settings(ctx.exchange(), |settings| {
595 out.set(settings.location.as_ref().map_or("XX", |loc| loc.as_str()))
596 })
597 }
598
599 fn set_location(&self, ctx: impl WriteContext, location: &str) -> Result<(), Error> {
600 if location.len() != 2 {
601 return Err(ErrorCode::ConstraintError.into());
602 }
603
604 let mut persist = Persist::new(ctx.kv());
605
606 Self::with_settings(ctx.exchange(), |settings| {
607 settings.set_location(location);
608
609 persist.store_tlv(BASIC_INFO_KEY, &*settings)
610 })?;
611
612 persist.run()
613 }
614
615 fn capability_minima<P: TLVBuilderParent>(
616 &self,
617 ctx: impl ReadContext,
618 builder: CapabilityMinimaStructBuilder<P>,
619 ) -> Result<P, Error> {
620 let cm = Self::config(ctx.exchange()).capability_minima;
621
622 builder
623 .case_sessions_per_fabric(cm.case_sessions_per_fabric)?
624 .subscriptions_per_fabric(cm.subscriptions_per_fabric)?
625 .end()
626 }
627
628 fn specification_version(&self, ctx: impl ReadContext) -> Result<u32, Error> {
629 Ok(Self::config(ctx.exchange()).specification_version)
630 }
631
632 fn max_paths_per_invoke(&self, ctx: impl ReadContext) -> Result<u16, Error> {
633 Ok(Self::config(ctx.exchange()).max_paths_per_invoke)
634 }
635
636 fn configuration_version(&self, ctx: impl ReadContext) -> Result<u32, Error> {
637 Self::with_settings(
640 ctx.exchange(),
641 |settings| Ok(settings.configuration_version),
642 )
643 }
644
645 fn handle_mfg_specific_ping(&self, _ctx: impl InvokeContext) -> Result<(), Error> {
646 Err(ErrorCode::CommandNotFound.into())
647 }
648
649 fn manufacturing_date<P: TLVBuilderParent>(
650 &self,
651 ctx: impl ReadContext,
652 builder: Utf8StrBuilder<P>,
653 ) -> Result<P, Error> {
654 builder.set(Self::config(ctx.exchange()).manufacturing_date)
655 }
656
657 fn part_number<P: TLVBuilderParent>(
658 &self,
659 ctx: impl ReadContext,
660 builder: Utf8StrBuilder<P>,
661 ) -> Result<P, Error> {
662 builder.set(Self::config(ctx.exchange()).part_number)
663 }
664
665 fn product_url<P: TLVBuilderParent>(
666 &self,
667 ctx: impl ReadContext,
668 builder: Utf8StrBuilder<P>,
669 ) -> Result<P, Error> {
670 builder.set(Self::config(ctx.exchange()).product_url)
671 }
672
673 fn product_label<P: TLVBuilderParent>(
674 &self,
675 ctx: impl ReadContext,
676 builder: Utf8StrBuilder<P>,
677 ) -> Result<P, Error> {
678 builder.set(Self::config(ctx.exchange()).product_label)
679 }
680
681 fn serial_number<P: TLVBuilderParent>(
682 &self,
683 ctx: impl ReadContext,
684 builder: Utf8StrBuilder<P>,
685 ) -> Result<P, Error> {
686 builder.set(Self::config(ctx.exchange()).serial_no)
687 }
688
689 fn local_config_disabled(&self, ctx: impl ReadContext) -> Result<bool, Error> {
690 Self::with_settings(
691 ctx.exchange(),
692 |settings| Ok(settings.local_config_disabled),
693 )
694 }
695
696 fn set_local_config_disabled(&self, ctx: impl WriteContext, value: bool) -> Result<(), Error> {
697 let mut persist = Persist::new(ctx.kv());
698
699 Self::with_settings(ctx.exchange(), |settings| {
700 settings.local_config_disabled = value;
701
702 persist.store_tlv(BASIC_INFO_KEY, &*settings)
703 })?;
704
705 persist.run()
706 }
707
708 fn unique_id<P: TLVBuilderParent>(
709 &self,
710 ctx: impl ReadContext,
711 builder: Utf8StrBuilder<P>,
712 ) -> Result<P, Error> {
713 builder.set(Self::config(ctx.exchange()).unique_id)
714 }
715
716 fn product_appearance<P: TLVBuilderParent>(
717 &self,
718 ctx: impl ReadContext,
719 builder: ProductAppearanceStructBuilder<P>,
720 ) -> Result<P, Error> {
721 let appearance = Self::config(ctx.exchange()).product_appearance;
722
723 builder
724 .finish(appearance.finish)?
725 .primary_color(Nullable::new(appearance.color))?
726 .end()
727 }
728}