use core::fmt;
use crate::error::{Error, ErrorCode};
use crate::tlv::{FromTLV, TLVArray, TLVElement, ToTLV};
use super::{ClusterId, CmdId, EndptId, GenericPath, IMStatusCode, Status};
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, FromTLV, ToTLV)]
#[tlvargs(datatype = "list")]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CmdPath {
pub endpoint: Option<EndptId>,
pub cluster: Option<ClusterId>,
pub cmd: Option<CmdId>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum CmdPathTag {
Endpoint = 0,
Cluster = 1,
Command = 2,
}
impl CmdPath {
pub const fn new(
endpoint: Option<EndptId>,
cluster: Option<ClusterId>,
cmd: Option<CmdId>,
) -> Self {
Self {
endpoint,
cluster,
cmd,
}
}
pub const fn from_gp(path: &GenericPath) -> Self {
Self {
endpoint: path.endpoint,
cluster: path.cluster,
cmd: path.leaf,
}
}
pub const fn to_gp(&self) -> GenericPath {
GenericPath::new(self.endpoint, self.cluster, self.cmd)
}
pub const fn is_wildcard(&self) -> bool {
self.endpoint.is_none() || self.cluster.is_none() || self.cmd.is_none()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, FromTLV, ToTLV)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CmdStatus {
pub path: CmdPath,
pub status: Status,
pub command_ref: Option<u16>,
}
impl CmdStatus {
pub const fn new(
path: CmdPath,
status: IMStatusCode,
cluster_status: Option<u16>,
command_ref: Option<u16>,
) -> Self {
Self {
path,
status: Status {
status,
cluster_status,
},
command_ref,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, FromTLV, ToTLV)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[tlvargs(lifetime = "'a")]
pub struct CmdData<'a> {
pub path: CmdPath,
pub data: TLVElement<'a>,
pub command_ref: Option<u16>,
}
impl<'a> CmdData<'a> {
pub const fn new(path: CmdPath, data: TLVElement<'a>, command_ref: Option<u16>) -> Self {
Self {
path,
data,
command_ref,
}
}
}
pub enum CmdDataTag {
Path = 0,
Data = 1,
CommandRef = 2,
}
#[derive(Clone, FromTLV, ToTLV, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[tlvargs(lifetime = "'a")]
pub enum CmdResp<'a> {
Cmd(CmdData<'a>),
Status(CmdStatus),
}
impl CmdResp<'_> {
pub const fn status_new(
cmd_path: CmdPath,
status: IMStatusCode,
cluster_status: Option<u16>,
command_ref: Option<u16>,
) -> Self {
Self::Status(CmdStatus {
path: cmd_path,
status: Status::new(status, cluster_status),
command_ref,
})
}
}
impl<'a> From<CmdData<'a>> for CmdResp<'a> {
fn from(value: CmdData<'a>) -> Self {
Self::Cmd(value)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum CmdRespTag {
Cmd = 0,
Status = 1,
}
impl From<CmdStatus> for CmdResp<'_> {
fn from(value: CmdStatus) -> Self {
Self::Status(value)
}
}
#[derive(Clone, PartialEq, Eq, Hash, FromTLV, ToTLV)]
#[tlvargs(lifetime = "'a")]
pub struct InvReq<'a>(TLVElement<'a>);
impl<'a> InvReq<'a> {
pub const fn new(element: TLVElement<'a>) -> Self {
Self(element)
}
pub fn suppress_response(&self) -> Result<bool, Error> {
self.0
.r#struct()?
.find_ctx(0)?
.non_empty()
.map(|t| t.bool())
.unwrap_or(Ok(false))
}
pub fn timed_request(&self) -> Result<bool, Error> {
self.0
.r#struct()?
.find_ctx(1)?
.non_empty()
.map(|t| t.bool())
.unwrap_or(Ok(false))
}
pub fn inv_requests(&self) -> Result<Option<TLVArray<'a, CmdData<'a>>>, Error> {
Option::from_tlv(&self.0.r#struct()?.find_ctx(2)?)
}
}
impl fmt::Debug for InvReq<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("InvReqRef")
.field("suppress_response", &self.suppress_response())
.field("timed_request", &self.timed_request())
.field("inv_requests", &self.inv_requests())
.finish()
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for InvReq<'_> {
fn format(&self, f: defmt::Formatter<'_>) {
defmt::write!(f,
"InvReqRef {{\n suppress_response: {:?},\n timed_request: {:?},\n inv_requests: {:?},\n}}",
self.suppress_response(),
self.timed_request(),
self.inv_requests(),
)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum InvReqTag {
SupressResponse = 0,
TimedReq = 1,
InvokeRequests = 2,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum InvRespTag {
SupressResponse = 0,
InvokeResponses = 1,
}
#[derive(Debug, Clone, FromTLV, ToTLV)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[tlvargs(lifetime = "'a")]
pub struct InvokeResp<'a> {
pub suppress_response: Option<bool>,
pub invoke_responses: Option<TLVArray<'a, CmdResp<'a>>>,
pub more_chunks: Option<bool>,
#[tagval(crate::im::encoding::IM_REVISION_TAG)]
pub interaction_model_revision: Option<u8>,
}
impl<'a> InvokeResp<'a> {
pub fn responses<R>(
&self,
cluster: ClusterId,
cmd: CmdId,
) -> impl Iterator<Item = (EndptId, Result<R, Error>)> + use<'_, 'a, R>
where
R: FromTLV<'a> + 'a,
{
self.invoke_responses
.as_ref()
.into_iter()
.flat_map(|arr| arr.iter())
.filter_map(move |resp| filter_cmd_resp::<R>(resp.ok()?, cluster, cmd))
}
pub fn statuses(
&self,
cluster: ClusterId,
cmd: CmdId,
) -> impl Iterator<Item = (EndptId, Result<(), Error>)> + '_ {
self.invoke_responses
.as_ref()
.into_iter()
.flat_map(|arr| arr.iter())
.filter_map(move |resp| match resp.ok()? {
CmdResp::Status(s) => {
if s.path.cluster != Some(cluster) || s.path.cmd != Some(cmd) {
return None;
}
let endpoint = s.path.endpoint?;
let result = if s.status.status == IMStatusCode::Success {
Ok(())
} else {
let err: Error = s
.status
.status
.to_error_code()
.unwrap_or(ErrorCode::Failure)
.into();
Err(err)
};
Some((endpoint, result))
}
CmdResp::Cmd(_) => None,
})
}
}
fn filter_cmd_resp<'a, R>(
resp: CmdResp<'a>,
cluster: ClusterId,
cmd: CmdId,
) -> Option<(EndptId, Result<R, Error>)>
where
R: FromTLV<'a>,
{
match resp {
CmdResp::Cmd(data) => {
if data.path.cluster != Some(cluster) || data.path.cmd != Some(cmd) {
return None;
}
let endpoint = data.path.endpoint?;
Some((endpoint, R::from_tlv(&data.data)))
}
CmdResp::Status(s) => {
if s.path.cluster != Some(cluster) || s.path.cmd != Some(cmd) {
return None;
}
let endpoint = s.path.endpoint?;
let err: Error = s
.status
.status
.to_error_code()
.unwrap_or(ErrorCode::Failure)
.into();
Some((endpoint, Err(err)))
}
}
}