1use bacnet_types::constructed::{BACnetObjectPropertyReference, BACnetPrescale, BACnetScale};
6use bacnet_types::enums::{ObjectType, PropertyIdentifier};
7use bacnet_types::error::Error;
8use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
9use std::borrow::Cow;
10
11use crate::common::{self, read_common_properties};
12use crate::traits::BACnetObject;
13
14pub struct AccumulatorObject {
20 oid: ObjectIdentifier,
21 name: String,
22 description: String,
23 present_value: u64,
24 max_pres_value: u64,
25 scale: BACnetScale,
26 prescale: Option<BACnetPrescale>,
27 pulse_rate: f32,
28 units: u32,
29 limit_monitoring_interval: u32,
30 status_flags: StatusFlags,
31 event_state: u32,
32 out_of_service: bool,
33 reliability: u32,
34 value_before_change: u64,
35 value_set: u64,
36}
37
38impl AccumulatorObject {
39 pub fn new(instance: u32, name: impl Into<String>, units: u32) -> Result<Self, Error> {
41 let oid = ObjectIdentifier::new(ObjectType::ACCUMULATOR, instance)?;
42 Ok(Self {
43 oid,
44 name: name.into(),
45 description: String::new(),
46 present_value: 0,
47 max_pres_value: u64::MAX,
48 scale: BACnetScale::FloatScale(1.0),
49 prescale: None,
50 pulse_rate: 0.0,
51 units,
52 limit_monitoring_interval: 0,
53 status_flags: StatusFlags::empty(),
54 event_state: 0,
55 out_of_service: false,
56 reliability: 0,
57 value_before_change: 0,
58 value_set: 0,
59 })
60 }
61
62 pub fn set_present_value(&mut self, value: u64) {
64 self.present_value = value;
65 }
66
67 pub fn set_description(&mut self, desc: impl Into<String>) {
69 self.description = desc.into();
70 }
71
72 pub fn set_scale(&mut self, scale: BACnetScale) {
74 self.scale = scale;
75 }
76
77 pub fn set_prescale(&mut self, prescale: BACnetPrescale) {
79 self.prescale = Some(prescale);
80 }
81}
82
83impl BACnetObject for AccumulatorObject {
84 fn object_identifier(&self) -> ObjectIdentifier {
85 self.oid
86 }
87
88 fn object_name(&self) -> &str {
89 &self.name
90 }
91
92 fn read_property(
93 &self,
94 property: PropertyIdentifier,
95 array_index: Option<u32>,
96 ) -> Result<PropertyValue, Error> {
97 if let Some(result) = read_common_properties!(self, property, array_index) {
98 return result;
99 }
100 match property {
101 p if p == PropertyIdentifier::OBJECT_TYPE => {
102 Ok(PropertyValue::Enumerated(ObjectType::ACCUMULATOR.to_raw()))
103 }
104 p if p == PropertyIdentifier::PRESENT_VALUE => {
105 Ok(PropertyValue::Unsigned(self.present_value))
106 }
107 p if p == PropertyIdentifier::MAX_PRES_VALUE => {
108 Ok(PropertyValue::Unsigned(self.max_pres_value))
109 }
110 p if p == PropertyIdentifier::SCALE => match &self.scale {
111 BACnetScale::FloatScale(v) => {
112 Ok(PropertyValue::List(vec![PropertyValue::Real(*v)]))
113 }
114 BACnetScale::IntegerScale(v) => {
115 Ok(PropertyValue::List(vec![PropertyValue::Signed(*v)]))
116 }
117 },
118 p if p == PropertyIdentifier::PRESCALE => match &self.prescale {
119 Some(ps) => Ok(PropertyValue::List(vec![
120 PropertyValue::Unsigned(ps.multiplier as u64),
121 PropertyValue::Unsigned(ps.modulo_divide as u64),
122 ])),
123 None => Ok(PropertyValue::Null),
124 },
125 p if p == PropertyIdentifier::PULSE_RATE => Ok(PropertyValue::Real(self.pulse_rate)),
126 p if p == PropertyIdentifier::UNITS => Ok(PropertyValue::Enumerated(self.units)),
127 p if p == PropertyIdentifier::LIMIT_MONITORING_INTERVAL => Ok(PropertyValue::Unsigned(
128 self.limit_monitoring_interval as u64,
129 )),
130 p if p == PropertyIdentifier::EVENT_STATE => {
131 Ok(PropertyValue::Enumerated(self.event_state))
132 }
133 p if p == PropertyIdentifier::VALUE_BEFORE_CHANGE => {
134 Ok(PropertyValue::Unsigned(self.value_before_change))
135 }
136 p if p == PropertyIdentifier::VALUE_SET => Ok(PropertyValue::Unsigned(self.value_set)),
137 _ => Err(common::unknown_property_error()),
138 }
139 }
140
141 fn write_property(
142 &mut self,
143 property: PropertyIdentifier,
144 _array_index: Option<u32>,
145 value: PropertyValue,
146 _priority: Option<u8>,
147 ) -> Result<(), Error> {
148 if property == PropertyIdentifier::PRESENT_VALUE {
150 return Err(common::write_access_denied_error());
151 }
152 if let Some(result) =
153 common::write_out_of_service(&mut self.out_of_service, property, &value)
154 {
155 return result;
156 }
157 if let Some(result) = common::write_description(&mut self.description, property, &value) {
158 return result;
159 }
160 match property {
161 p if p == PropertyIdentifier::MAX_PRES_VALUE => {
162 if let PropertyValue::Unsigned(v) = value {
163 self.max_pres_value = v;
164 Ok(())
165 } else {
166 Err(common::invalid_data_type_error())
167 }
168 }
169 p if p == PropertyIdentifier::PULSE_RATE => {
170 if let PropertyValue::Real(v) = value {
171 common::reject_non_finite(v)?;
172 self.pulse_rate = v;
173 Ok(())
174 } else {
175 Err(common::invalid_data_type_error())
176 }
177 }
178 p if p == PropertyIdentifier::LIMIT_MONITORING_INTERVAL => {
179 if let PropertyValue::Unsigned(v) = value {
180 self.limit_monitoring_interval = common::u64_to_u32(v)?;
181 Ok(())
182 } else {
183 Err(common::invalid_data_type_error())
184 }
185 }
186 _ => Err(common::write_access_denied_error()),
187 }
188 }
189
190 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
191 static PROPS: &[PropertyIdentifier] = &[
192 PropertyIdentifier::OBJECT_IDENTIFIER,
193 PropertyIdentifier::OBJECT_NAME,
194 PropertyIdentifier::DESCRIPTION,
195 PropertyIdentifier::OBJECT_TYPE,
196 PropertyIdentifier::PRESENT_VALUE,
197 PropertyIdentifier::MAX_PRES_VALUE,
198 PropertyIdentifier::SCALE,
199 PropertyIdentifier::PRESCALE,
200 PropertyIdentifier::PULSE_RATE,
201 PropertyIdentifier::UNITS,
202 PropertyIdentifier::LIMIT_MONITORING_INTERVAL,
203 PropertyIdentifier::STATUS_FLAGS,
204 PropertyIdentifier::EVENT_STATE,
205 PropertyIdentifier::OUT_OF_SERVICE,
206 PropertyIdentifier::RELIABILITY,
207 PropertyIdentifier::VALUE_BEFORE_CHANGE,
208 PropertyIdentifier::VALUE_SET,
209 ];
210 Cow::Borrowed(PROPS)
211 }
212}
213
214pub struct PulseConverterObject {
220 oid: ObjectIdentifier,
221 name: String,
222 description: String,
223 present_value: f32,
224 units: u32,
225 scale_factor: f32,
226 adjust_value: f32,
227 cov_increment: f32,
228 input_reference: Option<BACnetObjectPropertyReference>,
229 status_flags: StatusFlags,
230 event_state: u32,
231 out_of_service: bool,
232 reliability: u32,
233}
234
235impl PulseConverterObject {
236 pub fn new(instance: u32, name: impl Into<String>, units: u32) -> Result<Self, Error> {
238 let oid = ObjectIdentifier::new(ObjectType::PULSE_CONVERTER, instance)?;
239 Ok(Self {
240 oid,
241 name: name.into(),
242 description: String::new(),
243 present_value: 0.0,
244 units,
245 scale_factor: 1.0,
246 adjust_value: 0.0,
247 cov_increment: 0.0,
248 input_reference: None,
249 status_flags: StatusFlags::empty(),
250 event_state: 0,
251 out_of_service: false,
252 reliability: 0,
253 })
254 }
255
256 pub fn set_description(&mut self, desc: impl Into<String>) {
258 self.description = desc.into();
259 }
260
261 pub fn set_input_reference(&mut self, r: BACnetObjectPropertyReference) {
263 self.input_reference = Some(r);
264 }
265}
266
267impl BACnetObject for PulseConverterObject {
268 fn object_identifier(&self) -> ObjectIdentifier {
269 self.oid
270 }
271
272 fn object_name(&self) -> &str {
273 &self.name
274 }
275
276 fn read_property(
277 &self,
278 property: PropertyIdentifier,
279 array_index: Option<u32>,
280 ) -> Result<PropertyValue, Error> {
281 if let Some(result) = read_common_properties!(self, property, array_index) {
282 return result;
283 }
284 match property {
285 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
286 ObjectType::PULSE_CONVERTER.to_raw(),
287 )),
288 p if p == PropertyIdentifier::PRESENT_VALUE => {
289 Ok(PropertyValue::Real(self.present_value))
290 }
291 p if p == PropertyIdentifier::UNITS => Ok(PropertyValue::Enumerated(self.units)),
292 p if p == PropertyIdentifier::SCALE_FACTOR => {
293 Ok(PropertyValue::Real(self.scale_factor))
294 }
295 p if p == PropertyIdentifier::ADJUST_VALUE => {
296 Ok(PropertyValue::Real(self.adjust_value))
297 }
298 p if p == PropertyIdentifier::COV_INCREMENT => {
299 Ok(PropertyValue::Real(self.cov_increment))
300 }
301 p if p == PropertyIdentifier::INPUT_REFERENCE => match &self.input_reference {
302 Some(r) => Ok(PropertyValue::List(vec![
303 PropertyValue::ObjectIdentifier(r.object_identifier),
304 PropertyValue::Enumerated(r.property_identifier),
305 ])),
306 None => Ok(PropertyValue::Null),
307 },
308 p if p == PropertyIdentifier::EVENT_STATE => {
309 Ok(PropertyValue::Enumerated(self.event_state))
310 }
311 _ => Err(common::unknown_property_error()),
312 }
313 }
314
315 fn write_property(
316 &mut self,
317 property: PropertyIdentifier,
318 _array_index: Option<u32>,
319 value: PropertyValue,
320 _priority: Option<u8>,
321 ) -> Result<(), Error> {
322 if let Some(result) =
323 common::write_out_of_service(&mut self.out_of_service, property, &value)
324 {
325 return result;
326 }
327 if let Some(result) = common::write_description(&mut self.description, property, &value) {
328 return result;
329 }
330 if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
331 {
332 return result;
333 }
334 match property {
335 p if p == PropertyIdentifier::PRESENT_VALUE => {
336 if let PropertyValue::Real(v) = value {
337 common::reject_non_finite(v)?;
338 self.present_value = v;
339 Ok(())
340 } else {
341 Err(common::invalid_data_type_error())
342 }
343 }
344 p if p == PropertyIdentifier::SCALE_FACTOR => {
345 if let PropertyValue::Real(v) = value {
346 common::reject_non_finite(v)?;
347 self.scale_factor = v;
348 Ok(())
349 } else {
350 Err(common::invalid_data_type_error())
351 }
352 }
353 p if p == PropertyIdentifier::ADJUST_VALUE => {
354 if let PropertyValue::Real(v) = value {
355 common::reject_non_finite(v)?;
356 self.adjust_value = v;
357 Ok(())
358 } else {
359 Err(common::invalid_data_type_error())
360 }
361 }
362 p if p == PropertyIdentifier::INPUT_REFERENCE => match value {
363 PropertyValue::Null => {
364 self.input_reference = None;
365 Ok(())
366 }
367 PropertyValue::List(ref items) if items.len() >= 2 => {
368 if let (PropertyValue::ObjectIdentifier(oid), PropertyValue::Enumerated(prop)) =
369 (&items[0], &items[1])
370 {
371 self.input_reference =
372 Some(BACnetObjectPropertyReference::new(*oid, *prop));
373 Ok(())
374 } else {
375 Err(common::invalid_data_type_error())
376 }
377 }
378 _ => Err(common::invalid_data_type_error()),
379 },
380 _ => Err(common::write_access_denied_error()),
381 }
382 }
383
384 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
385 static PROPS: &[PropertyIdentifier] = &[
386 PropertyIdentifier::OBJECT_IDENTIFIER,
387 PropertyIdentifier::OBJECT_NAME,
388 PropertyIdentifier::DESCRIPTION,
389 PropertyIdentifier::OBJECT_TYPE,
390 PropertyIdentifier::PRESENT_VALUE,
391 PropertyIdentifier::UNITS,
392 PropertyIdentifier::SCALE_FACTOR,
393 PropertyIdentifier::ADJUST_VALUE,
394 PropertyIdentifier::COV_INCREMENT,
395 PropertyIdentifier::INPUT_REFERENCE,
396 PropertyIdentifier::STATUS_FLAGS,
397 PropertyIdentifier::EVENT_STATE,
398 PropertyIdentifier::OUT_OF_SERVICE,
399 PropertyIdentifier::RELIABILITY,
400 ];
401 Cow::Borrowed(PROPS)
402 }
403
404 fn cov_increment(&self) -> Option<f32> {
405 Some(self.cov_increment)
406 }
407}
408
409#[cfg(test)]
414mod tests {
415 use super::*;
416 use bacnet_types::constructed::BACnetPrescale;
417
418 #[test]
421 fn accumulator_create_and_read_defaults() {
422 let acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
423 assert_eq!(acc.object_name(), "ACC-1");
424 assert_eq!(
425 acc.read_property(PropertyIdentifier::PRESENT_VALUE, None)
426 .unwrap(),
427 PropertyValue::Unsigned(0)
428 );
429 assert_eq!(
430 acc.read_property(PropertyIdentifier::UNITS, None).unwrap(),
431 PropertyValue::Enumerated(95)
432 );
433 }
434
435 #[test]
436 fn accumulator_read_present_value() {
437 let mut acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
438 acc.set_present_value(42);
439 assert_eq!(
440 acc.read_property(PropertyIdentifier::PRESENT_VALUE, None)
441 .unwrap(),
442 PropertyValue::Unsigned(42)
443 );
444 }
445
446 #[test]
447 fn accumulator_present_value_read_only_from_network() {
448 let mut acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
449 let result = acc.write_property(
450 PropertyIdentifier::PRESENT_VALUE,
451 None,
452 PropertyValue::Unsigned(10),
453 None,
454 );
455 assert!(result.is_err());
456 }
457
458 #[test]
459 fn accumulator_read_scale_float() {
460 let acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
461 let val = acc.read_property(PropertyIdentifier::SCALE, None).unwrap();
462 assert_eq!(val, PropertyValue::List(vec![PropertyValue::Real(1.0)]));
463 }
464
465 #[test]
466 fn accumulator_read_scale_integer() {
467 let mut acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
468 acc.set_scale(BACnetScale::IntegerScale(10));
469 let val = acc.read_property(PropertyIdentifier::SCALE, None).unwrap();
470 assert_eq!(val, PropertyValue::List(vec![PropertyValue::Signed(10)]));
471 }
472
473 #[test]
474 fn accumulator_read_prescale_none() {
475 let acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
476 let val = acc
477 .read_property(PropertyIdentifier::PRESCALE, None)
478 .unwrap();
479 assert_eq!(val, PropertyValue::Null);
480 }
481
482 #[test]
483 fn accumulator_read_prescale_set() {
484 let mut acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
485 acc.set_prescale(BACnetPrescale {
486 multiplier: 5,
487 modulo_divide: 100,
488 });
489 let val = acc
490 .read_property(PropertyIdentifier::PRESCALE, None)
491 .unwrap();
492 assert_eq!(
493 val,
494 PropertyValue::List(vec![
495 PropertyValue::Unsigned(5),
496 PropertyValue::Unsigned(100),
497 ])
498 );
499 }
500
501 #[test]
502 fn accumulator_object_type() {
503 let acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
504 let val = acc
505 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
506 .unwrap();
507 assert_eq!(
508 val,
509 PropertyValue::Enumerated(ObjectType::ACCUMULATOR.to_raw())
510 );
511 }
512
513 #[test]
514 fn accumulator_property_list() {
515 let acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
516 let list = acc.property_list();
517 assert!(list.contains(&PropertyIdentifier::PRESENT_VALUE));
518 assert!(list.contains(&PropertyIdentifier::SCALE));
519 assert!(list.contains(&PropertyIdentifier::PRESCALE));
520 assert!(list.contains(&PropertyIdentifier::MAX_PRES_VALUE));
521 assert!(list.contains(&PropertyIdentifier::PULSE_RATE));
522 }
523
524 #[test]
527 fn pulse_converter_create_and_read_defaults() {
528 let pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
529 assert_eq!(pc.object_name(), "PC-1");
530 assert_eq!(
531 pc.read_property(PropertyIdentifier::PRESENT_VALUE, None)
532 .unwrap(),
533 PropertyValue::Real(0.0)
534 );
535 assert_eq!(
536 pc.read_property(PropertyIdentifier::UNITS, None).unwrap(),
537 PropertyValue::Enumerated(62)
538 );
539 }
540
541 #[test]
542 fn pulse_converter_read_write_present_value() {
543 let mut pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
544 pc.write_property(
545 PropertyIdentifier::PRESENT_VALUE,
546 None,
547 PropertyValue::Real(123.45),
548 None,
549 )
550 .unwrap();
551 assert_eq!(
552 pc.read_property(PropertyIdentifier::PRESENT_VALUE, None)
553 .unwrap(),
554 PropertyValue::Real(123.45)
555 );
556 }
557
558 #[test]
559 fn pulse_converter_read_scale_factor() {
560 let pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
561 let val = pc
562 .read_property(PropertyIdentifier::SCALE_FACTOR, None)
563 .unwrap();
564 assert_eq!(val, PropertyValue::Real(1.0));
565 }
566
567 #[test]
568 fn pulse_converter_write_scale_factor() {
569 let mut pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
570 pc.write_property(
571 PropertyIdentifier::SCALE_FACTOR,
572 None,
573 PropertyValue::Real(2.5),
574 None,
575 )
576 .unwrap();
577 assert_eq!(
578 pc.read_property(PropertyIdentifier::SCALE_FACTOR, None)
579 .unwrap(),
580 PropertyValue::Real(2.5)
581 );
582 }
583
584 #[test]
585 fn pulse_converter_cov_increment() {
586 let mut pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
587 assert_eq!(pc.cov_increment(), Some(0.0));
588 pc.write_property(
589 PropertyIdentifier::COV_INCREMENT,
590 None,
591 PropertyValue::Real(1.5),
592 None,
593 )
594 .unwrap();
595 assert_eq!(
596 pc.read_property(PropertyIdentifier::COV_INCREMENT, None)
597 .unwrap(),
598 PropertyValue::Real(1.5)
599 );
600 assert_eq!(pc.cov_increment(), Some(1.5));
601 }
602
603 #[test]
604 fn pulse_converter_object_type() {
605 let pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
606 let val = pc
607 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
608 .unwrap();
609 assert_eq!(
610 val,
611 PropertyValue::Enumerated(ObjectType::PULSE_CONVERTER.to_raw())
612 );
613 }
614
615 #[test]
616 fn pulse_converter_write_wrong_type_rejected() {
617 let mut pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
618 let result = pc.write_property(
619 PropertyIdentifier::PRESENT_VALUE,
620 None,
621 PropertyValue::Unsigned(42),
622 None,
623 );
624 assert!(result.is_err());
625 }
626
627 #[test]
628 fn pulse_converter_input_reference_defaults_null() {
629 let pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
630 assert_eq!(
631 pc.read_property(PropertyIdentifier::INPUT_REFERENCE, None)
632 .unwrap(),
633 PropertyValue::Null
634 );
635 }
636
637 #[test]
638 fn pulse_converter_set_input_reference() {
639 let mut pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
640 let oid = ObjectIdentifier::new(ObjectType::ACCUMULATOR, 1).unwrap();
641 let prop_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
642 pc.set_input_reference(BACnetObjectPropertyReference::new(oid, prop_raw));
643 let val = pc
644 .read_property(PropertyIdentifier::INPUT_REFERENCE, None)
645 .unwrap();
646 assert_eq!(
647 val,
648 PropertyValue::List(vec![
649 PropertyValue::ObjectIdentifier(oid),
650 PropertyValue::Enumerated(prop_raw),
651 ])
652 );
653 }
654
655 #[test]
656 fn pulse_converter_property_list() {
657 let pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
658 let list = pc.property_list();
659 assert!(list.contains(&PropertyIdentifier::PRESENT_VALUE));
660 assert!(list.contains(&PropertyIdentifier::SCALE_FACTOR));
661 assert!(list.contains(&PropertyIdentifier::ADJUST_VALUE));
662 assert!(list.contains(&PropertyIdentifier::COV_INCREMENT));
663 assert!(list.contains(&PropertyIdentifier::INPUT_REFERENCE));
664 }
665}