Skip to main content

bacnet_objects/averaging/
schedule.rs

1//! When the server samples an Averaging object (Clauses 12.5.14 and 12.5.15,
2//! #1144).
3//!
4//! Samples are spaced Window_Interval / Window_Samples apart on the
5//! database's monotonic clock. The schedule restarts whenever the window is
6//! emptied, so a window filled by the server covers Window_Interval from the
7//! moment it was last reset: Attempted_Samples reaches Window_Samples only once
8//! that much time has passed (Clause 12.5.11).
9
10use std::fmt;
11use std::sync::Arc;
12use std::time::Duration;
13
14use crate::traits::MonotonicClock;
15
16/// Shortest spacing at which the server samples one Averaging object.
17///
18/// Window_Interval / Window_Samples can be well under a millisecond (one
19/// second over 1,440 samples), and every sample takes the database's write
20/// lock. A configured spacing below this floor is stretched to it, so such a
21/// window spans more than Window_Interval. Clause 12.5.14 leaves the shortest
22/// acceptable Window_Interval to the implementation.
23pub const MIN_SAMPLE_PERIOD: Duration = Duration::from_millis(100);
24
25/// The monotonic instant the next scheduled sample is due.
26#[derive(Clone, Default)]
27pub(super) struct SampleSchedule {
28    clock: Option<Arc<MonotonicClock>>,
29    /// `None` until a clock is bound.
30    next_due: Option<Duration>,
31}
32
33impl fmt::Debug for SampleSchedule {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        f.debug_struct("SampleSchedule")
36            .field("clock_bound", &self.clock.is_some())
37            .field("next_due", &self.next_due)
38            .finish()
39    }
40}
41
42impl SampleSchedule {
43    /// Bind or remove the clock and restart the schedule from its present
44    /// reading. Removing it stops scheduled sampling.
45    pub(super) fn bind(&mut self, clock: Option<Arc<MonotonicClock>>, period: Duration) {
46        self.clock = clock;
47        self.restart(period);
48    }
49
50    /// Start counting periods from now. The first sample falls one period
51    /// later, so the window holds Window_Samples attempts exactly when
52    /// Window_Interval has passed since the restart.
53    pub(super) fn restart(&mut self, period: Duration) {
54        self.next_due = self
55            .clock
56            .as_ref()
57            .map(|clock| clock().saturating_add(period));
58    }
59
60    pub(super) fn next_due(&self) -> Option<Duration> {
61        self.next_due
62    }
63
64    /// Whether a sample is due at `now`; when it is, move on to the next one.
65    ///
66    /// Due times keep a fixed cadence from the restart, so a pass that runs a
67    /// little late doesn't shift later samples. One that falls a whole period
68    /// or more behind takes a single sample and restarts the cadence from
69    /// `now` instead of catching up with a burst of samples.
70    pub(super) fn take_due(&mut self, now: Duration, period: Duration) -> bool {
71        match self.next_due {
72            Some(due) if due <= now => {
73                let next = due.saturating_add(period);
74                self.next_due = Some(if next > now {
75                    next
76                } else {
77                    now.saturating_add(period)
78                });
79                true
80            }
81            _ => false,
82        }
83    }
84}