Skip to main content

bacnet_objects/database/
averaging_sampling.rs

1//! Server-taken Averaging samples (Clauses 12.5.13 to 12.5.15, #1144). Each
2//! object keeps its own schedule; the database reads the property a due
3//! sample names, because only it can reach the other object.
4
5use std::time::Duration;
6
7use bacnet_types::enums::PropertyIdentifier;
8use bacnet_types::primitives::ObjectIdentifier;
9
10use super::ObjectDatabase;
11
12impl ObjectDatabase {
13    /// Take every Averaging sample due at the monotonic instant `now` and
14    /// return the objects that recorded one, for COV processing.
15    ///
16    /// The caller must hold exclusive database access for the whole call. A
17    /// due object names the local property it averages
18    /// (`take_due_averaging_sample_internal`); this reads it and records the
19    /// value. An attempt that yields nothing usable is recorded as a miss,
20    /// which counts toward Attempted_Samples but not Valid_Samples: a
21    /// referenced object or property that doesn't exist, an array index on a
22    /// property that isn't an array, any other read error, and a value the
23    /// object refuses to average (a datatype outside the five Clause 12.5
24    /// accepts, or a REAL that isn't finite).
25    ///
26    /// Without a bound monotonic clock nothing is ever due. Reads go through
27    /// [`BACnetObject::read_property`](crate::traits::BACnetObject::read_property),
28    /// so they must stay bounded, as for Trend Log polling.
29    pub fn sample_due_averaging_objects(&mut self, now: Duration) -> Vec<ObjectIdentifier> {
30        let mut due = Vec::new();
31        self.for_each_object_mut(|oid, object| {
32            if let Some(reference) = object.take_due_averaging_sample_internal(now) {
33                due.push((oid, reference));
34            }
35        });
36        let mut sampled = Vec::with_capacity(due.len());
37        for (oid, reference) in due {
38            let property = PropertyIdentifier::from_raw(reference.property_identifier);
39            let index = reference.property_array_index;
40            let value = self
41                .get(&reference.object_identifier)
42                // An index on anything but an array fails, as ReadProperty's
43                // gate makes it; object read arms may ignore the index.
44                .filter(|target| index.is_none() || target.is_array_property(property))
45                .and_then(|target| target.read_property(property, index).ok());
46            let Some(object) = self.get_mut(&oid) else {
47                continue;
48            };
49            let recorded = value
50                .and_then(|value| object.add_averaging_sample_internal(Some(value)).ok())
51                .is_some()
52                || object.add_averaging_sample_internal(None).is_ok();
53            if recorded {
54                sampled.push(oid);
55            }
56        }
57        sampled
58    }
59}
60
61#[cfg(test)]
62#[path = "averaging_sampling_tests.rs"]
63mod tests;