use bacnet_types::constructed::{
BACnetDeviceObjectPropertyReference, BACnetObjectPropertyReference,
};
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use super::{writes, ScheduleObject, ScheduleWrite};
use crate::{common, device_reference};
pub(crate) const MAX_REFERENCES: usize = 1024;
#[derive(Debug, Clone)]
pub(super) struct HeldCommand {
priority: u8,
references: Vec<BACnetObjectPropertyReference>,
}
fn decode_references(value: &PropertyValue) -> Result<Vec<BACnetObjectPropertyReference>, Error> {
let members: Vec<BACnetDeviceObjectPropertyReference> =
device_reference::decode_references_at(value)?;
if members.len() > MAX_REFERENCES {
return Err(common::at_list_element(
writes::no_space_error(),
MAX_REFERENCES,
));
}
members
.into_iter()
.enumerate()
.map(|(index, member)| {
device_reference::into_local_property_reference(member)
.map_err(|error| common::at_list_element(error, index))
})
.collect()
}
impl ScheduleObject {
pub(super) fn write_object_property_references(
&mut self,
value: PropertyValue,
) -> Result<(), Error> {
let references = decode_references(&value)?;
self.set_object_property_references(references)
}
pub(super) fn write_priority_for_writing(&mut self, value: PropertyValue) -> Result<(), Error> {
let PropertyValue::Unsigned(priority) = value else {
return Err(common::invalid_data_type_error());
};
let priority = u8::try_from(priority).map_err(|_| common::value_out_of_range_error())?;
self.set_priority_for_writing(priority)
}
pub(super) fn targets_changed(&mut self) {
if let Some(held) = self.held.take() {
let priority = held.priority;
let still_written = priority == self.priority_for_writing;
let list = &self.list_of_object_property_references;
let (kept, left): (Vec<_>, Vec<_>) = held
.references
.into_iter()
.partition(|reference| still_written && list.contains(reference));
if !left.is_empty() {
self.relinquish_owed.push(ScheduleWrite {
value: PropertyValue::Null,
priority,
references: left,
retry: false,
});
}
if !kept.is_empty() {
self.held = Some(HeldCommand {
priority,
references: kept,
});
}
}
self.rewrite_owed = true;
let list = &self.list_of_object_property_references;
self.refusing_references
.retain(|reference| list.contains(reference));
let _ = self.recompute_reliability();
}
pub(super) fn command(&mut self, value: PropertyValue) -> Option<ScheduleWrite> {
self.rewrite_owed = false;
let references = &self.list_of_object_property_references;
if references.is_empty() {
self.held = None;
return None;
}
self.held = (value != PropertyValue::Null).then(|| HeldCommand {
priority: self.priority_for_writing,
references: references.clone(),
});
Some(ScheduleWrite {
value,
priority: self.priority_for_writing,
references: references.clone(),
retry: false,
})
}
pub(super) fn take_owed_writes(&mut self) -> Vec<ScheduleWrite> {
let mut writes = std::mem::take(&mut self.relinquish_owed);
let owed = match self.simulated_write.take() {
Some(value) => Some(value),
None if self.out_of_service && self.rewrite_owed => Some(self.present_value.clone()),
None => None,
};
if let Some(value) = owed {
writes.extend(self.command(value));
}
writes
}
}