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