use bacnet_types::constructed::BACnetObjectPropertyReference;
use bacnet_types::enums::{ObjectType, PropertyIdentifier};
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
use super::ObjectDatabase;
use crate::accumulator::InputReading;
use crate::traits::BACnetObject;
enum InputSource {
Unusable,
Idle,
Reading(InputReading),
}
impl ObjectDatabase {
pub fn check_input_reference(&mut self, oid: &ObjectIdentifier) -> bool {
let Some((usable, _)) = self.judge(oid) else {
return false;
};
self.objects
.get_mut(oid)
.is_some_and(|object| object.set_input_usable_internal(usable))
}
pub(super) fn recheck_input_references(
&mut self,
changed: ObjectIdentifier,
) -> Vec<ObjectIdentifier> {
let concerned: Vec<_> = self
.type_index
.get(&ObjectType::PULSE_CONVERTER)
.into_iter()
.flatten()
.copied()
.filter(|converter| {
*converter == changed
|| self
.objects
.get(converter)
.and_then(|object| object.input_reference_internal().flatten())
.is_some_and(|reference| reference.object_identifier == changed)
})
.collect();
concerned
.into_iter()
.filter(|converter| self.check_input_reference(converter))
.collect()
}
pub fn count_pulse_inputs(&mut self) -> Vec<ObjectIdentifier> {
let converters = self.find_by_type(ObjectType::PULSE_CONVERTER);
let mut changed = Vec::new();
for oid in converters {
let Some((usable, reading)) = self.judge(&oid) else {
continue;
};
let Some(object) = self.objects.get_mut(&oid) else {
continue;
};
let judged = object.set_input_usable_internal(usable);
let counted = object.take_input_reading_internal(reading);
if judged || counted {
changed.push(oid);
}
}
changed
}
fn judge(&self, oid: &ObjectIdentifier) -> Option<(bool, Option<InputReading>)> {
let reference = self.objects.get(oid)?.input_reference_internal()?;
Some(
match reference.map(|reference| self.input_source(*oid, reference)) {
None | Some(InputSource::Idle) => (true, None),
Some(InputSource::Unusable) => (false, None),
Some(InputSource::Reading(reading)) => (true, Some(reading)),
},
)
}
fn input_source(
&self,
converter: ObjectIdentifier,
reference: &BACnetObjectPropertyReference,
) -> InputSource {
if reference.object_identifier == converter {
return InputSource::Unusable;
}
let Some(target) = self.get(&reference.object_identifier) else {
return InputSource::Unusable;
};
let property = PropertyIdentifier::from_raw(reference.property_identifier);
let index = reference.property_array_index;
match index {
Some(0) => return InputSource::Unusable,
Some(_) if !target.is_array_property(property) => return InputSource::Unusable,
_ => {}
}
let slot = property == PropertyIdentifier::PRIORITY_ARRAY && index.is_some();
if slot
&& integer(target.read_property(PropertyIdentifier::RELINQUISH_DEFAULT, None)).is_none()
{
return InputSource::Unusable;
}
let value = target.read_property(property, index);
if slot && matches!(value, Ok(PropertyValue::Null)) {
return InputSource::Idle;
}
match integer(value) {
Some(value) => InputSource::Reading(InputReading {
value,
wraps_after: wrap_bound(target, property, index),
}),
None => InputSource::Unusable,
}
}
}
fn integer(read: Result<PropertyValue, bacnet_types::error::Error>) -> Option<i128> {
match read {
Ok(PropertyValue::Unsigned(value)) => Some(i128::from(value)),
Ok(PropertyValue::Signed(value)) => Some(i128::from(value)),
_ => None,
}
}
fn wrap_bound(
target: &dyn BACnetObject,
property: PropertyIdentifier,
index: Option<u32>,
) -> Option<u64> {
let counter = target.object_identifier().object_type() == ObjectType::ACCUMULATOR
&& property == PropertyIdentifier::PRESENT_VALUE
&& index.is_none();
match target.read_property(PropertyIdentifier::MAX_PRES_VALUE, None) {
Ok(PropertyValue::Unsigned(max)) if counter => Some(max),
_ => None,
}
}
#[cfg(test)]
#[path = "input_references_tests.rs"]
mod tests;