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 status_flags: StatusFlags,
30 controlled_variable_reference: Option<BACnetObjectPropertyReference>,
31 manipulated_variable_reference: Option<BACnetObjectPropertyReference>,
32 setpoint_reference: Option<BACnetObjectPropertyReference>,
33}
34
35impl LoopObject {
36 pub fn new(instance: u32, name: impl Into<String>, output_units: u32) -> Result<Self, Error> {
37 let oid = ObjectIdentifier::new(ObjectType::LOOP, instance)?;
38 Ok(Self {
39 oid,
40 name: name.into(),
41 description: String::new(),
42 present_value: 0.0,
43 setpoint: 0.0,
44 proportional_constant: 1.0,
45 integral_constant: 0.0,
46 derivative_constant: 0.0,
47 output_units,
48 update_interval: 1000, out_of_service: false,
50 reliability: 0,
51 status_flags: StatusFlags::empty(),
52 controlled_variable_reference: None,
53 manipulated_variable_reference: None,
54 setpoint_reference: None,
55 })
56 }
57
58 pub fn set_present_value(&mut self, value: f32) {
60 self.present_value = value;
61 }
62
63 pub fn set_description(&mut self, desc: impl Into<String>) {
65 self.description = desc.into();
66 }
67
68 pub fn set_controlled_variable_reference(&mut self, r: BACnetObjectPropertyReference) {
71 self.controlled_variable_reference = Some(r);
72 }
73
74 pub fn set_manipulated_variable_reference(&mut self, r: BACnetObjectPropertyReference) {
77 self.manipulated_variable_reference = Some(r);
78 }
79
80 pub fn set_setpoint_reference(&mut self, r: BACnetObjectPropertyReference) {
83 self.setpoint_reference = Some(r);
84 }
85}
86
87impl BACnetObject for LoopObject {
88 fn object_identifier(&self) -> ObjectIdentifier {
89 self.oid
90 }
91
92 fn object_name(&self) -> &str {
93 &self.name
94 }
95
96 fn read_property(
97 &self,
98 property: PropertyIdentifier,
99 array_index: Option<u32>,
100 ) -> Result<PropertyValue, Error> {
101 match property {
102 p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
103 Ok(PropertyValue::ObjectIdentifier(self.oid))
104 }
105 p if p == PropertyIdentifier::OBJECT_NAME => {
106 Ok(PropertyValue::CharacterString(self.name.clone()))
107 }
108 p if p == PropertyIdentifier::DESCRIPTION => {
109 Ok(PropertyValue::CharacterString(self.description.clone()))
110 }
111 p if p == PropertyIdentifier::OBJECT_TYPE => {
112 Ok(PropertyValue::Enumerated(ObjectType::LOOP.to_raw()))
113 }
114 p if p == PropertyIdentifier::PRESENT_VALUE => {
115 Ok(PropertyValue::Real(self.present_value))
116 }
117 p if p == PropertyIdentifier::SETPOINT => Ok(PropertyValue::Real(self.setpoint)),
118 p if p == PropertyIdentifier::PROPORTIONAL_CONSTANT => {
119 Ok(PropertyValue::Real(self.proportional_constant))
120 }
121 p if p == PropertyIdentifier::INTEGRAL_CONSTANT => {
122 Ok(PropertyValue::Real(self.integral_constant))
123 }
124 p if p == PropertyIdentifier::DERIVATIVE_CONSTANT => {
125 Ok(PropertyValue::Real(self.derivative_constant))
126 }
127 p if p == PropertyIdentifier::OUTPUT_UNITS => {
128 Ok(PropertyValue::Enumerated(self.output_units))
129 }
130 p if p == PropertyIdentifier::UPDATE_INTERVAL => {
131 Ok(PropertyValue::Unsigned(self.update_interval as u64))
132 }
133 p if p == PropertyIdentifier::STATUS_FLAGS => Ok(PropertyValue::BitString {
134 unused_bits: 4,
135 data: vec![self.status_flags.bits() << 4],
136 }),
137 p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(0)),
138 p if p == PropertyIdentifier::RELIABILITY => {
139 Ok(PropertyValue::Enumerated(self.reliability))
140 }
141 p if p == PropertyIdentifier::OUT_OF_SERVICE => {
142 Ok(PropertyValue::Boolean(self.out_of_service))
143 }
144 p if p == PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE => {
145 match &self.controlled_variable_reference {
146 Some(r) => Ok(PropertyValue::List(vec![
147 PropertyValue::ObjectIdentifier(r.object_identifier),
148 PropertyValue::Enumerated(r.property_identifier),
149 ])),
150 None => Ok(PropertyValue::Null),
151 }
152 }
153 p if p == PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE => {
154 match &self.manipulated_variable_reference {
155 Some(r) => Ok(PropertyValue::List(vec![
156 PropertyValue::ObjectIdentifier(r.object_identifier),
157 PropertyValue::Enumerated(r.property_identifier),
158 ])),
159 None => Ok(PropertyValue::Null),
160 }
161 }
162 p if p == PropertyIdentifier::SETPOINT_REFERENCE => match &self.setpoint_reference {
163 Some(r) => Ok(PropertyValue::List(vec![
164 PropertyValue::ObjectIdentifier(r.object_identifier),
165 PropertyValue::Enumerated(r.property_identifier),
166 ])),
167 None => Ok(PropertyValue::Null),
168 },
169 p if p == PropertyIdentifier::PROPERTY_LIST => {
170 read_property_list_property(&self.property_list(), array_index)
171 }
172 _ => Err(Error::Protocol {
173 class: ErrorClass::PROPERTY.to_raw() as u32,
174 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
175 }),
176 }
177 }
178
179 fn write_property(
180 &mut self,
181 property: PropertyIdentifier,
182 _array_index: Option<u32>,
183 value: PropertyValue,
184 _priority: Option<u8>,
185 ) -> Result<(), Error> {
186 match property {
187 p if p == PropertyIdentifier::SETPOINT => {
188 if let PropertyValue::Real(v) = value {
189 common::reject_non_finite(v)?;
190 self.setpoint = v;
191 return Ok(());
192 }
193 Err(Error::Protocol {
194 class: ErrorClass::PROPERTY.to_raw() as u32,
195 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
196 })
197 }
198 p if p == PropertyIdentifier::PROPORTIONAL_CONSTANT => {
199 if let PropertyValue::Real(v) = value {
200 common::reject_non_finite(v)?;
201 self.proportional_constant = v;
202 return Ok(());
203 }
204 Err(Error::Protocol {
205 class: ErrorClass::PROPERTY.to_raw() as u32,
206 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
207 })
208 }
209 p if p == PropertyIdentifier::INTEGRAL_CONSTANT => {
210 if let PropertyValue::Real(v) = value {
211 common::reject_non_finite(v)?;
212 self.integral_constant = v;
213 return Ok(());
214 }
215 Err(Error::Protocol {
216 class: ErrorClass::PROPERTY.to_raw() as u32,
217 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
218 })
219 }
220 p if p == PropertyIdentifier::DERIVATIVE_CONSTANT => {
221 if let PropertyValue::Real(v) = value {
222 common::reject_non_finite(v)?;
223 self.derivative_constant = v;
224 return Ok(());
225 }
226 Err(Error::Protocol {
227 class: ErrorClass::PROPERTY.to_raw() as u32,
228 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
229 })
230 }
231 p if p == PropertyIdentifier::UPDATE_INTERVAL => {
232 if let PropertyValue::Unsigned(v) = value {
233 self.update_interval = common::u64_to_u32(v)?;
234 return Ok(());
235 }
236 Err(Error::Protocol {
237 class: ErrorClass::PROPERTY.to_raw() as u32,
238 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
239 })
240 }
241 p if p == PropertyIdentifier::RELIABILITY => {
242 if let PropertyValue::Enumerated(v) = value {
243 self.reliability = v;
244 return Ok(());
245 }
246 Err(Error::Protocol {
247 class: ErrorClass::PROPERTY.to_raw() as u32,
248 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
249 })
250 }
251 p if p == PropertyIdentifier::OUT_OF_SERVICE => {
252 if let PropertyValue::Boolean(v) = value {
253 self.out_of_service = v;
254 return Ok(());
255 }
256 Err(Error::Protocol {
257 class: ErrorClass::PROPERTY.to_raw() as u32,
258 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
259 })
260 }
261 p if p == PropertyIdentifier::DESCRIPTION => {
262 if let PropertyValue::CharacterString(s) = value {
263 self.description = s;
264 return Ok(());
265 }
266 Err(Error::Protocol {
267 class: ErrorClass::PROPERTY.to_raw() as u32,
268 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
269 })
270 }
271 p if p == PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE => match value {
272 PropertyValue::Null => {
273 self.controlled_variable_reference = None;
274 Ok(())
275 }
276 PropertyValue::List(ref items) if items.len() >= 2 => {
277 if let (PropertyValue::ObjectIdentifier(oid), PropertyValue::Enumerated(prop)) =
278 (&items[0], &items[1])
279 {
280 self.controlled_variable_reference =
281 Some(BACnetObjectPropertyReference::new(*oid, *prop));
282 return Ok(());
283 }
284 Err(Error::Protocol {
285 class: ErrorClass::PROPERTY.to_raw() as u32,
286 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
287 })
288 }
289 _ => Err(Error::Protocol {
290 class: ErrorClass::PROPERTY.to_raw() as u32,
291 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
292 }),
293 },
294 p if p == PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE => match value {
295 PropertyValue::Null => {
296 self.manipulated_variable_reference = None;
297 Ok(())
298 }
299 PropertyValue::List(ref items) if items.len() >= 2 => {
300 if let (PropertyValue::ObjectIdentifier(oid), PropertyValue::Enumerated(prop)) =
301 (&items[0], &items[1])
302 {
303 self.manipulated_variable_reference =
304 Some(BACnetObjectPropertyReference::new(*oid, *prop));
305 return Ok(());
306 }
307 Err(Error::Protocol {
308 class: ErrorClass::PROPERTY.to_raw() as u32,
309 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
310 })
311 }
312 _ => Err(Error::Protocol {
313 class: ErrorClass::PROPERTY.to_raw() as u32,
314 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
315 }),
316 },
317 p if p == PropertyIdentifier::SETPOINT_REFERENCE => match value {
318 PropertyValue::Null => {
319 self.setpoint_reference = None;
320 Ok(())
321 }
322 PropertyValue::List(ref items) if items.len() >= 2 => {
323 if let (PropertyValue::ObjectIdentifier(oid), PropertyValue::Enumerated(prop)) =
324 (&items[0], &items[1])
325 {
326 self.setpoint_reference =
327 Some(BACnetObjectPropertyReference::new(*oid, *prop));
328 return Ok(());
329 }
330 Err(Error::Protocol {
331 class: ErrorClass::PROPERTY.to_raw() as u32,
332 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
333 })
334 }
335 _ => Err(Error::Protocol {
336 class: ErrorClass::PROPERTY.to_raw() as u32,
337 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
338 }),
339 },
340 _ => Err(Error::Protocol {
341 class: ErrorClass::PROPERTY.to_raw() as u32,
342 code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
343 }),
344 }
345 }
346
347 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
348 static PROPS: &[PropertyIdentifier] = &[
349 PropertyIdentifier::OBJECT_IDENTIFIER,
350 PropertyIdentifier::OBJECT_NAME,
351 PropertyIdentifier::DESCRIPTION,
352 PropertyIdentifier::OBJECT_TYPE,
353 PropertyIdentifier::PRESENT_VALUE,
354 PropertyIdentifier::SETPOINT,
355 PropertyIdentifier::PROPORTIONAL_CONSTANT,
356 PropertyIdentifier::INTEGRAL_CONSTANT,
357 PropertyIdentifier::DERIVATIVE_CONSTANT,
358 PropertyIdentifier::OUTPUT_UNITS,
359 PropertyIdentifier::UPDATE_INTERVAL,
360 PropertyIdentifier::STATUS_FLAGS,
361 PropertyIdentifier::EVENT_STATE,
362 PropertyIdentifier::RELIABILITY,
363 PropertyIdentifier::OUT_OF_SERVICE,
364 PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE,
365 PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE,
366 PropertyIdentifier::SETPOINT_REFERENCE,
367 ];
368 Cow::Borrowed(PROPS)
369 }
370
371 fn supports_cov(&self) -> bool {
372 true
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379
380 #[test]
381 fn loop_read_defaults() {
382 let lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
383 assert_eq!(
384 lo.read_property(PropertyIdentifier::PRESENT_VALUE, None)
385 .unwrap(),
386 PropertyValue::Real(0.0)
387 );
388 assert_eq!(
389 lo.read_property(PropertyIdentifier::SETPOINT, None)
390 .unwrap(),
391 PropertyValue::Real(0.0)
392 );
393 assert_eq!(
394 lo.read_property(PropertyIdentifier::PROPORTIONAL_CONSTANT, None)
395 .unwrap(),
396 PropertyValue::Real(1.0)
397 );
398 }
399
400 #[test]
401 fn loop_write_pid_constants() {
402 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
403 lo.write_property(
404 PropertyIdentifier::SETPOINT,
405 None,
406 PropertyValue::Real(72.0),
407 None,
408 )
409 .unwrap();
410 lo.write_property(
411 PropertyIdentifier::PROPORTIONAL_CONSTANT,
412 None,
413 PropertyValue::Real(2.5),
414 None,
415 )
416 .unwrap();
417 lo.write_property(
418 PropertyIdentifier::INTEGRAL_CONSTANT,
419 None,
420 PropertyValue::Real(0.1),
421 None,
422 )
423 .unwrap();
424 lo.write_property(
425 PropertyIdentifier::DERIVATIVE_CONSTANT,
426 None,
427 PropertyValue::Real(0.05),
428 None,
429 )
430 .unwrap();
431
432 assert_eq!(
433 lo.read_property(PropertyIdentifier::SETPOINT, None)
434 .unwrap(),
435 PropertyValue::Real(72.0)
436 );
437 assert_eq!(
438 lo.read_property(PropertyIdentifier::PROPORTIONAL_CONSTANT, None)
439 .unwrap(),
440 PropertyValue::Real(2.5)
441 );
442 assert_eq!(
443 lo.read_property(PropertyIdentifier::INTEGRAL_CONSTANT, None)
444 .unwrap(),
445 PropertyValue::Real(0.1)
446 );
447 assert_eq!(
448 lo.read_property(PropertyIdentifier::DERIVATIVE_CONSTANT, None)
449 .unwrap(),
450 PropertyValue::Real(0.05)
451 );
452 }
453
454 #[test]
455 fn loop_set_present_value() {
456 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
457 lo.set_present_value(55.0);
458 assert_eq!(
459 lo.read_property(PropertyIdentifier::PRESENT_VALUE, None)
460 .unwrap(),
461 PropertyValue::Real(55.0)
462 );
463 }
464
465 #[test]
466 fn loop_read_object_type() {
467 let lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
468 let val = lo
469 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
470 .unwrap();
471 assert_eq!(val, PropertyValue::Enumerated(ObjectType::LOOP.to_raw()));
472 }
473
474 #[test]
475 fn loop_write_wrong_type_rejected() {
476 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
477 let result = lo.write_property(
478 PropertyIdentifier::SETPOINT,
479 None,
480 PropertyValue::Unsigned(72),
481 None,
482 );
483 assert!(result.is_err());
484 }
485
486 #[test]
489 fn loop_references_default_to_null() {
490 let lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
491 assert_eq!(
492 lo.read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
493 .unwrap(),
494 PropertyValue::Null
495 );
496 assert_eq!(
497 lo.read_property(PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE, None)
498 .unwrap(),
499 PropertyValue::Null
500 );
501 assert_eq!(
502 lo.read_property(PropertyIdentifier::SETPOINT_REFERENCE, None)
503 .unwrap(),
504 PropertyValue::Null
505 );
506 }
507
508 #[test]
509 fn loop_set_controlled_variable_reference_read_back() {
510 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
511 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 5).unwrap();
512 let prop_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
513 lo.set_controlled_variable_reference(BACnetObjectPropertyReference::new(oid, prop_raw));
514
515 let val = lo
516 .read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
517 .unwrap();
518 assert_eq!(
519 val,
520 PropertyValue::List(vec![
521 PropertyValue::ObjectIdentifier(oid),
522 PropertyValue::Enumerated(prop_raw),
523 ])
524 );
525 }
526
527 #[test]
528 fn loop_set_manipulated_variable_reference_read_back() {
529 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
530 let oid = ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 3).unwrap();
531 let prop_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
532 lo.set_manipulated_variable_reference(BACnetObjectPropertyReference::new(oid, prop_raw));
533
534 let val = lo
535 .read_property(PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE, None)
536 .unwrap();
537 assert_eq!(
538 val,
539 PropertyValue::List(vec![
540 PropertyValue::ObjectIdentifier(oid),
541 PropertyValue::Enumerated(prop_raw),
542 ])
543 );
544 }
545
546 #[test]
547 fn loop_set_setpoint_reference_read_back() {
548 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
549 let oid = ObjectIdentifier::new(ObjectType::ANALOG_VALUE, 10).unwrap();
550 let prop_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
551 lo.set_setpoint_reference(BACnetObjectPropertyReference::new(oid, prop_raw));
552
553 let val = lo
554 .read_property(PropertyIdentifier::SETPOINT_REFERENCE, None)
555 .unwrap();
556 assert_eq!(
557 val,
558 PropertyValue::List(vec![
559 PropertyValue::ObjectIdentifier(oid),
560 PropertyValue::Enumerated(prop_raw),
561 ])
562 );
563 }
564
565 #[test]
566 fn loop_references_in_property_list() {
567 let lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
568 let list = lo.property_list();
569 assert!(list.contains(&PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE));
570 assert!(list.contains(&PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE));
571 assert!(list.contains(&PropertyIdentifier::SETPOINT_REFERENCE));
572 }
573
574 #[test]
575 fn loop_write_reference_via_write_property() {
576 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
577 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 7).unwrap();
578 let prop_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
579
580 lo.write_property(
581 PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE,
582 None,
583 PropertyValue::List(vec![
584 PropertyValue::ObjectIdentifier(oid),
585 PropertyValue::Enumerated(prop_raw),
586 ]),
587 None,
588 )
589 .unwrap();
590
591 assert_eq!(
592 lo.read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
593 .unwrap(),
594 PropertyValue::List(vec![
595 PropertyValue::ObjectIdentifier(oid),
596 PropertyValue::Enumerated(prop_raw),
597 ])
598 );
599 }
600
601 #[test]
602 fn loop_write_null_clears_reference() {
603 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
604 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
605 lo.set_controlled_variable_reference(BACnetObjectPropertyReference::new(
606 oid,
607 PropertyIdentifier::PRESENT_VALUE.to_raw(),
608 ));
609
610 assert_ne!(
612 lo.read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
613 .unwrap(),
614 PropertyValue::Null
615 );
616
617 lo.write_property(
619 PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE,
620 None,
621 PropertyValue::Null,
622 None,
623 )
624 .unwrap();
625
626 assert_eq!(
627 lo.read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
628 .unwrap(),
629 PropertyValue::Null
630 );
631 }
632
633 #[test]
634 fn loop_write_reference_wrong_type_rejected() {
635 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
636 let result = lo.write_property(
637 PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE,
638 None,
639 PropertyValue::Unsigned(42),
640 None,
641 );
642 assert!(result.is_err());
643 }
644}