diff --git a/examples/onoff_light/src/main.rs b/examples/onoff_light/src/main.rs index 50ae775..ff86b68 100644 --- a/examples/onoff_light/src/main.rs +++ b/examples/onoff_light/src/main.rs @@ -35,11 +35,12 @@ fn main() { pid: 0x8002, hw_ver: 2, sw_ver: 1, + serial_no: "aabbccdd".to_string(), device_name: "OnOff Light".to_string(), }; let dev_att = Box::new(dev_att::HardCodedDevAtt::new()); - let mut matter = core::Matter::new(&dev_info, dev_att, comm_data).unwrap(); + let mut matter = core::Matter::new(dev_info, dev_att, comm_data).unwrap(); let dm = matter.get_data_model(); { let mut node = dm.node.write().unwrap(); diff --git a/matter/src/core.rs b/matter/src/core.rs index 21b72fd..0161fbe 100644 --- a/matter/src/core.rs +++ b/matter/src/core.rs @@ -54,7 +54,7 @@ impl Matter { /// requires a set of device attestation certificates and keys. It is the responsibility of /// this object to return the device attestation details when queried upon. pub fn new( - dev_det: &BasicInfoConfig, + dev_det: BasicInfoConfig, dev_att: Box, dev_comm: CommissioningData, ) -> Result, Error> { diff --git a/matter/src/data_model/cluster_basic_information.rs b/matter/src/data_model/cluster_basic_information.rs index 149096a..164d11c 100644 --- a/matter/src/data_model/cluster_basic_information.rs +++ b/matter/src/data_model/cluster_basic_information.rs @@ -17,13 +17,18 @@ use super::objects::*; use crate::error::*; +use num_derive::FromPrimitive; pub const ID: u32 = 0x0028; + +#[derive(FromPrimitive)] enum Attributes { + DMRevision = 0, VendorId = 2, ProductId = 4, HwVer = 7, SwVer = 9, + SerialNo = 0x0f, } #[derive(Default)] @@ -32,6 +37,16 @@ pub struct BasicInfoConfig { pub pid: u16, pub hw_ver: u16, pub sw_ver: u32, + pub serial_no: String, +} + +fn attr_dm_rev_new() -> Result { + Attribute::new( + Attributes::DMRevision as u16, + AttrValue::Uint8(1), + Access::RV, + Quality::FIXED, + ) /// Device name; up to 32 characters pub device_name: String, } @@ -72,19 +87,31 @@ fn attr_sw_ver_new(sw_ver: u32) -> Result { ) } +fn attr_serial_no_new(label: String) -> Result { + Attribute::new( + Attributes::SerialNo as u16, + AttrValue::Utf8(label), + Access::RV, + Quality::FIXED, + ) +} pub struct BasicInfoCluster { base: Cluster, } impl BasicInfoCluster { - pub fn new(cfg: &BasicInfoConfig) -> Result, Error> { + pub fn new(cfg: BasicInfoConfig) -> Result, Error> { let mut cluster = Box::new(BasicInfoCluster { base: Cluster::new(ID)?, }); + cluster.base.add_attribute(attr_dm_rev_new()?)?; cluster.base.add_attribute(attr_vid_new(cfg.vid)?)?; cluster.base.add_attribute(attr_pid_new(cfg.pid)?)?; cluster.base.add_attribute(attr_hw_ver_new(cfg.hw_ver)?)?; cluster.base.add_attribute(attr_sw_ver_new(cfg.sw_ver)?)?; + cluster + .base + .add_attribute(attr_serial_no_new(cfg.serial_no)?)?; Ok(cluster) } } diff --git a/matter/src/data_model/core.rs b/matter/src/data_model/core.rs index 11bf060..8cddaf4 100644 --- a/matter/src/data_model/core.rs +++ b/matter/src/data_model/core.rs @@ -51,7 +51,7 @@ pub struct DataModel { impl DataModel { pub fn new( - dev_details: &BasicInfoConfig, + dev_details: BasicInfoConfig, dev_att: Box, fabric_mgr: Arc, acl_mgr: Arc, @@ -84,7 +84,7 @@ impl DataModel { ) -> Result { let node = self.node.read().unwrap(); let cluster = node.get_cluster(endpoint, cluster)?; - cluster.base().read_attribute_raw(attr).map(|a| *a) + cluster.base().read_attribute_raw(attr).map(|a| a.clone()) } // Encode a write attribute from a path that may or may not be wildcard diff --git a/matter/src/data_model/device_types.rs b/matter/src/data_model/device_types.rs index a0c9c28..12874f4 100644 --- a/matter/src/data_model/device_types.rs +++ b/matter/src/data_model/device_types.rs @@ -32,18 +32,23 @@ use crate::secure_channel::pake::PaseMgr; use std::sync::Arc; use std::sync::RwLockWriteGuard; +pub const DEV_TYPE_ROOT_NODE: DeviceType = DeviceType { + dtype: 0x0016, + drev: 1, +}; + type WriteNode<'a> = RwLockWriteGuard<'a, Box>; pub fn device_type_add_root_node( node: &mut WriteNode, - dev_info: &BasicInfoConfig, + dev_info: BasicInfoConfig, dev_att: Box, fabric_mgr: Arc, acl_mgr: Arc, pase_mgr: PaseMgr, ) -> Result { // Add the root endpoint - let endpoint = node.add_endpoint()?; + let endpoint = node.add_endpoint(DEV_TYPE_ROOT_NODE)?; if endpoint != 0 { // Somehow endpoint 0 was already added, this shouldn't be the case return Err(Error::Invalid); @@ -63,8 +68,13 @@ pub fn device_type_add_root_node( Ok(endpoint) } +const DEV_TYPE_ON_OFF_LIGHT: DeviceType = DeviceType { + dtype: 0x0100, + drev: 2, +}; + pub fn device_type_add_on_off_light(node: &mut WriteNode) -> Result { - let endpoint = node.add_endpoint()?; + let endpoint = node.add_endpoint(DEV_TYPE_ON_OFF_LIGHT)?; node.add_cluster(endpoint, OnOffCluster::new()?)?; Ok(endpoint) } diff --git a/matter/src/data_model/objects/attribute.rs b/matter/src/data_model/objects/attribute.rs index 5498875..0ffbc73 100644 --- a/matter/src/data_model/objects/attribute.rs +++ b/matter/src/data_model/objects/attribute.rs @@ -88,7 +88,7 @@ bitflags! { * - instead of arrays, can use linked-lists to conserve space and avoid the internal fragmentation */ -#[derive(PartialEq, Copy, Clone)] +#[derive(PartialEq, Clone)] pub enum AttrValue { Int64(i64), Uint8(u8), @@ -96,6 +96,7 @@ pub enum AttrValue { Uint32(u32), Uint64(u64), Bool(bool), + Utf8(String), Custom, } @@ -108,6 +109,7 @@ impl Debug for AttrValue { AttrValue::Uint32(v) => write!(f, "{:?}", *v), AttrValue::Uint64(v) => write!(f, "{:?}", *v), AttrValue::Bool(v) => write!(f, "{:?}", *v), + AttrValue::Utf8(v) => write!(f, "{:?}", *v), AttrValue::Custom => write!(f, "custom-attribute"), }?; Ok(()) @@ -123,6 +125,7 @@ impl ToTLV for AttrValue { AttrValue::Uint16(v) => tw.u16(tag_type, *v), AttrValue::Uint32(v) => tw.u32(tag_type, *v), AttrValue::Uint64(v) => tw.u64(tag_type, *v), + AttrValue::Utf8(v) => tw.utf8(tag_type, v.as_bytes()), _ => { error!("Attribute type not yet supported"); Err(Error::AttributeNotFound) diff --git a/matter/src/data_model/objects/cluster.rs b/matter/src/data_model/objects/cluster.rs index a42aac2..d279363 100644 --- a/matter/src/data_model/objects/cluster.rs +++ b/matter/src/data_model/objects/cluster.rs @@ -87,7 +87,6 @@ pub trait ClusterType { pub struct Cluster { pub(super) id: u32, attributes: Vec, - feature_map: Option, data_ver: u32, } @@ -96,7 +95,6 @@ impl Cluster { let mut c = Cluster { id, attributes: Vec::with_capacity(ATTRS_PER_CLUSTER), - feature_map: None, data_ver: rand::thread_rng().gen_range(0..0xFFFFFFFF), }; c.add_default_attributes()?; @@ -112,22 +110,20 @@ impl Cluster { } pub fn set_feature_map(&mut self, map: u32) -> Result<(), Error> { - if self.feature_map.is_none() { - self.add_attribute(Attribute::new( - GlobalElements::FeatureMap as u16, - AttrValue::Uint32(map), - Access::RV, - Quality::NONE, - )?)?; - } else { - self.write_attribute_raw(GlobalElements::FeatureMap as u16, AttrValue::Uint32(map)) - .map_err(|_| Error::Invalid)?; - } - self.feature_map = Some(map); + self.write_attribute_raw(GlobalElements::FeatureMap as u16, AttrValue::Uint32(map)) + .map_err(|_| Error::Invalid)?; Ok(()) } fn add_default_attributes(&mut self) -> Result<(), Error> { + // Default feature map is 0 + self.add_attribute(Attribute::new( + GlobalElements::FeatureMap as u16, + AttrValue::Uint32(0), + Access::RV, + Quality::NONE, + )?)?; + self.add_attribute(Attribute::new( GlobalElements::AttributeList as u16, AttrValue::Custom, @@ -233,8 +229,7 @@ impl Cluster { return; } GlobalElements::FeatureMap => { - let val = if let Some(m) = self.feature_map { m } else { 0 }; - encoder.encode(EncodeValue::Value(&val)); + encoder.encode(EncodeValue::Value(&attr.value)); return; } _ => { @@ -284,7 +279,7 @@ impl Cluster { ) -> Result<(), IMStatusCode> { let a = self.get_attribute_mut(attr_id)?; if a.value != AttrValue::Custom { - let mut value = a.value; + let mut value = a.value.clone(); value .update_from_tlv(data) .map_err(|_| IMStatusCode::Failure)?; diff --git a/matter/src/data_model/objects/encoder.rs b/matter/src/data_model/objects/encoder.rs index 3e756d8..24a81aa 100644 --- a/matter/src/data_model/objects/encoder.rs +++ b/matter/src/data_model/objects/encoder.rs @@ -115,3 +115,9 @@ pub trait Encoder { /// Encode a status report fn encode_status(&mut self, status: IMStatusCode, cluster_status: u16); } + +#[derive(ToTLV, Copy, Clone)] +pub struct DeviceType { + pub dtype: u16, + pub drev: u16, +} diff --git a/matter/src/data_model/objects/endpoint.rs b/matter/src/data_model/objects/endpoint.rs index a87f887..7ea22dd 100644 --- a/matter/src/data_model/objects/endpoint.rs +++ b/matter/src/data_model/objects/endpoint.rs @@ -19,17 +19,21 @@ use crate::{data_model::objects::ClusterType, error::*, interaction_model::core: use std::fmt; +use super::DeviceType; + pub const CLUSTERS_PER_ENDPT: usize = 9; pub struct Endpoint { + dev_type: DeviceType, clusters: Vec>, } pub type BoxedClusters = [Box]; impl Endpoint { - pub fn new() -> Result, Error> { + pub fn new(dev_type: DeviceType) -> Result, Error> { Ok(Box::new(Endpoint { + dev_type, clusters: Vec::with_capacity(CLUSTERS_PER_ENDPT), })) } @@ -43,6 +47,10 @@ impl Endpoint { } } + pub fn get_dev_type(&self) -> &DeviceType { + &self.dev_type + } + fn get_cluster_index(&self, cluster_id: u32) -> Option { self.clusters.iter().position(|c| c.base().id == cluster_id) } diff --git a/matter/src/data_model/objects/node.rs b/matter/src/data_model/objects/node.rs index 8e391cf..1f2b7cf 100644 --- a/matter/src/data_model/objects/node.rs +++ b/matter/src/data_model/objects/node.rs @@ -23,6 +23,8 @@ use crate::{ }; use std::fmt; +use super::DeviceType; + pub trait ChangeConsumer { fn endpoint_added(&self, id: u16, endpoint: &mut Endpoint) -> Result<(), Error>; } @@ -59,13 +61,13 @@ impl Node { self.changes_cb = Some(consumer); } - pub fn add_endpoint(&mut self) -> Result { + pub fn add_endpoint(&mut self, dev_type: DeviceType) -> Result { let index = self .endpoints .iter() .position(|x| x.is_none()) .ok_or(Error::NoSpace)?; - let mut endpoint = Endpoint::new()?; + let mut endpoint = Endpoint::new(dev_type)?; if let Some(cb) = &self.changes_cb { cb.endpoint_added(index as u16, &mut endpoint)?; } diff --git a/matter/src/data_model/system_model/descriptor.rs b/matter/src/data_model/system_model/descriptor.rs index 4609a73..29fb15b 100644 --- a/matter/src/data_model/system_model/descriptor.rs +++ b/matter/src/data_model/system_model/descriptor.rs @@ -21,7 +21,7 @@ use crate::data_model::core::DataModel; use crate::data_model::objects::*; use crate::error::*; use crate::interaction_model::messages::GenericPath; -use crate::tlv::{TLVWriter, TagType}; +use crate::tlv::{TLVWriter, TagType, ToTLV}; use log::error; pub const ID: u32 = 0x001D; @@ -48,11 +48,28 @@ impl DescriptorCluster { data_model, base: Cluster::new(ID)?, }); + c.base.add_attribute(attr_devtypelist_new()?)?; c.base.add_attribute(attr_serverlist_new()?)?; c.base.add_attribute(attr_partslist_new()?)?; Ok(c) } + fn encode_devtype_list(&self, tag: TagType, tw: &mut TLVWriter) { + let path = GenericPath { + endpoint: Some(self.endpoint_id), + cluster: None, + leaf: None, + }; + let _ = tw.start_array(tag); + let dm = self.data_model.node.read().unwrap(); + let _ = dm.for_each_endpoint(&path, |_, e| { + let dev_type = e.get_dev_type(); + let _ = dev_type.to_tlv(tw, TagType::Anonymous); + Ok(()) + }); + let _ = tw.end_container(); + } + fn encode_server_list(&self, tag: TagType, tw: &mut TLVWriter) { let path = GenericPath { endpoint: Some(self.endpoint_id), @@ -69,8 +86,24 @@ impl DescriptorCluster { } fn encode_parts_list(&self, tag: TagType, tw: &mut TLVWriter) { - // TODO: Support Partslist + let path = GenericPath { + endpoint: None, + cluster: None, + leaf: None, + }; let _ = tw.start_array(tag); + if self.endpoint_id == 0 { + // TODO: If endpoint is another than 0, need to figure out what to do + let dm = self.data_model.node.read().unwrap(); + let _ = dm.for_each_endpoint(&path, |current_path, _| { + if let Some(endpoint_id) = current_path.endpoint { + if endpoint_id != 0 { + let _ = tw.u16(TagType::Anonymous, endpoint_id); + } + } + Ok(()) + }); + } let _ = tw.end_container(); } } @@ -85,6 +118,9 @@ impl ClusterType for DescriptorCluster { fn read_custom_attribute(&self, encoder: &mut dyn Encoder, attr: &AttrDetails) { match num::FromPrimitive::from_u16(attr.attr_id) { + Some(Attributes::DeviceTypeList) => encoder.encode(EncodeValue::Closure(&|tag, tw| { + self.encode_devtype_list(tag, tw) + })), Some(Attributes::ServerList) => encoder.encode(EncodeValue::Closure(&|tag, tw| { self.encode_server_list(tag, tw) })), @@ -98,6 +134,14 @@ impl ClusterType for DescriptorCluster { } } +fn attr_devtypelist_new() -> Result { + Attribute::new( + Attributes::DeviceTypeList as u16, + AttrValue::Custom, + Access::RV, + Quality::NONE, + ) +} fn attr_serverlist_new() -> Result { Attribute::new( Attributes::ServerList as u16, diff --git a/matter/src/lib.rs b/matter/src/lib.rs index 2116822..06c6bd2 100644 --- a/matter/src/lib.rs +++ b/matter/src/lib.rs @@ -50,12 +50,13 @@ //! pid: 0xFFF1, //! hw_ver: 2, //! sw_ver: 1, +//! serial_no: "aabbcc".to_string(), //! device_name: "OnOff Light".to_string(), //! }; //! //! /// Get the Matter Object //! /// The dev_att is an object that implements the DevAttDataFetcher trait. -//! let mut matter = Matter::new(&dev_info, dev_att, comm_data).unwrap(); +//! let mut matter = Matter::new(dev_info, dev_att, comm_data).unwrap(); //! let dm = matter.get_data_model(); //! { //! let mut node = dm.node.write().unwrap(); diff --git a/matter/tests/common/im_engine.rs b/matter/tests/common/im_engine.rs index b1a69de..994bbaa 100644 --- a/matter/tests/common/im_engine.rs +++ b/matter/tests/common/im_engine.rs @@ -94,6 +94,7 @@ impl ImEngine { pid: 11, hw_ver: 12, sw_ver: 13, + serial_no: "aabbccdd".to_string(), device_name: "Test Device".to_string(), }; @@ -106,7 +107,14 @@ impl ImEngine { // Only allow the standard peer node id of the IM Engine default_acl.add_subject(IM_ENGINE_PEER_ID).unwrap(); acl_mgr.add(default_acl).unwrap(); - let dm = DataModel::new(&dev_det, dev_att, fabric_mgr, acl_mgr.clone(), pase_mgr).unwrap(); + let dm = DataModel::new( + dev_det, + dev_att, + fabric_mgr.clone(), + acl_mgr.clone(), + pase_mgr, + ) + .unwrap(); { let mut d = dm.node.write().unwrap(); diff --git a/matter/tests/data_model/acl_and_dataver.rs b/matter/tests/data_model/acl_and_dataver.rs index 11e3576..3dd2f60 100644 --- a/matter/tests/data_model/acl_and_dataver.rs +++ b/matter/tests/data_model/acl_and_dataver.rs @@ -204,10 +204,10 @@ fn read_cluster_id_write_attr(im: &ImEngine, endpoint: u16) -> AttrValue { let node = im.dm.node.read().unwrap(); let echo = node.get_cluster(endpoint, echo_cluster::ID).unwrap(); - *echo - .base() + echo.base() .read_attribute_raw(echo_cluster::Attributes::AttWrite as u16) .unwrap() + .clone() } fn read_cluster_id_data_ver(im: &ImEngine, endpoint: u16) -> u32 { diff --git a/matter/tests/data_model/attributes.rs b/matter/tests/data_model/attributes.rs index 5e230c7..b53614f 100644 --- a/matter/tests/data_model/attributes.rs +++ b/matter/tests/data_model/attributes.rs @@ -238,6 +238,7 @@ fn test_read_wc_endpoint_wc_attribute() { let attr_list_tlvs = get_tlvs( &mut buf, &[ + GlobalElements::FeatureMap as u16, GlobalElements::AttributeList as u16, echo_cluster::Attributes::Att1 as u16, echo_cluster::Attributes::Att2 as u16, @@ -247,6 +248,14 @@ fn test_read_wc_endpoint_wc_attribute() { ); let expected = &[ + attr_data!( + GenericPath::new( + Some(0), + Some(echo_cluster::ID), + Some(GlobalElements::FeatureMap as u32), + ), + ElementType::U8(0) + ), attr_data!( GenericPath::new( Some(0), @@ -279,6 +288,14 @@ fn test_read_wc_endpoint_wc_attribute() { ), ElementType::U32(echo_cluster::ATTR_CUSTOM_VALUE) ), + attr_data!( + GenericPath::new( + Some(1), + Some(echo_cluster::ID), + Some(GlobalElements::FeatureMap as u32), + ), + ElementType::U8(0) + ), attr_data!( GenericPath::new( Some(1),