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