use core::str::FromStr;
use crate::dm::{Cluster, Dataver, InvokeContext, ReadContext, WriteContext};
use crate::error::{Error, ErrorCode};
use crate::fabric::MAX_FABRICS;
use crate::persist::{KvBlobStore, Persist, BASIC_INFO_KEY};
use crate::tlv::{FromTLV, Nullable, TLVBuilderParent, TLVElement, ToTLV, Utf8StrBuilder};
use crate::transport::exchange::Exchange;
use crate::transport::session::MAX_SESSIONS;
use crate::utils::bitflags::bitflags;
use crate::utils::init::{init, Init};
use crate::{except, with};
pub use crate::dm::clusters::decl::basic_information::*;
pub use crate::dm::clusters::decl::general_commissioning::RegulatoryLocationTypeEnum;
pub const DEFAULT_MATTER_SPEC_VERSION: u32 = 0x01050100;
pub const DEFAULT_DATA_MODEL_REVISION: u16 = 19;
pub const DEFAULT_MAX_PATHS_PER_INVOKE: u16 = 5;
bitflags! {
#[repr(transparent)]
#[derive(Default)]
#[cfg_attr(not(feature = "defmt"), derive(Debug, Copy, Clone, Eq, PartialEq, Hash))]
pub struct PairingHintFlags: u32 {
const POWER_CYCLE = 0x0000_0001;
const DEV_MANUFACTURER_URL = 0x0000_0002;
const ADMINISTRATOR = 0x0000_0004;
const SETTINGS_MENU = 0x0000_0008;
const CUSTOM_INSTRUCTION = 0x0000_0010;
const DEVICE_MANUAL = 0x0000_0020;
const PRESS_RESET_BUTTON = 0x0000_0040;
const PRESS_RESET_BUTTON_WITH_POWER = 0x0000_0080;
const PRESS_RESET_BUTTON_FOR_N_SECONDS = 0x0000_0100;
const PRESS_RESET_BUTTON_UNTIL_LIGHT_BLINKS = 0x0000_0200;
const PRESS_RESET_BUTTON_FOR_N_SECONDS_WITH_POWER = 0x0000_0400;
const PRESS_RESET_BUTTON_UNTIL_LIGHT_BLINKS_WITH_POWER = 0x0000_0800;
const PRESS_RESET_BUTTON_N_TIMES = 0x0000_1000;
const PRESS_SETUP_BUTTON = 0x0000_2000;
const PRESS_SETUP_BUTTON_WITH_POWER = 0x0000_4000;
const PRESS_SETUP_BUTTON_FOR_N_SECONDS = 0x0000_8000;
const PRESS_SETUP_BUTTON_UNTIL_LIGHT_BLINKS = 0x0001_0000;
const PRESS_SETUP_BUTTON_FOR_N_SECONDS_WITH_POWER = 0x0002_0000;
const PRESS_SETUP_BUTTON_UNTIL_LIGHT_BLINKS_WITH_POWER = 0x0004_0000;
const PRESS_SETUP_BUTTON_N_TIMES = 0x0008_0000;
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct BasicInfoConfig<'a> {
pub vendor_name: &'a str,
pub vid: u16,
pub product_name: &'a str,
pub pid: u16,
pub hw_ver: u16,
pub hw_ver_str: &'a str,
pub sw_ver: u32,
pub sw_ver_str: &'a str,
pub manufacturing_date: &'a str,
pub part_number: &'a str,
pub product_url: &'a str,
pub product_label: &'a str,
pub serial_no: &'a str,
pub unique_id: &'a str,
pub capability_minima: CapabilityMinima,
pub product_appearance: ProductAppearance,
pub specification_version: u32,
pub data_model_revision: u16,
pub max_paths_per_invoke: u16,
pub device_name: &'a str,
pub device_type: Option<u16>,
pub pairing_hint: PairingHintFlags,
pub pairing_instruction: &'a str,
pub sai: Option<u32>,
pub sii: Option<u32>,
pub tcp_supported: bool,
}
impl BasicInfoConfig<'_> {
pub const fn new() -> Self {
Self {
vid: 0,
pid: 0,
hw_ver: 0,
hw_ver_str: "",
sw_ver: 0,
sw_ver_str: "",
serial_no: "",
product_name: "",
vendor_name: "",
manufacturing_date: "",
part_number: "",
product_url: "",
product_label: "",
unique_id: "",
capability_minima: CapabilityMinima::new(),
product_appearance: ProductAppearance::new(),
specification_version: DEFAULT_MATTER_SPEC_VERSION,
data_model_revision: DEFAULT_DATA_MODEL_REVISION,
max_paths_per_invoke: DEFAULT_MAX_PATHS_PER_INVOKE,
device_name: "",
device_type: None,
pairing_hint: PairingHintFlags::empty(),
pairing_instruction: "",
sai: None,
sii: None,
tcp_supported: false,
}
}
}
impl Default for BasicInfoConfig<'_> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CapabilityMinima {
pub case_sessions_per_fabric: u16,
pub subscriptions_per_fabric: u16,
}
const SUBSCRIPTIONS_PER_FABRIC: u16 = 3;
impl CapabilityMinima {
pub const fn new() -> Self {
Self {
case_sessions_per_fabric: (MAX_SESSIONS / MAX_FABRICS) as _,
subscriptions_per_fabric: SUBSCRIPTIONS_PER_FABRIC,
}
}
}
impl Default for CapabilityMinima {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ProductAppearance {
pub finish: ProductFinishEnum,
pub color: Option<ColorEnum>,
}
impl ProductAppearance {
pub const fn new() -> Self {
Self {
finish: ProductFinishEnum::Other,
color: None,
}
}
}
impl Default for ProductAppearance {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, ToTLV, FromTLV)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct BasicInfoSettings {
pub node_label: heapless::String<32>, pub location: Option<heapless::String<2>>, pub location_type: RegulatoryLocationTypeEnum,
pub local_config_disabled: bool,
pub configuration_version: u32,
}
impl BasicInfoSettings {
pub const fn new() -> Self {
Self {
node_label: heapless::String::new(),
location: None,
location_type: RegulatoryLocationTypeEnum::IndoorOutdoor,
local_config_disabled: false,
configuration_version: 1,
}
}
pub fn init() -> impl Init<Self> {
init!(Self {
node_label: heapless::String::new(),
location: None,
location_type: RegulatoryLocationTypeEnum::IndoorOutdoor,
local_config_disabled: false,
configuration_version: 1,
})
}
pub fn reset(&mut self) {
self.node_label.clear();
self.location = None;
self.local_config_disabled = false;
self.configuration_version = 1;
}
pub fn bump_configuration_version(&mut self) -> u32 {
self.configuration_version = self.configuration_version.saturating_add(1);
self.configuration_version
}
pub fn set_location(&mut self, location: &str) {
if location == "XX" {
self.location = None;
} else {
self.location = Some(unwrap!(heapless::String::<2>::from_str(location)));
}
}
pub fn reset_persist<S: KvBlobStore>(
&mut self,
mut store: S,
buf: &mut [u8],
) -> Result<(), Error> {
self.reset();
store.remove(BASIC_INFO_KEY, buf)?;
info!("Removed basic info settings from storage");
Ok(())
}
pub fn load(&mut self, data: &[u8]) -> Result<(), Error> {
let info = Self::from_tlv(&TLVElement::new(data))?;
self.node_label = info.node_label;
self.location = info.location;
self.location_type = info.location_type;
self.local_config_disabled = info.local_config_disabled;
self.configuration_version = info.configuration_version;
Ok(())
}
pub fn load_persist<S: KvBlobStore>(
&mut self,
mut store: S,
buf: &mut [u8],
) -> Result<(), Error> {
self.reset();
if let Some(data) = store.load(BASIC_INFO_KEY, buf)? {
self.load(data)?;
info!("Loaded basic info settings from storage");
}
Ok(())
}
}
impl Default for BasicInfoSettings {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct BasicInfoHandler(Dataver);
impl BasicInfoHandler {
pub fn new(dataver: Dataver) -> Self {
Self(dataver)
}
pub const fn adapt(self) -> HandlerAdaptor<Self> {
HandlerAdaptor(self)
}
fn config<'a>(exchange: &'a Exchange) -> &'a BasicInfoConfig<'a> {
exchange.matter().dev_det()
}
fn with_settings<F, R>(exchange: &Exchange, f: F) -> Result<R, Error>
where
F: FnOnce(&mut BasicInfoSettings) -> Result<R, Error>,
{
exchange.with_state(|state| f(&mut state.basic_info_settings))
}
}
impl ClusterHandler for BasicInfoHandler {
const CLUSTER: Cluster<'static> = FULL_CLUSTER
.with_attrs(except!(
AttributeId::Reachable | AttributeId::ConfigurationVersion
))
.with_cmds(with!());
fn dataver(&self) -> u32 {
self.0.get()
}
fn dataver_changed(&self) {
self.0.changed();
}
fn data_model_revision(&self, ctx: impl ReadContext) -> Result<u16, Error> {
Ok(Self::config(ctx.exchange()).data_model_revision)
}
fn vendor_id(&self, ctx: impl ReadContext) -> Result<u16, Error> {
Ok(Self::config(ctx.exchange()).vid)
}
fn vendor_name<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
out: Utf8StrBuilder<P>,
) -> Result<P, Error> {
out.set(Self::config(ctx.exchange()).vendor_name)
}
fn product_id(&self, ctx: impl ReadContext) -> Result<u16, Error> {
Ok(Self::config(ctx.exchange()).pid)
}
fn product_name<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
out: Utf8StrBuilder<P>,
) -> Result<P, Error> {
out.set(Self::config(ctx.exchange()).product_name)
}
fn hardware_version(&self, ctx: impl ReadContext) -> Result<u16, Error> {
Ok(Self::config(ctx.exchange()).hw_ver)
}
fn hardware_version_string<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
out: Utf8StrBuilder<P>,
) -> Result<P, Error> {
out.set(Self::config(ctx.exchange()).hw_ver_str)
}
fn software_version(&self, ctx: impl ReadContext) -> Result<u32, Error> {
Ok(Self::config(ctx.exchange()).sw_ver)
}
fn software_version_string<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
out: Utf8StrBuilder<P>,
) -> Result<P, Error> {
out.set(Self::config(ctx.exchange()).sw_ver_str)
}
fn node_label<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
out: Utf8StrBuilder<P>,
) -> Result<P, Error> {
Self::with_settings(ctx.exchange(), |settings| {
out.set(settings.node_label.as_str())
})
}
fn set_node_label(&self, ctx: impl WriteContext, label: &str) -> Result<(), Error> {
if label.len() > 32 {
return Err(ErrorCode::ConstraintError.into());
}
let mut persist = Persist::new(ctx.kv());
Self::with_settings(ctx.exchange(), |settings| {
settings.node_label.clear();
settings
.node_label
.push_str(label)
.map_err(|_| ErrorCode::ConstraintError)?;
persist.store_tlv(BASIC_INFO_KEY, &*settings)
})?;
persist.run()
}
fn location<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
out: Utf8StrBuilder<P>,
) -> Result<P, Error> {
Self::with_settings(ctx.exchange(), |settings| {
out.set(settings.location.as_ref().map_or("XX", |loc| loc.as_str()))
})
}
fn set_location(&self, ctx: impl WriteContext, location: &str) -> Result<(), Error> {
if location.len() != 2 {
return Err(ErrorCode::ConstraintError.into());
}
let mut persist = Persist::new(ctx.kv());
Self::with_settings(ctx.exchange(), |settings| {
settings.set_location(location);
persist.store_tlv(BASIC_INFO_KEY, &*settings)
})?;
persist.run()
}
fn capability_minima<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
builder: CapabilityMinimaStructBuilder<P>,
) -> Result<P, Error> {
let cm = Self::config(ctx.exchange()).capability_minima;
builder
.case_sessions_per_fabric(cm.case_sessions_per_fabric)?
.subscriptions_per_fabric(cm.subscriptions_per_fabric)?
.end()
}
fn specification_version(&self, ctx: impl ReadContext) -> Result<u32, Error> {
Ok(Self::config(ctx.exchange()).specification_version)
}
fn max_paths_per_invoke(&self, ctx: impl ReadContext) -> Result<u16, Error> {
Ok(Self::config(ctx.exchange()).max_paths_per_invoke)
}
fn configuration_version(&self, ctx: impl ReadContext) -> Result<u32, Error> {
Self::with_settings(
ctx.exchange(),
|settings| Ok(settings.configuration_version),
)
}
fn handle_mfg_specific_ping(&self, _ctx: impl InvokeContext) -> Result<(), Error> {
Err(ErrorCode::CommandNotFound.into())
}
fn manufacturing_date<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
builder: Utf8StrBuilder<P>,
) -> Result<P, Error> {
builder.set(Self::config(ctx.exchange()).manufacturing_date)
}
fn part_number<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
builder: Utf8StrBuilder<P>,
) -> Result<P, Error> {
builder.set(Self::config(ctx.exchange()).part_number)
}
fn product_url<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
builder: Utf8StrBuilder<P>,
) -> Result<P, Error> {
builder.set(Self::config(ctx.exchange()).product_url)
}
fn product_label<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
builder: Utf8StrBuilder<P>,
) -> Result<P, Error> {
builder.set(Self::config(ctx.exchange()).product_label)
}
fn serial_number<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
builder: Utf8StrBuilder<P>,
) -> Result<P, Error> {
builder.set(Self::config(ctx.exchange()).serial_no)
}
fn local_config_disabled(&self, ctx: impl ReadContext) -> Result<bool, Error> {
Self::with_settings(
ctx.exchange(),
|settings| Ok(settings.local_config_disabled),
)
}
fn set_local_config_disabled(&self, ctx: impl WriteContext, value: bool) -> Result<(), Error> {
let mut persist = Persist::new(ctx.kv());
Self::with_settings(ctx.exchange(), |settings| {
settings.local_config_disabled = value;
persist.store_tlv(BASIC_INFO_KEY, &*settings)
})?;
persist.run()
}
fn unique_id<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
builder: Utf8StrBuilder<P>,
) -> Result<P, Error> {
builder.set(Self::config(ctx.exchange()).unique_id)
}
fn product_appearance<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
builder: ProductAppearanceStructBuilder<P>,
) -> Result<P, Error> {
let appearance = Self::config(ctx.exchange()).product_appearance;
builder
.finish(appearance.finish)?
.primary_color(Nullable::new(appearance.color))?
.end()
}
}