bacnet_objects/accumulator/
mod.rs1use bacnet_encoding::constructed::{encode_prescale, encode_scale};
6use bacnet_types::constructed::{BACnetPrescale, BACnetScale};
7use bacnet_types::enums::{EventState, ObjectType, PropertyIdentifier, Reliability};
8use bacnet_types::error::Error;
9use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
10use bytes::BytesMut;
11use std::borrow::Cow;
12
13use crate::common::{self, read_common_properties};
14use crate::traits::BACnetObject;
15
16mod metadata;
17mod pulse_converter;
18
19#[doc(hidden)]
20pub use pulse_converter::InputReading;
21pub use pulse_converter::PulseConverterObject;
22
23pub struct AccumulatorObject {
29 oid: ObjectIdentifier,
30 name: String,
31 description: String,
32 present_value: u64,
33 max_pres_value: u64,
34 scale: BACnetScale,
35 prescale: Option<BACnetPrescale>,
36 pulse_rate: f32,
37 units: u32,
38 limit_monitoring_interval: u32,
39 status_flags: StatusFlags,
40 event_state: EventState,
41 out_of_service: bool,
42 reliability: Reliability,
43 value_before_change: u64,
44 value_set: u64,
45}
46
47impl AccumulatorObject {
48 pub fn new(instance: u32, name: impl Into<String>, units: u32) -> Result<Self, Error> {
50 let oid = ObjectIdentifier::new(ObjectType::ACCUMULATOR, instance)?;
51 Ok(Self {
52 oid,
53 name: name.into(),
54 description: String::new(),
55 present_value: 0,
56 max_pres_value: u64::MAX,
57 scale: BACnetScale::FloatScale(1.0),
58 prescale: None,
59 pulse_rate: 0.0,
60 units,
61 limit_monitoring_interval: 0,
62 status_flags: StatusFlags::empty(),
63 event_state: EventState::NORMAL,
64 out_of_service: false,
65 reliability: Reliability::NO_FAULT_DETECTED,
66 value_before_change: 0,
67 value_set: 0,
68 })
69 }
70
71 pub fn set_present_value(&mut self, value: u64) {
73 self.present_value = value;
74 }
75
76 pub fn set_description(&mut self, desc: impl Into<String>) {
78 self.description = desc.into();
79 }
80
81 pub fn set_scale(&mut self, scale: BACnetScale) {
83 self.scale = scale;
84 }
85
86 pub fn set_prescale(&mut self, prescale: BACnetPrescale) {
91 self.prescale = Some(prescale);
92 }
93
94 fn has_prescale(&self) -> bool {
96 self.prescale.is_some()
97 }
98}
99
100impl BACnetObject for AccumulatorObject {
101 fn object_identifier(&self) -> ObjectIdentifier {
102 self.oid
103 }
104
105 fn object_name(&self) -> &str {
106 &self.name
107 }
108
109 fn read_property(
110 &self,
111 property: PropertyIdentifier,
112 array_index: Option<u32>,
113 ) -> Result<PropertyValue, Error> {
114 if let Some(result) = read_common_properties!(self, property, array_index) {
115 return result;
116 }
117 match property {
118 p if p == PropertyIdentifier::OBJECT_TYPE => {
119 Ok(PropertyValue::Enumerated(ObjectType::ACCUMULATOR.to_raw()))
120 }
121 p if p == PropertyIdentifier::PRESENT_VALUE => {
122 Ok(PropertyValue::Unsigned(self.present_value))
123 }
124 p if p == PropertyIdentifier::MAX_PRES_VALUE => {
125 Ok(PropertyValue::Unsigned(self.max_pres_value))
126 }
127 p if p == PropertyIdentifier::SCALE => {
131 let mut encoded = BytesMut::new();
132 encode_scale(&mut encoded, &self.scale);
133 Ok(PropertyValue::ApplicationData(encoded.to_vec()))
134 }
135 p if p == PropertyIdentifier::PRESCALE => match &self.prescale {
136 Some(prescale) => {
137 let mut encoded = BytesMut::new();
138 encode_prescale(&mut encoded, prescale);
139 Ok(PropertyValue::ApplicationData(encoded.to_vec()))
140 }
141 None => Err(common::unknown_property_error()),
142 },
143 p if p == PropertyIdentifier::PULSE_RATE => Ok(PropertyValue::Real(self.pulse_rate)),
144 p if p == PropertyIdentifier::UNITS => Ok(PropertyValue::Enumerated(self.units)),
145 p if p == PropertyIdentifier::LIMIT_MONITORING_INTERVAL => Ok(PropertyValue::Unsigned(
146 self.limit_monitoring_interval as u64,
147 )),
148 p if p == PropertyIdentifier::EVENT_STATE => {
149 Ok(PropertyValue::Enumerated(self.event_state.to_raw()))
150 }
151 p if p == PropertyIdentifier::VALUE_BEFORE_CHANGE => {
152 Ok(PropertyValue::Unsigned(self.value_before_change))
153 }
154 p if p == PropertyIdentifier::VALUE_SET => Ok(PropertyValue::Unsigned(self.value_set)),
155 _ => Err(common::unknown_property_error()),
156 }
157 }
158
159 fn write_property(
160 &mut self,
161 property: PropertyIdentifier,
162 _array_index: Option<u32>,
163 value: PropertyValue,
164 _priority: Option<u8>,
165 ) -> Result<(), Error> {
166 if property == PropertyIdentifier::PRESENT_VALUE {
168 return Err(common::write_access_denied_error());
169 }
170 if let Some(result) =
171 common::write_out_of_service(&mut self.out_of_service, property, &value)
172 {
173 return result;
174 }
175 if let Some(result) = common::write_description(&mut self.description, property, &value) {
176 return result;
177 }
178 match property {
179 p if p == PropertyIdentifier::MAX_PRES_VALUE => {
180 if let PropertyValue::Unsigned(v) = value {
181 self.max_pres_value = v;
182 Ok(())
183 } else {
184 Err(common::invalid_data_type_error())
185 }
186 }
187 p if p == PropertyIdentifier::PULSE_RATE => {
188 if let PropertyValue::Real(v) = value {
189 common::reject_non_finite(v)?;
190 self.pulse_rate = v;
191 Ok(())
192 } else {
193 Err(common::invalid_data_type_error())
194 }
195 }
196 p if p == PropertyIdentifier::LIMIT_MONITORING_INTERVAL => {
197 if let PropertyValue::Unsigned(v) = value {
198 self.limit_monitoring_interval = common::u64_to_u32(v)?;
199 Ok(())
200 } else {
201 Err(common::invalid_data_type_error())
202 }
203 }
204 _ => Err(crate::common::unhandled_write_error(
205 self.property_metadata().as_ref(),
206 property,
207 _array_index,
208 )),
209 }
210 }
211
212 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
213 metadata::for_accumulator_object(self)
214 }
215
216 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
217 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
218 }
219
220 fn supports_cov(&self) -> bool {
221 true
222 }
223}
224
225#[cfg(test)]
230mod tests {
231 use super::*;
232 use bacnet_types::constructed::BACnetPrescale;
233
234 #[test]
237 fn accumulator_create_and_read_defaults() {
238 let acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
239 assert_eq!(acc.object_name(), "ACC-1");
240 assert_eq!(
241 acc.read_property(PropertyIdentifier::PRESENT_VALUE, None)
242 .unwrap(),
243 PropertyValue::Unsigned(0)
244 );
245 assert_eq!(
246 acc.read_property(PropertyIdentifier::UNITS, None).unwrap(),
247 PropertyValue::Enumerated(95)
248 );
249 }
250
251 #[test]
252 fn accumulator_read_present_value() {
253 let mut acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
254 acc.set_present_value(42);
255 assert_eq!(
256 acc.read_property(PropertyIdentifier::PRESENT_VALUE, None)
257 .unwrap(),
258 PropertyValue::Unsigned(42)
259 );
260 }
261
262 #[test]
263 fn accumulator_present_value_read_only_from_network() {
264 let mut acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
265 let result = acc.write_property(
266 PropertyIdentifier::PRESENT_VALUE,
267 None,
268 PropertyValue::Unsigned(10),
269 None,
270 );
271 assert!(result.is_err());
272 }
273
274 #[test]
275 fn accumulator_read_scale_float() {
276 let acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
278 let val = acc.read_property(PropertyIdentifier::SCALE, None).unwrap();
279 assert_eq!(
280 val,
281 PropertyValue::ApplicationData(vec![0x0C, 0x3F, 0x80, 0x00, 0x00])
282 );
283 }
284
285 #[test]
286 fn accumulator_read_scale_integer() {
287 let mut acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
289 acc.set_scale(BACnetScale::IntegerScale(10));
290 let val = acc.read_property(PropertyIdentifier::SCALE, None).unwrap();
291 assert_eq!(val, PropertyValue::ApplicationData(vec![0x19, 0x0A]));
292 acc.set_scale(BACnetScale::IntegerScale(-2));
293 let val = acc.read_property(PropertyIdentifier::SCALE, None).unwrap();
294 assert_eq!(val, PropertyValue::ApplicationData(vec![0x19, 0xFE]));
295 }
296
297 #[test]
298 fn accumulator_without_prescale_does_not_serve_it() {
299 let acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
301 let err = acc
302 .read_property(PropertyIdentifier::PRESCALE, None)
303 .unwrap_err();
304 assert!(
305 matches!(err, Error::Protocol { class, code }
306 if class == bacnet_types::enums::ErrorClass::PROPERTY.to_raw() as u32
307 && code == bacnet_types::enums::ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32),
308 "{err:?}"
309 );
310 assert!(!acc.property_list().contains(&PropertyIdentifier::PRESCALE));
311 }
312
313 #[test]
314 fn accumulator_read_prescale_set() {
315 let mut acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
316 acc.set_prescale(BACnetPrescale {
317 multiplier: 5,
318 modulo_divide: 100,
319 });
320 let val = acc
321 .read_property(PropertyIdentifier::PRESCALE, None)
322 .unwrap();
323 assert_eq!(
325 val,
326 PropertyValue::ApplicationData(vec![0x09, 0x05, 0x19, 0x64])
327 );
328 }
329
330 #[test]
331 fn accumulator_object_type() {
332 let acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
333 let val = acc
334 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
335 .unwrap();
336 assert_eq!(
337 val,
338 PropertyValue::Enumerated(ObjectType::ACCUMULATOR.to_raw())
339 );
340 }
341
342 #[test]
343 fn accumulator_property_list() {
344 let mut acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
345 acc.set_prescale(BACnetPrescale {
346 multiplier: 1,
347 modulo_divide: 1,
348 });
349 let list = acc.property_list();
350 assert!(list.contains(&PropertyIdentifier::PRESENT_VALUE));
351 assert!(list.contains(&PropertyIdentifier::SCALE));
352 assert!(list.contains(&PropertyIdentifier::PRESCALE));
353 assert!(list.contains(&PropertyIdentifier::MAX_PRES_VALUE));
354 assert!(list.contains(&PropertyIdentifier::PULSE_RATE));
355 }
356}