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;