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 fn supports_cov(&self) -> bool {
214 true
215 }
216}
217
218pub struct PulseConverterObject {
224 oid: ObjectIdentifier,
225 name: String,
226 description: String,
227 present_value: f32,
228 units: u32,
229 scale_factor: f32,
230 adjust_value: f32,
231 cov_increment: f32,
232 input_reference: Option<BACnetObjectPropertyReference>,
233 status_flags: StatusFlags,
234 event_state: u32,
235 out_of_service: bool,
236 reliability: u32,
237}
238
239impl PulseConverterObject {
240 pub fn new(instance: u32, name: impl Into<String>, units: u32) -> Result<Self, Error> {
242 let oid = ObjectIdentifier::new(ObjectType::PULSE_CONVERTER, instance)?;
243 Ok(Self {
244 oid,
245 name: name.into(),
246 description: String::new(),
247 present_value: 0.0,
248 units,
249 scale_factor: 1.0,
250 adjust_value: 0.0,
251 cov_increment: 0.0,
252 input_reference: None,
253 status_flags: StatusFlags::empty(),
254 event_state: 0,
255 out_of_service: false,
256 reliability: 0,
257 })
258 }
259
260 pub fn set_description(&mut self, desc: impl Into<String>) {
262 self.description = desc.into();
263 }
264
265 pub fn set_input_reference(&mut self, r: BACnetObjectPropertyReference) {
267 self.input_reference = Some(r);
268 }
269}
270
271impl BACnetObject for PulseConverterObject {
272 fn object_identifier(&self) -> ObjectIdentifier {
273 self.oid
274 }
275
276 fn object_name(&self) -> &str {
277 &self.name
278 }
279
280 fn read_property(
281 &self,
282 property: PropertyIdentifier,
283 array_index: Option<u32>,
284 ) -> Result<PropertyValue, Error> {
285 if let Some(result) = read_common_properties!(self, property, array_index) {
286 return result;
287 }
288 match property {
289 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
290 ObjectType::PULSE_CONVERTER.to_raw(),
291 )),
292 p if p == PropertyIdentifier::PRESENT_VALUE => {
293 Ok(PropertyValue::Real(self.present_value))
294 }
295 p if p == PropertyIdentifier::UNITS => Ok(PropertyValue::Enumerated(self.units)),
296 p if p == PropertyIdentifier::SCALE_FACTOR => {
297 Ok(PropertyValue::Real(self.scale_factor))
298 }
299 p if p == PropertyIdentifier::ADJUST_VALUE => {
300 Ok(PropertyValue::Real(self.adjust_value))
301 }
302 p if p == PropertyIdentifier::COV_INCREMENT => {
303 Ok(PropertyValue::Real(self.cov_increment))
304 }
305 p if p == PropertyIdentifier::INPUT_REFERENCE => match &self.input_reference {
306 Some(r) => Ok(PropertyValue::List(vec![
307 PropertyValue::ObjectIdentifier(r.object_identifier),
308 PropertyValue::Enumerated(r.property_identifier),
309 ])),
310 None => Ok(PropertyValue::Null),
311 },
312 p if p == PropertyIdentifier::EVENT_STATE => {
313 Ok(PropertyValue::Enumerated(self.event_state))
314 }
315 _ => Err(common::unknown_property_error()),
316 }
317 }
318
319 fn write_property(
320 &mut self,
321 property: PropertyIdentifier,
322 _array_index: Option<u32>,
323 value: PropertyValue,
324 _priority: Option<u8>,
325 ) -> Result<(), Error> {
326 if let Some(result) =
327 common::write_out_of_service(&mut self.out_of_service, property, &value)
328 {
329 return result;
330 }
331 if let Some(result) = common::write_description(&mut self.description, property, &value) {
332 return result;
333 }
334 if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
335 {
336 return result;
337 }
338 match property {
339 p if p == PropertyIdentifier::PRESENT_VALUE => {
340 if let PropertyValue::Real(v) = value {
341 common::reject_non_finite(v)?;
342 self.present_value = v;
343 Ok(())
344 } else {
345 Err(common::invalid_data_type_error())
346 }
347 }
348 p if p == PropertyIdentifier::SCALE_FACTOR => {
349 if let PropertyValue::Real(v) = value {
350 common::reject_non_finite(v)?;
351 self.scale_factor = v;
352 Ok(())
353 } else {
354 Err(common::invalid_data_type_error())
355 }
356 }
357 p if p == PropertyIdentifier::ADJUST_VALUE => {
358 if let PropertyValue::Real(v) = value {
359 common::reject_non_finite(v)?;
360 self.adjust_value = v;
361 Ok(())
362 } else {
363 Err(common::invalid_data_type_error())
364 }
365 }
366 p if p == PropertyIdentifier::INPUT_REFERENCE => match value {
367 PropertyValue::Null => {
368 self.input_reference = None;
369 Ok(())
370 }
371 PropertyValue::List(ref items) if items.len() >= 2 => {
372 if let (PropertyValue::ObjectIdentifier(oid), PropertyValue::Enumerated(prop)) =
373 (&items[0], &items[1])
374 {
375 self.input_reference =
376 Some(BACnetObjectPropertyReference::new(*oid, *prop));
377 Ok(())
378 } else {
379 Err(common::invalid_data_type_error())
380 }
381 }
382 _ => Err(common::invalid_data_type_error()),
383 },
384 _ => Err(common::write_access_denied_error()),
385 }
386 }
387
388 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
389 static PROPS: &[PropertyIdentifier] = &[
390 PropertyIdentifier::OBJECT_IDENTIFIER,
391 PropertyIdentifier::OBJECT_NAME,
392 PropertyIdentifier::DESCRIPTION,
393 PropertyIdentifier::OBJECT_TYPE,
394 PropertyIdentifier::PRESENT_VALUE,
395 PropertyIdentifier::UNITS,
396 PropertyIdentifier::SCALE_FACTOR,
397 PropertyIdentifier::ADJUST_VALUE,
398 PropertyIdentifier::COV_INCREMENT,
399 PropertyIdentifier::INPUT_REFERENCE,
400 PropertyIdentifier::STATUS_FLAGS,
401 PropertyIdentifier::EVENT_STATE,
402 PropertyIdentifier::OUT_OF_SERVICE,
403 PropertyIdentifier::RELIABILITY,
404 ];
405 Cow::Borrowed(PROPS)
406 }
407
408 fn supports_cov(&self) -> bool {
409 true
410 }
411
412 fn cov_increment(&self) -> Option<f32> {
413 Some(self.cov_increment)
414 }
415}
416
417#[cfg(test)]
422mod tests {
423 use super::*;
424 use bacnet_types::constructed::BACnetPrescale;
425
426 #[test]
429 fn accumulator_create_and_read_defaults() {
430 let acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
431 assert_eq!(acc.object_name(), "ACC-1");
432 assert_eq!(
433 acc.read_property(PropertyIdentifier::PRESENT_VALUE, None)
434 .unwrap(),
435 PropertyValue::Unsigned(0)
436 );
437 assert_eq!(
438 acc.read_property(PropertyIdentifier::UNITS, None).unwrap(),
439 PropertyValue::Enumerated(95)
440 );
441 }
442
443 #[test]
444 fn accumulator_read_present_value() {
445 let mut acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
446 acc.set_present_value(42);
447 assert_eq!(
448 acc.read_property(PropertyIdentifier::PRESENT_VALUE, None)
449 .unwrap(),
450 PropertyValue::Unsigned(42)
451 );
452 }
453
454 #[test]
455 fn accumulator_present_value_read_only_from_network() {
456 let mut acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
457 let result = acc.write_property(
458 PropertyIdentifier::PRESENT_VALUE,
459 None,
460 PropertyValue::Unsigned(10),
461 None,
462 );
463 assert!(result.is_err());
464 }
465
466 #[test]
467 fn accumulator_read_scale_float() {
468 let acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
469 let val = acc.read_property(PropertyIdentifier::SCALE, None).unwrap();
470 assert_eq!(val, PropertyValue::List(vec![PropertyValue::Real(1.0)]));
471 }
472
473 #[test]
474 fn accumulator_read_scale_integer() {
475 let mut acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
476 acc.set_scale(BACnetScale::IntegerScale(10));
477 let val = acc.read_property(PropertyIdentifier::SCALE, None).unwrap();
478 assert_eq!(val, PropertyValue::List(vec![PropertyValue::Signed(10)]));
479 }
480
481 #[test]
482 fn accumulator_read_prescale_none() {
483 let acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
484 let val = acc
485 .read_property(PropertyIdentifier::PRESCALE, None)
486 .unwrap();
487 assert_eq!(val, PropertyValue::Null);
488 }
489
490 #[test]
491 fn accumulator_read_prescale_set() {
492 let mut acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
493 acc.set_prescale(BACnetPrescale {
494 multiplier: 5,
495 modulo_divide: 100,
496 });
497 let val = acc
498 .read_property(PropertyIdentifier::PRESCALE, None)
499 .unwrap();
500 assert_eq!(
501 val,
502 PropertyValue::List(vec![
503 PropertyValue::Unsigned(5),
504 PropertyValue::Unsigned(100),
505 ])
506 );
507 }
508
509 #[test]
510 fn accumulator_object_type() {
511 let acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
512 let val = acc
513 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
514 .unwrap();
515 assert_eq!(
516 val,
517 PropertyValue::Enumerated(ObjectType::ACCUMULATOR.to_raw())
518 );
519 }
520
521 #[test]
522 fn accumulator_property_list() {
523 let acc = AccumulatorObject::new(1, "ACC-1", 95).unwrap();
524 let list = acc.property_list();
525 assert!(list.contains(&PropertyIdentifier::PRESENT_VALUE));
526 assert!(list.contains(&PropertyIdentifier::SCALE));
527 assert!(list.contains(&PropertyIdentifier::PRESCALE));
528 assert!(list.contains(&PropertyIdentifier::MAX_PRES_VALUE));
529 assert!(list.contains(&PropertyIdentifier::PULSE_RATE));
530 }
531
532 #[test]
535 fn pulse_converter_create_and_read_defaults() {
536 let pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
537 assert_eq!(pc.object_name(), "PC-1");
538 assert_eq!(
539 pc.read_property(PropertyIdentifier::PRESENT_VALUE, None)
540 .unwrap(),
541 PropertyValue::Real(0.0)
542 );
543 assert_eq!(
544 pc.read_property(PropertyIdentifier::UNITS, None).unwrap(),
545 PropertyValue::Enumerated(62)
546 );
547 }
548
549 #[test]
550 fn pulse_converter_read_write_present_value() {
551 let mut pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
552 pc.write_property(
553 PropertyIdentifier::PRESENT_VALUE,
554 None,
555 PropertyValue::Real(123.45),
556 None,
557 )
558 .unwrap();
559 assert_eq!(
560 pc.read_property(PropertyIdentifier::PRESENT_VALUE, None)
561 .unwrap(),
562 PropertyValue::Real(123.45)
563 );
564 }
565
566 #[test]
567 fn pulse_converter_read_scale_factor() {
568 let pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
569 let val = pc
570 .read_property(PropertyIdentifier::SCALE_FACTOR, None)
571 .unwrap();
572 assert_eq!(val, PropertyValue::Real(1.0));
573 }
574
575 #[test]
576 fn pulse_converter_write_scale_factor() {
577 let mut pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
578 pc.write_property(
579 PropertyIdentifier::SCALE_FACTOR,
580 None,
581 PropertyValue::Real(2.5),
582 None,
583 )
584 .unwrap();
585 assert_eq!(
586 pc.read_property(PropertyIdentifier::SCALE_FACTOR, None)
587 .unwrap(),
588 PropertyValue::Real(2.5)
589 );
590 }
591
592 #[test]
593 fn pulse_converter_cov_increment() {
594 let mut pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
595 assert_eq!(pc.cov_increment(), Some(0.0));
596 pc.write_property(
597 PropertyIdentifier::COV_INCREMENT,
598 None,
599 PropertyValue::Real(1.5),
600 None,
601 )
602 .unwrap();
603 assert_eq!(
604 pc.read_property(PropertyIdentifier::COV_INCREMENT, None)
605 .unwrap(),
606 PropertyValue::Real(1.5)
607 );
608 assert_eq!(pc.cov_increment(), Some(1.5));
609 }
610
611 #[test]
612 fn pulse_converter_object_type() {
613 let pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
614 let val = pc
615 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
616 .unwrap();
617 assert_eq!(
618 val,
619 PropertyValue::Enumerated(ObjectType::PULSE_CONVERTER.to_raw())
620 );
621 }
622
623 #[test]
624 fn pulse_converter_write_wrong_type_rejected() {
625 let mut pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
626 let result = pc.write_property(
627 PropertyIdentifier::PRESENT_VALUE,
628 None,
629 PropertyValue::Unsigned(42),
630 None,
631 );
632 assert!(result.is_err());
633 }
634
635 #[test]
636 fn pulse_converter_input_reference_defaults_null() {
637 let pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
638 assert_eq!(
639 pc.read_property(PropertyIdentifier::INPUT_REFERENCE, None)
640 .unwrap(),
641 PropertyValue::Null
642 );
643 }
644
645 #[test]
646 fn pulse_converter_set_input_reference() {
647 let mut pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
648 let oid = ObjectIdentifier::new(ObjectType::ACCUMULATOR, 1).unwrap();
649 let prop_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
650 pc.set_input_reference(BACnetObjectPropertyReference::new(oid, prop_raw));
651 let val = pc
652 .read_property(PropertyIdentifier::INPUT_REFERENCE, None)
653 .unwrap();
654 assert_eq!(
655 val,
656 PropertyValue::List(vec![
657 PropertyValue::ObjectIdentifier(oid),
658 PropertyValue::Enumerated(prop_raw),
659 ])
660 );
661 }
662
663 #[test]
664 fn pulse_converter_property_list() {
665 let pc = PulseConverterObject::new(1, "PC-1", 62).unwrap();
666 let list = pc.property_list();
667 assert!(list.contains(&PropertyIdentifier::PRESENT_VALUE));
668 assert!(list.contains(&PropertyIdentifier::SCALE_FACTOR));
669 assert!(list.contains(&PropertyIdentifier::ADJUST_VALUE));
670 assert!(list.contains(&PropertyIdentifier::COV_INCREMENT));
671 assert!(list.contains(&PropertyIdentifier::INPUT_REFERENCE));
672 }
673}