1use bacnet_types::constructed::BACnetObjectPropertyReference;
7use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType, PropertyIdentifier};
8use bacnet_types::error::Error;
9use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
10use std::borrow::Cow;
11
12use crate::common::{self, read_property_list_property};
13use crate::traits::BACnetObject;
14
15pub struct LoopObject {
17 oid: ObjectIdentifier,
18 name: String,
19 description: String,
20 present_value: f32,
21 setpoint: f32,
22 proportional_constant: f32,
23 integral_constant: f32,
24 derivative_constant: f32,
25 output_units: u32,
26 update_interval: u32,
27 out_of_service: bool,
28 reliability: u32,
29 reliability_before_out_of_service: Option<u32>,
32 status_flags: StatusFlags,
33 controlled_variable_reference: Option<BACnetObjectPropertyReference>,
34 manipulated_variable_reference: Option<BACnetObjectPropertyReference>,
35 setpoint_reference: Option<BACnetObjectPropertyReference>,
36}
37
38impl LoopObject {
39 pub fn new(instance: u32, name: impl Into<String>, output_units: u32) -> Result<Self, Error> {
40 let oid = ObjectIdentifier::new(ObjectType::LOOP, instance)?;
41 Ok(Self {
42 oid,
43 name: name.into(),
44 description: String::new(),
45 present_value: 0.0,
46 setpoint: 0.0,
47 proportional_constant: 1.0,
48 integral_constant: 0.0,
49 derivative_constant: 0.0,
50 output_units,
51 update_interval: 1000, out_of_service: false,
53 reliability: 0,
54 reliability_before_out_of_service: None,
55 status_flags: StatusFlags::empty(),
56 controlled_variable_reference: None,
57 manipulated_variable_reference: None,
58 setpoint_reference: None,
59 })
60 }
61
62 pub fn set_present_value(&mut self, value: f32) {
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_controlled_variable_reference(&mut self, r: BACnetObjectPropertyReference) {
75 self.controlled_variable_reference = Some(r);
76 }
77
78 pub fn set_manipulated_variable_reference(&mut self, r: BACnetObjectPropertyReference) {
81 self.manipulated_variable_reference = Some(r);
82 }
83
84 pub fn set_setpoint_reference(&mut self, r: BACnetObjectPropertyReference) {
87 self.setpoint_reference = Some(r);
88 }
89}
90
91impl BACnetObject for LoopObject {
92 fn object_identifier(&self) -> ObjectIdentifier {
93 self.oid
94 }
95
96 fn object_name(&self) -> &str {
97 &self.name
98 }
99
100 fn read_property(
101 &self,
102 property: PropertyIdentifier,
103 array_index: Option<u32>,
104 ) -> Result<PropertyValue, Error> {
105 match property {
106 p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
107 Ok(PropertyValue::ObjectIdentifier(self.oid))
108 }
109 p if p == PropertyIdentifier::OBJECT_NAME => {
110 Ok(PropertyValue::CharacterString(self.name.clone()))
111 }
112 p if p == PropertyIdentifier::DESCRIPTION => {
113 Ok(PropertyValue::CharacterString(self.description.clone()))
114 }
115 p if p == PropertyIdentifier::OBJECT_TYPE => {
116 Ok(PropertyValue::Enumerated(ObjectType::LOOP.to_raw()))
117 }
118 p if p == PropertyIdentifier::PRESENT_VALUE => {
119 Ok(PropertyValue::Real(self.present_value))
120 }
121 p if p == PropertyIdentifier::SETPOINT => Ok(PropertyValue::Real(self.setpoint)),
122 p if p == PropertyIdentifier::PROPORTIONAL_CONSTANT => {
123 Ok(PropertyValue::Real(self.proportional_constant))
124 }
125 p if p == PropertyIdentifier::INTEGRAL_CONSTANT => {
126 Ok(PropertyValue::Real(self.integral_constant))
127 }
128 p if p == PropertyIdentifier::DERIVATIVE_CONSTANT => {
129 Ok(PropertyValue::Real(self.derivative_constant))
130 }
131 p if p == PropertyIdentifier::OUTPUT_UNITS => {
132 Ok(PropertyValue::Enumerated(self.output_units))
133 }
134 p if p == PropertyIdentifier::UPDATE_INTERVAL => {
135 Ok(PropertyValue::Unsigned(self.update_interval as u64))
136 }
137 p if p == PropertyIdentifier::STATUS_FLAGS => Ok(PropertyValue::BitString {
138 unused_bits: 4,
139 data: vec![self.status_flags.bits() << 4],
140 }),
141 p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(0)),
142 p if p == PropertyIdentifier::RELIABILITY => {
143 Ok(PropertyValue::Enumerated(self.reliability))
144 }
145 p if p == PropertyIdentifier::OUT_OF_SERVICE => {
146 Ok(PropertyValue::Boolean(self.out_of_service))
147 }
148 p if p == PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE => Ok(
149 crate::reference::reference_read_value(&self.controlled_variable_reference),
150 ),
151 p if p == PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE => Ok(
152 crate::reference::reference_read_value(&self.manipulated_variable_reference),
153 ),
154 p if p == PropertyIdentifier::SETPOINT_REFERENCE => Ok(
155 crate::reference::reference_read_value(&self.setpoint_reference),
156 ),
157 p if p == PropertyIdentifier::PROPERTY_LIST => {
158 read_property_list_property(&self.property_list(), array_index)
159 }
160 _ => Err(Error::Protocol {
161 class: ErrorClass::PROPERTY.to_raw() as u32,
162 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
163 }),
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 let Some(result) = common::write_out_of_service_with_reliability_restore(
175 &mut self.out_of_service,
176 &mut self.reliability,
177 &mut self.reliability_before_out_of_service,
178 property,
179 &value,
180 ) {
181 return result;
182 }
183 match property {
184 p if p == PropertyIdentifier::SETPOINT => {
185 if let PropertyValue::Real(v) = value {
186 common::reject_non_finite(v)?;
187 self.setpoint = v;
188 return Ok(());
189 }
190 Err(Error::Protocol {
191 class: ErrorClass::PROPERTY.to_raw() as u32,
192 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
193 })
194 }
195 p if p == PropertyIdentifier::PROPORTIONAL_CONSTANT => {
196 if let PropertyValue::Real(v) = value {
197 common::reject_non_finite(v)?;
198 self.proportional_constant = v;
199 return Ok(());
200 }
201 Err(Error::Protocol {
202 class: ErrorClass::PROPERTY.to_raw() as u32,
203 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
204 })
205 }
206 p if p == PropertyIdentifier::INTEGRAL_CONSTANT => {
207 if let PropertyValue::Real(v) = value {
208 common::reject_non_finite(v)?;
209 self.integral_constant = v;
210 return Ok(());
211 }
212 Err(Error::Protocol {
213 class: ErrorClass::PROPERTY.to_raw() as u32,
214 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
215 })
216 }
217 p if p == PropertyIdentifier::DERIVATIVE_CONSTANT => {
218 if let PropertyValue::Real(v) = value {
219 common::reject_non_finite(v)?;
220 self.derivative_constant = v;
221 return Ok(());
222 }
223 Err(Error::Protocol {
224 class: ErrorClass::PROPERTY.to_raw() as u32,
225 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
226 })
227 }
228 p if p == PropertyIdentifier::UPDATE_INTERVAL => {
229 if let PropertyValue::Unsigned(v) = value {
230 self.update_interval = common::u64_to_u32(v)?;
231 return Ok(());
232 }
233 Err(Error::Protocol {
234 class: ErrorClass::PROPERTY.to_raw() as u32,
235 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
236 })
237 }
238 p if p == PropertyIdentifier::RELIABILITY => {
250 if !self.out_of_service {
251 return Err(common::write_access_denied_error());
252 }
253 if let PropertyValue::Enumerated(v) = value {
254 if !common::is_reliability_value_valid(v) {
255 return Err(common::value_out_of_range_error());
256 }
257 self.reliability = v;
258 return Ok(());
259 }
260 Err(Error::Protocol {
261 class: ErrorClass::PROPERTY.to_raw() as u32,
262 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
263 })
264 }
265 p if p == PropertyIdentifier::DESCRIPTION => {
266 if let PropertyValue::CharacterString(s) = value {
267 self.description = s;
268 return Ok(());
269 }
270 Err(Error::Protocol {
271 class: ErrorClass::PROPERTY.to_raw() as u32,
272 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
273 })
274 }
275 p if p == PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE => {
280 self.controlled_variable_reference = crate::reference::decode_reference_write(
281 &value,
282 crate::reference::ReferenceFrame::Bare,
283 )?;
284 Ok(())
285 }
286 p if p == PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE => {
287 self.manipulated_variable_reference = crate::reference::decode_reference_write(
288 &value,
289 crate::reference::ReferenceFrame::Bare,
290 )?;
291 Ok(())
292 }
293 p if p == PropertyIdentifier::SETPOINT_REFERENCE => {
297 self.setpoint_reference = crate::reference::decode_reference_write(
298 &value,
299 crate::reference::ReferenceFrame::Setpoint,
300 )?;
301 Ok(())
302 }
303 _ => Err(Error::Protocol {
304 class: ErrorClass::PROPERTY.to_raw() as u32,
305 code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
306 }),
307 }
308 }
309
310 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
311 static PROPS: &[PropertyIdentifier] = &[
312 PropertyIdentifier::OBJECT_IDENTIFIER,
313 PropertyIdentifier::OBJECT_NAME,
314 PropertyIdentifier::DESCRIPTION,
315 PropertyIdentifier::OBJECT_TYPE,
316 PropertyIdentifier::PRESENT_VALUE,
317 PropertyIdentifier::SETPOINT,
318 PropertyIdentifier::PROPORTIONAL_CONSTANT,
319 PropertyIdentifier::INTEGRAL_CONSTANT,
320 PropertyIdentifier::DERIVATIVE_CONSTANT,
321 PropertyIdentifier::OUTPUT_UNITS,
322 PropertyIdentifier::UPDATE_INTERVAL,
323 PropertyIdentifier::STATUS_FLAGS,
324 PropertyIdentifier::EVENT_STATE,
325 PropertyIdentifier::RELIABILITY,
326 PropertyIdentifier::OUT_OF_SERVICE,
327 PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE,
328 PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE,
329 PropertyIdentifier::SETPOINT_REFERENCE,
330 ];
331 Cow::Borrowed(PROPS)
332 }
333
334 fn supports_cov(&self) -> bool {
335 true
336 }
337
338 fn set_reliability_internal(&mut self, reliability: u32) -> Result<(), Error> {
339 if self.out_of_service {
344 return Err(common::write_access_denied_error());
345 }
346 if !common::is_reliability_value_valid(reliability) {
347 return Err(common::value_out_of_range_error());
348 }
349 self.reliability = reliability;
350 Ok(())
351 }
352
353 fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
354 matches!(
357 property,
358 PropertyIdentifier::SETPOINT
359 | PropertyIdentifier::PROPORTIONAL_CONSTANT
360 | PropertyIdentifier::INTEGRAL_CONSTANT
361 | PropertyIdentifier::DERIVATIVE_CONSTANT
362 | PropertyIdentifier::UPDATE_INTERVAL
363 | PropertyIdentifier::RELIABILITY
364 | PropertyIdentifier::OUT_OF_SERVICE
365 | PropertyIdentifier::DESCRIPTION
366 | PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE
367 | PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE
368 | PropertyIdentifier::SETPOINT_REFERENCE
369 )
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn loop_read_defaults() {
379 let lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
380 assert_eq!(
381 lo.read_property(PropertyIdentifier::PRESENT_VALUE, None)
382 .unwrap(),
383 PropertyValue::Real(0.0)
384 );
385 assert_eq!(
386 lo.read_property(PropertyIdentifier::SETPOINT, None)
387 .unwrap(),
388 PropertyValue::Real(0.0)
389 );
390 assert_eq!(
391 lo.read_property(PropertyIdentifier::PROPORTIONAL_CONSTANT, None)
392 .unwrap(),
393 PropertyValue::Real(1.0)
394 );
395 }
396
397 #[test]
398 fn loop_write_pid_constants() {
399 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
400 lo.write_property(
401 PropertyIdentifier::SETPOINT,
402 None,
403 PropertyValue::Real(72.0),
404 None,
405 )
406 .unwrap();
407 lo.write_property(
408 PropertyIdentifier::PROPORTIONAL_CONSTANT,
409 None,
410 PropertyValue::Real(2.5),
411 None,
412 )
413 .unwrap();
414 lo.write_property(
415 PropertyIdentifier::INTEGRAL_CONSTANT,
416 None,
417 PropertyValue::Real(0.1),
418 None,
419 )
420 .unwrap();
421 lo.write_property(
422 PropertyIdentifier::DERIVATIVE_CONSTANT,
423 None,
424 PropertyValue::Real(0.05),
425 None,
426 )
427 .unwrap();
428
429 assert_eq!(
430 lo.read_property(PropertyIdentifier::SETPOINT, None)
431 .unwrap(),
432 PropertyValue::Real(72.0)
433 );
434 assert_eq!(
435 lo.read_property(PropertyIdentifier::PROPORTIONAL_CONSTANT, None)
436 .unwrap(),
437 PropertyValue::Real(2.5)
438 );
439 assert_eq!(
440 lo.read_property(PropertyIdentifier::INTEGRAL_CONSTANT, None)
441 .unwrap(),
442 PropertyValue::Real(0.1)
443 );
444 assert_eq!(
445 lo.read_property(PropertyIdentifier::DERIVATIVE_CONSTANT, None)
446 .unwrap(),
447 PropertyValue::Real(0.05)
448 );
449 }
450
451 #[test]
452 fn loop_set_present_value() {
453 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
454 lo.set_present_value(55.0);
455 assert_eq!(
456 lo.read_property(PropertyIdentifier::PRESENT_VALUE, None)
457 .unwrap(),
458 PropertyValue::Real(55.0)
459 );
460 }
461
462 #[test]
463 fn loop_read_object_type() {
464 let lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
465 let val = lo
466 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
467 .unwrap();
468 assert_eq!(val, PropertyValue::Enumerated(ObjectType::LOOP.to_raw()));
469 }
470
471 #[test]
472 fn loop_write_wrong_type_rejected() {
473 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
474 let result = lo.write_property(
475 PropertyIdentifier::SETPOINT,
476 None,
477 PropertyValue::Unsigned(72),
478 None,
479 );
480 assert!(result.is_err());
481 }
482
483 #[test]
486 fn loop_references_default_to_null() {
487 let lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
488 assert_eq!(
489 lo.read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
490 .unwrap(),
491 PropertyValue::Null
492 );
493 assert_eq!(
494 lo.read_property(PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE, None)
495 .unwrap(),
496 PropertyValue::Null
497 );
498 assert_eq!(
499 lo.read_property(PropertyIdentifier::SETPOINT_REFERENCE, None)
500 .unwrap(),
501 PropertyValue::Null
502 );
503 }
504
505 #[test]
506 fn loop_set_references_read_back() {
507 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 5).unwrap();
508 let prop_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
509 let expect = PropertyValue::List(vec![
510 PropertyValue::ObjectIdentifier(oid),
511 PropertyValue::Enumerated(prop_raw),
512 ]);
513 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
514 lo.set_controlled_variable_reference(BACnetObjectPropertyReference::new(oid, prop_raw));
515 lo.set_manipulated_variable_reference(BACnetObjectPropertyReference::new(oid, prop_raw));
516 lo.set_setpoint_reference(BACnetObjectPropertyReference::new(oid, prop_raw));
517 for property in [
518 PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE,
519 PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE,
520 PropertyIdentifier::SETPOINT_REFERENCE,
521 ] {
522 assert_eq!(lo.read_property(property, None).unwrap(), expect);
523 }
524 }
525
526 #[test]
527 fn loop_references_in_property_list() {
528 let lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
529 let list = lo.property_list();
530 assert!(list.contains(&PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE));
531 assert!(list.contains(&PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE));
532 assert!(list.contains(&PropertyIdentifier::SETPOINT_REFERENCE));
533 }
534
535 #[test]
536 fn loop_write_reference_via_write_property() {
537 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
538 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 7).unwrap();
539 let prop_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
540
541 lo.write_property(
542 PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE,
543 None,
544 PropertyValue::List(vec![
545 PropertyValue::ObjectIdentifier(oid),
546 PropertyValue::Enumerated(prop_raw),
547 ]),
548 None,
549 )
550 .unwrap();
551
552 assert_eq!(
553 lo.read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
554 .unwrap(),
555 PropertyValue::List(vec![
556 PropertyValue::ObjectIdentifier(oid),
557 PropertyValue::Enumerated(prop_raw),
558 ])
559 );
560 }
561
562 #[test]
563 fn loop_write_null_clears_reference() {
564 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
565 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
566 lo.set_controlled_variable_reference(BACnetObjectPropertyReference::new(
567 oid,
568 PropertyIdentifier::PRESENT_VALUE.to_raw(),
569 ));
570
571 assert_ne!(
573 lo.read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
574 .unwrap(),
575 PropertyValue::Null
576 );
577
578 lo.write_property(
580 PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE,
581 None,
582 PropertyValue::Null,
583 None,
584 )
585 .unwrap();
586
587 assert_eq!(
588 lo.read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
589 .unwrap(),
590 PropertyValue::Null
591 );
592 }
593
594 #[test]
595 fn loop_write_reference_wrong_type_rejected() {
596 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
597 let result = lo.write_property(
598 PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE,
599 None,
600 PropertyValue::Unsigned(42),
601 None,
602 );
603 assert!(result.is_err());
604 }
605
606 fn framed_reference(r: &BACnetObjectPropertyReference) -> PropertyValue {
609 let mut buf = bytes::BytesMut::new();
610 bacnet_encoding::constructed::encode_object_property_reference(&mut buf, r);
611 PropertyValue::ApplicationData(buf.to_vec())
612 }
613
614 #[test]
615 fn loop_write_framed_indexed_reference_reads_back_with_index() {
616 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
617 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 7).unwrap();
618 let r = BACnetObjectPropertyReference::new_indexed(
619 oid,
620 PropertyIdentifier::PRESENT_VALUE.to_raw(),
621 3,
622 );
623 lo.write_property(
624 PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE,
625 None,
626 framed_reference(&r),
627 None,
628 )
629 .unwrap();
630 assert_eq!(
632 lo.read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
633 .unwrap(),
634 PropertyValue::List(vec![
635 PropertyValue::ObjectIdentifier(oid),
636 PropertyValue::Enumerated(PropertyIdentifier::PRESENT_VALUE.to_raw()),
637 PropertyValue::Unsigned(3),
638 ])
639 );
640 }
641
642 #[test]
643 fn loop_write_setpoint_reference_accepts_bacnetsetpointreference_frame() {
644 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
645 let oid = ObjectIdentifier::new(ObjectType::ANALOG_VALUE, 10).unwrap();
646 let r = BACnetObjectPropertyReference::new(oid, PropertyIdentifier::PRESENT_VALUE.to_raw());
647 let mut buf = bytes::BytesMut::new();
648 bacnet_encoding::constructed::encode_setpoint_reference(&mut buf, &r);
649 lo.write_property(
650 PropertyIdentifier::SETPOINT_REFERENCE,
651 None,
652 PropertyValue::ApplicationData(buf.to_vec()),
653 None,
654 )
655 .unwrap();
656 assert_eq!(
657 lo.read_property(PropertyIdentifier::SETPOINT_REFERENCE, None)
658 .unwrap(),
659 PropertyValue::List(vec![
660 PropertyValue::ObjectIdentifier(oid),
661 PropertyValue::Enumerated(PropertyIdentifier::PRESENT_VALUE.to_raw()),
662 ])
663 );
664 }
665
666 #[test]
667 fn loop_write_device_qualified_reference_rejected_and_preserves() {
668 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
669 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 7).unwrap();
670 let prop_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
671 lo.set_controlled_variable_reference(BACnetObjectPropertyReference::new(oid, prop_raw));
672 let mut buf = bytes::BytesMut::new();
675 bacnet_encoding::constructed::encode_object_property_reference(
676 &mut buf,
677 &BACnetObjectPropertyReference::new(oid, prop_raw),
678 );
679 bacnet_encoding::primitives::encode_ctx_object_id(
680 &mut buf,
681 3,
682 &ObjectIdentifier::new(ObjectType::DEVICE, 77).unwrap(),
683 );
684 let err = lo
685 .write_property(
686 PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE,
687 None,
688 PropertyValue::ApplicationData(buf.to_vec()),
689 None,
690 )
691 .unwrap_err();
692 match err {
693 Error::Protocol { class, code } => {
694 assert_eq!(class, ErrorClass::PROPERTY.to_raw() as u32);
695 assert_eq!(code, ErrorCode::INVALID_DATA_ENCODING.to_raw() as u32);
696 }
697 other => panic!("expected PROPERTY/INVALID_DATA_ENCODING, got {other:?}"),
698 }
699 assert_eq!(
700 lo.read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
701 .unwrap(),
702 PropertyValue::List(vec![
703 PropertyValue::ObjectIdentifier(oid),
704 PropertyValue::Enumerated(prop_raw),
705 ])
706 );
707 }
708
709 #[test]
710 fn loop_write_empty_setpoint_frame_clears_reference() {
711 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
712 let oid = ObjectIdentifier::new(ObjectType::ANALOG_VALUE, 10).unwrap();
713 let r = BACnetObjectPropertyReference::new(oid, PropertyIdentifier::PRESENT_VALUE.to_raw());
714 let mut buf = bytes::BytesMut::new();
715 bacnet_encoding::constructed::encode_setpoint_reference(&mut buf, &r);
716 lo.write_property(
717 PropertyIdentifier::SETPOINT_REFERENCE,
718 None,
719 PropertyValue::ApplicationData(buf.to_vec()),
720 None,
721 )
722 .unwrap();
723 assert_ne!(
724 lo.read_property(PropertyIdentifier::SETPOINT_REFERENCE, None)
725 .unwrap(),
726 PropertyValue::Null
727 );
728
729 lo.write_property(
735 PropertyIdentifier::SETPOINT_REFERENCE,
736 None,
737 PropertyValue::ApplicationData(vec![0x0E, 0x0F]),
738 None,
739 )
740 .unwrap();
741 assert_eq!(
742 lo.read_property(PropertyIdentifier::SETPOINT_REFERENCE, None)
743 .unwrap(),
744 PropertyValue::Null
745 );
746 }
747
748 #[test]
749 fn loop_write_local_list_with_index_still_accepted() {
750 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
751 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 7).unwrap();
752 let prop_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
753 lo.write_property(
754 PropertyIdentifier::SETPOINT_REFERENCE,
755 None,
756 PropertyValue::List(vec![
757 PropertyValue::ObjectIdentifier(oid),
758 PropertyValue::Enumerated(prop_raw),
759 PropertyValue::Unsigned(9),
760 ]),
761 None,
762 )
763 .unwrap();
764 assert_eq!(
765 lo.read_property(PropertyIdentifier::SETPOINT_REFERENCE, None)
766 .unwrap(),
767 PropertyValue::List(vec![
768 PropertyValue::ObjectIdentifier(oid),
769 PropertyValue::Enumerated(prop_raw),
770 PropertyValue::Unsigned(9),
771 ])
772 );
773 }
774}