Skip to main content

bacnet_objects/trend/
acquisition.rs

1//! How a Trend Log or Trend Log Multiple acquires its records: Logging_Type,
2//! Log_Interval, Align_Intervals, Interval_Offset and Trigger (Clauses
3//! 12.25.9 and 12.25.26 to 12.25.29; 12.30.12 to 12.30.16). The database's
4//! trend poller reads these rows to schedule acquisitions; the object owns
5//! their values and write rules.
6
7use bacnet_types::enums::{ErrorClass, ErrorCode, LoggingType, PropertyIdentifier as P};
8use bacnet_types::error::Error;
9use bacnet_types::primitives::PropertyValue;
10
11use crate::common;
12use crate::log_persistence::LogAcquisitionSettings;
13
14/// The Log_Interval a trend log takes, in hundredths of a second (one
15/// minute), when POLLED is chosen while its Log_Interval is zero. Clauses
16/// 12.25.26 and 12.30.12 leave the default to the device.
17pub const DEFAULT_LOG_INTERVAL: u32 = 6_000;
18
19/// Which object type's rules an [`Acquisition`] follows where the two
20/// differ.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub(super) enum Rules {
23    /// Trend Log (Clause 12.25), which logs by COV too (#1480). A
24    /// Log_Interval write is how older clients move it between POLLED and
25    /// COV (12.25.9): a POLLED log's nonzero interval written to zero turns
26    /// it to COV, and a nonzero interval written while COV turns it back to
27    /// POLLED at that interval. Any value but POLLED, COV and TRIGGERED is
28    /// PROPERTY / OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED, the answer Clause
29    /// 12.25.26 gives for a Logging_Type the object doesn't support.
30    TrendLog,
31    /// Trend Log Multiple (Clause 12.30), which never logs by COV: COV, like
32    /// any value but POLLED and TRIGGERED, is PROPERTY / VALUE_OUT_OF_RANGE
33    /// (12.30.12), and a zero Log_Interval just leaves a POLLED log idle.
34    TrendLogMultiple,
35}
36
37/// A trend log's acquisition settings.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub(super) struct Acquisition {
40    rules: Rules,
41    logging_type: LoggingType,
42    /// Hundredths of a second; zero while TRIGGERED or COV.
43    log_interval: u32,
44    align_intervals: bool,
45    interval_offset: u32,
46    trigger: bool,
47}
48
49impl Acquisition {
50    /// POLLED with no interval, so nothing is acquired until one is set.
51    pub(super) fn new(rules: Rules) -> Self {
52        Self {
53            rules,
54            logging_type: LoggingType::POLLED,
55            log_interval: 0,
56            align_intervals: false,
57            interval_offset: 0,
58            trigger: false,
59        }
60    }
61
62    pub(super) fn logging_type(&self) -> LoggingType {
63        self.logging_type
64    }
65
66    /// The settings a durable log saves (#1541). Trigger isn't one: a
67    /// pending acquisition doesn't outlive a restart.
68    pub(super) fn settings(&self) -> LogAcquisitionSettings {
69        LogAcquisitionSettings {
70            logging_type: self.logging_type,
71            log_interval: self.log_interval,
72            align_intervals: self.align_intervals,
73            interval_offset: self.interval_offset,
74        }
75    }
76
77    /// Take back saved settings as they were. The snapshot was checked
78    /// against these rules when it was read.
79    pub(super) fn restore(&mut self, settings: LogAcquisitionSettings) {
80        self.logging_type = settings.logging_type;
81        self.log_interval = settings.log_interval;
82        self.align_intervals = settings.align_intervals;
83        self.interval_offset = settings.interval_offset;
84        self.trigger = false;
85    }
86
87    /// Whether Log_Interval takes writes now: in every mode but TRIGGERED
88    /// (Table 12-29 footnote 3, Table 12-35 footnote 2).
89    pub(super) fn log_interval_writable(&self) -> bool {
90        self.logging_type != LoggingType::TRIGGERED
91    }
92
93    /// Serve one of the five rows; `None` for any other property.
94    pub(super) fn read(&self, property: P) -> Option<PropertyValue> {
95        Some(match property {
96            P::LOGGING_TYPE => PropertyValue::Enumerated(self.logging_type.to_raw()),
97            P::LOG_INTERVAL => PropertyValue::Unsigned(self.log_interval.into()),
98            P::ALIGN_INTERVALS => PropertyValue::Boolean(self.align_intervals),
99            P::INTERVAL_OFFSET => PropertyValue::Unsigned(self.interval_offset.into()),
100            P::TRIGGER => PropertyValue::Boolean(self.trigger),
101            _ => return None,
102        })
103    }
104
105    /// A write of one of the five rows, a client's or a local process's;
106    /// `None` for any other property, and for Log_Interval while it is
107    /// read-only, when the caller refuses it as it refuses any read-only row.
108    pub(super) fn write(
109        &mut self,
110        property: P,
111        value: &PropertyValue,
112    ) -> Option<Result<(), Error>> {
113        Some(match (property, value) {
114            (P::LOGGING_TYPE, PropertyValue::Enumerated(raw)) => {
115                self.set_logging_type(LoggingType::from_raw(*raw))
116            }
117            (P::LOG_INTERVAL, _) if !self.log_interval_writable() => return None,
118            (P::LOG_INTERVAL, PropertyValue::Unsigned(raw)) => {
119                common::u64_to_u32(*raw).and_then(|hundredths| self.store_log_interval(hundredths))
120            }
121            (P::INTERVAL_OFFSET, PropertyValue::Unsigned(raw)) => {
122                common::u64_to_u32(*raw).map(|hundredths| self.interval_offset = hundredths)
123            }
124            (P::ALIGN_INTERVALS, PropertyValue::Boolean(align)) => {
125                self.align_intervals = *align;
126                Ok(())
127            }
128            (P::TRIGGER, PropertyValue::Boolean(trigger)) => self.write_trigger(*trigger),
129            (
130                P::LOGGING_TYPE
131                | P::LOG_INTERVAL
132                | P::INTERVAL_OFFSET
133                | P::ALIGN_INTERVALS
134                | P::TRIGGER,
135                _,
136            ) => Err(common::invalid_data_type_error()),
137            _ => return None,
138        })
139    }
140
141    /// Choose POLLED, TRIGGERED or (on a Trend Log) COV acquisition
142    /// (Clauses 12.25.26 and 12.30.12). Any other value changes nothing and
143    /// is refused with the error [`Rules`] gives each object type. POLLED
144    /// with a zero Log_Interval sets [`DEFAULT_LOG_INTERVAL`]; TRIGGERED and
145    /// COV set Log_Interval to zero. Leaving TRIGGERED drops a Trigger not
146    /// yet acted on.
147    pub(super) fn set_logging_type(&mut self, logging_type: LoggingType) -> Result<(), Error> {
148        match logging_type {
149            LoggingType::POLLED => {
150                if self.log_interval == 0 {
151                    self.log_interval = DEFAULT_LOG_INTERVAL;
152                }
153                self.trigger = false;
154            }
155            LoggingType::TRIGGERED => self.log_interval = 0,
156            LoggingType::COV if self.rules == Rules::TrendLog => {
157                self.log_interval = 0;
158                self.trigger = false;
159            }
160            _ => return Err(self.unsupported_logging_type()),
161        }
162        self.logging_type = logging_type;
163        Ok(())
164    }
165
166    /// Set Log_Interval as local configuration, held to the network rules:
167    /// while TRIGGERED it is read-only, PROPERTY / WRITE_ACCESS_DENIED, and
168    /// on a Trend Log it may switch between POLLED and COV (see
169    /// [`Rules::TrendLog`]).
170    pub(super) fn set_log_interval(&mut self, hundredths: u32) -> Result<(), Error> {
171        if !self.log_interval_writable() {
172            return Err(common::write_access_denied_error());
173        }
174        self.store_log_interval(hundredths)
175    }
176
177    /// Store a Log_Interval written while it is writable, with a Trend
178    /// Log's two Clause 12.25.9 switches: a POLLED log whose interval goes
179    /// from nonzero to zero now logs by COV, and a COV log given a nonzero
180    /// interval polls at it. Zero written to a COV log, or to a POLLED log
181    /// already at zero, changes no mode.
182    fn store_log_interval(&mut self, hundredths: u32) -> Result<(), Error> {
183        if self.rules == Rules::TrendLog {
184            match self.logging_type {
185                LoggingType::POLLED if self.log_interval != 0 && hundredths == 0 => {
186                    self.logging_type = LoggingType::COV;
187                }
188                LoggingType::COV if hundredths != 0 => {
189                    self.logging_type = LoggingType::POLLED;
190                }
191                _ => {}
192            }
193        }
194        self.log_interval = hundredths;
195        Ok(())
196    }
197
198    /// The refusal of a Logging_Type this object doesn't carry out (see
199    /// [`Rules`]).
200    fn unsupported_logging_type(&self) -> Error {
201        match self.rules {
202            Rules::TrendLog => common::protocol_error(
203                ErrorClass::PROPERTY,
204                ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
205            ),
206            Rules::TrendLogMultiple => common::value_out_of_range_error(),
207        }
208    }
209
210    pub(super) fn set_align_intervals(&mut self, align: bool) {
211        self.align_intervals = align;
212    }
213
214    pub(super) fn set_interval_offset(&mut self, hundredths: u32) {
215        self.interval_offset = hundredths;
216    }
217
218    /// Trigger is set TRUE to ask for one acquisition, which the poller
219    /// makes and then clears it (Clauses 12.25.29 and 12.30.16). Only a
220    /// TRIGGERED log takes it: TRUE on any other is PROPERTY /
221    /// NOT_CONFIGURED_FOR_TRIGGERED_LOGGING. FALSE is accepted and changes
222    /// nothing, so an acquisition already asked for still happens.
223    fn write_trigger(&mut self, trigger: bool) -> Result<(), Error> {
224        if !trigger {
225            return Ok(());
226        }
227        if self.logging_type != LoggingType::TRIGGERED {
228            return Err(common::protocol_error(
229                ErrorClass::PROPERTY,
230                ErrorCode::NOT_CONFIGURED_FOR_TRIGGERED_LOGGING,
231            ));
232        }
233        self.trigger = true;
234        Ok(())
235    }
236
237    /// Set Trigger TRUE as a local process would.
238    pub(super) fn trigger(&mut self) -> Result<(), Error> {
239        self.write_trigger(true)
240    }
241
242    /// A record was acquired: a pending Trigger has been served.
243    pub(super) fn acquired(&mut self) {
244        self.trigger = false;
245    }
246}