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