use super::read_budget::{ReadFailure, Work};
use super::rpm_budget::{self, PlannedObject, PlannedRow};
use super::*;
use bacnet_encoding::constructed::decode_read_access_specification;
use bacnet_objects::traits::BACnetObject;
use bacnet_types::constructed::{PropertyReference, ReadAccessSpecification};
pub(super) struct GroupMembers(Result<Vec<PlannedObject>, Error>);
impl GroupMembers {
pub(super) fn rows(&self) -> impl Iterator<Item = (ObjectIdentifier, &PlannedRow)> {
self.0
.as_deref()
.into_iter()
.flat_map(rpm_budget::planned_rows)
}
}
pub(super) fn plan_property(
db: &ObjectDatabase,
view: Option<&DeviceReadContext<'_>>,
object: &dyn BACnetObject,
reference: &PropertyReference,
) -> Result<Option<GroupMembers>, ReadFailure> {
let mut work = Work::new(view.map_or(usize::MAX, |view| view.work_limit));
work.charge()?;
plan_members(db, view, object, reference, &mut work)
}
pub(super) fn read_served_property(
db: &ObjectDatabase,
view: Option<&DeviceReadContext<'_>>,
object: &dyn BACnetObject,
property: PropertyIdentifier,
array_index: Option<u32>,
members: Option<GroupMembers>,
) -> Result<PropertyValue, Error> {
match members {
Some(members) => value(db, view, members),
None => object.read_property(property, array_index),
}
}
pub(super) fn plan_members(
db: &ObjectDatabase,
view: Option<&DeviceReadContext<'_>>,
object: &dyn BACnetObject,
reference: &PropertyReference,
work: &mut Work,
) -> Result<Option<GroupMembers>, ReadFailure> {
if reference.property_identifier != PropertyIdentifier::PRESENT_VALUE
|| reference.property_array_index.is_some()
|| object.object_identifier().object_type() != ObjectType::GROUP
{
return Ok(None);
}
let planned = match members(object) {
Ok(specs) => Ok(rpm_budget::plan_specs(db, &specs, work, view, false)?),
Err(error) => Err(error),
};
Ok(Some(GroupMembers(planned)))
}
pub(super) fn value(
db: &ObjectDatabase,
view: Option<&DeviceReadContext<'_>>,
members: GroupMembers,
) -> Result<PropertyValue, Error> {
let results = members
.0?
.into_iter()
.map(|member| {
let stored = read_property::read_target_object(db, &member.lookup_oid);
let served = stored.and_then(|object| view.map(|view| view.object(object)));
let object = served
.as_ref()
.map(|served| served as &dyn BACnetObject)
.or(stored);
let result = ReadAccessResult {
object_identifier: member.lookup_oid,
list_of_results: member
.properties
.into_iter()
.map(|row| rpm_budget::read_row(db, view, object, row))
.collect(),
};
let mut encoded = BytesMut::new();
result.encode(&mut encoded);
PropertyValue::ApplicationData(encoded.to_vec())
})
.collect();
Ok(PropertyValue::List(results))
}
pub(super) fn members(group: &dyn BACnetObject) -> Result<Vec<ReadAccessSpecification>, Error> {
let PropertyValue::List(elements) =
group.read_property(PropertyIdentifier::LIST_OF_GROUP_MEMBERS, None)?
else {
return Err(other());
};
elements
.iter()
.map(|element| match element {
PropertyValue::ApplicationData(bytes) => {
match decode_read_access_specification(bytes, 0) {
Ok((member, end)) if end == bytes.len() => Ok(member),
_ => Err(other()),
}
}
_ => Err(other()),
})
.collect()
}
fn other() -> Error {
Error::protocol(ErrorClass::PROPERTY, ErrorCode::OTHER)
}