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