use super::CovSample;
use bacnet_types::{error::Error, primitives::PropertyValue};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CovObservation {
sample: CovSample,
flags: Option<u8>,
source_companions: Option<(CovSample, Option<CovSample>)>,
triggers: Box<[CovSample]>,
}
impl CovObservation {
pub fn new(sample: CovSample, flags: Option<&PropertyValue>) -> Result<Self, Error> {
Ok(Self {
sample,
source_companions: None,
flags: flags.map(validate_flags).transpose()?,
triggers: Box::default(),
})
}
pub(crate) fn with_triggers(mut self, triggers: Box<[CovSample]>) -> Self {
self.triggers = triggers;
self
}
pub(crate) fn triggers_changed(&self, previous: Option<&Self>) -> bool {
!self.triggers.is_empty() && previous.map(|p| &p.triggers) != Some(&self.triggers)
}
pub(crate) fn with_source_companions(
mut self,
pv: CovSample,
priority: Option<CovSample>,
) -> Self {
self.source_companions = Some((pv, priority));
self
}
pub(crate) fn source_companions(&self) -> Option<&(CovSample, Option<CovSample>)> {
self.source_companions.as_ref()
}
pub fn sample(&self) -> &CovSample {
&self.sample
}
pub fn status_flags(&self) -> Option<u8> {
self.flags
}
pub(crate) fn flags_changed(&self, previous: Option<&Self>) -> bool {
self.flags.is_some() && previous.map(|p| p.flags) != Some(self.flags)
}
}
pub(crate) fn validate_flags(value: &PropertyValue) -> Result<u8, Error> {
match value {
PropertyValue::BitString {
unused_bits: 4,
data,
} if data.len() == 1 && data[0] & 0x0f == 0 => Ok(data[0] >> 4),
_ => Err(Error::Encoding(
"COV Status_Flags must be a canonical four-bit BitString".into(),
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cov_status_observation_validation_and_presence_semantics() {
let sample = CovSample::new(&PropertyValue::Real(1.0)).unwrap();
let absent = CovObservation::new(sample.clone(), None).unwrap();
let flags = PropertyValue::BitString {
unused_bits: 4,
data: vec![0x80],
};
let present = CovObservation::new(sample.clone(), Some(&flags)).unwrap();
assert!(present.flags_changed(None));
assert!(present.flags_changed(Some(&absent)));
assert!(!present.flags_changed(Some(&present)));
assert!(!absent.flags_changed(Some(&present)));
assert_eq!(present.status_flags(), Some(8));
assert_eq!(absent.status_flags(), None);
for invalid in [
PropertyValue::Null,
PropertyValue::BitString {
unused_bits: 0,
data: vec![0],
},
PropertyValue::BitString {
unused_bits: 4,
data: vec![],
},
PropertyValue::BitString {
unused_bits: 4,
data: vec![0, 0],
},
PropertyValue::BitString {
unused_bits: 4,
data: vec![0x81],
},
] {
assert!(CovObservation::new(sample.clone(), Some(&invalid)).is_err());
}
}
}