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
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[test]
377 fn loop_read_defaults() {
378 let lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
379 assert_eq!(
380 lo.read_property(PropertyIdentifier::PRESENT_VALUE, None)
381 .unwrap(),
382 PropertyValue::Real(0.0)
383 );
384 assert_eq!(
385 lo.read_property(PropertyIdentifier::SETPOINT, None)
386 .unwrap(),
387 PropertyValue::Real(0.0)
388 );
389 assert_eq!(
390 lo.read_property(PropertyIdentifier::PROPORTIONAL_CONSTANT, None)
391 .unwrap(),
392 PropertyValue::Real(1.0)
393 );
394 }
395
396 #[test]
397 fn loop_write_pid_constants() {
398 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
399 lo.write_property(
400 PropertyIdentifier::SETPOINT,
401 None,
402 PropertyValue::Real(72.0),
403 None,
404 )
405 .unwrap();
406 lo.write_property(
407 PropertyIdentifier::PROPORTIONAL_CONSTANT,
408 None,
409 PropertyValue::Real(2.5),
410 None,
411 )
412 .unwrap();
413 lo.write_property(
414 PropertyIdentifier::INTEGRAL_CONSTANT,
415 None,
416 PropertyValue::Real(0.1),
417 None,
418 )
419 .unwrap();
420 lo.write_property(
421 PropertyIdentifier::DERIVATIVE_CONSTANT,
422 None,
423 PropertyValue::Real(0.05),
424 None,
425 )
426 .unwrap();
427
428 assert_eq!(
429 lo.read_property(PropertyIdentifier::SETPOINT, None)
430 .unwrap(),
431 PropertyValue::Real(72.0)
432 );
433 assert_eq!(
434 lo.read_property(PropertyIdentifier::PROPORTIONAL_CONSTANT, None)
435 .unwrap(),
436 PropertyValue::Real(2.5)
437 );
438 assert_eq!(
439 lo.read_property(PropertyIdentifier::INTEGRAL_CONSTANT, None)
440 .unwrap(),
441 PropertyValue::Real(0.1)
442 );
443 assert_eq!(
444 lo.read_property(PropertyIdentifier::DERIVATIVE_CONSTANT, None)
445 .unwrap(),
446 PropertyValue::Real(0.05)
447 );
448 }
449
450 #[test]
451 fn loop_set_present_value() {
452 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
453 lo.set_present_value(55.0);
454 assert_eq!(
455 lo.read_property(PropertyIdentifier::PRESENT_VALUE, None)
456 .unwrap(),
457 PropertyValue::Real(55.0)
458 );
459 }
460
461 #[test]
462 fn loop_read_object_type() {
463 let lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
464 let val = lo
465 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
466 .unwrap();
467 assert_eq!(val, PropertyValue::Enumerated(ObjectType::LOOP.to_raw()));
468 }
469
470 #[test]
471 fn loop_write_wrong_type_rejected() {
472 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
473 let result = lo.write_property(
474 PropertyIdentifier::SETPOINT,
475 None,
476 PropertyValue::Unsigned(72),
477 None,
478 );
479 assert!(result.is_err());
480 }
481
482 #[test]
485 fn loop_references_default_to_null() {
486 let lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
487 assert_eq!(
488 lo.read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
489 .unwrap(),
490 PropertyValue::Null
491 );
492 assert_eq!(
493 lo.read_property(PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE, None)
494 .unwrap(),
495 PropertyValue::Null
496 );
497 assert_eq!(
498 lo.read_property(PropertyIdentifier::SETPOINT_REFERENCE, None)
499 .unwrap(),
500 PropertyValue::Null
501 );
502 }
503
504 #[test]
505 fn loop_set_controlled_variable_reference_read_back() {
506 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
507 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 5).unwrap();
508 let prop_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
509 lo.set_controlled_variable_reference(BACnetObjectPropertyReference::new(oid, prop_raw));
510
511 let val = lo
512 .read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
513 .unwrap();
514 assert_eq!(
515 val,
516 PropertyValue::List(vec![
517 PropertyValue::ObjectIdentifier(oid),
518 PropertyValue::Enumerated(prop_raw),
519 ])
520 );
521 }
522
523 #[test]
524 fn loop_set_manipulated_variable_reference_read_back() {
525 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
526 let oid = ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 3).unwrap();
527 let prop_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
528 lo.set_manipulated_variable_reference(BACnetObjectPropertyReference::new(oid, prop_raw));
529
530 let val = lo
531 .read_property(PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE, None)
532 .unwrap();
533 assert_eq!(
534 val,
535 PropertyValue::List(vec![
536 PropertyValue::ObjectIdentifier(oid),
537 PropertyValue::Enumerated(prop_raw),
538 ])
539 );
540 }
541
542 #[test]
543 fn loop_set_setpoint_reference_read_back() {
544 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
545 let oid = ObjectIdentifier::new(ObjectType::ANALOG_VALUE, 10).unwrap();
546 let prop_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
547 lo.set_setpoint_reference(BACnetObjectPropertyReference::new(oid, prop_raw));
548
549 let val = lo
550 .read_property(PropertyIdentifier::SETPOINT_REFERENCE, None)
551 .unwrap();
552 assert_eq!(
553 val,
554 PropertyValue::List(vec![
555 PropertyValue::ObjectIdentifier(oid),
556 PropertyValue::Enumerated(prop_raw),
557 ])
558 );
559 }
560
561 #[test]
562 fn loop_references_in_property_list() {
563 let lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
564 let list = lo.property_list();
565 assert!(list.contains(&PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE));
566 assert!(list.contains(&PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE));
567 assert!(list.contains(&PropertyIdentifier::SETPOINT_REFERENCE));
568 }
569
570 #[test]
571 fn loop_write_reference_via_write_property() {
572 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
573 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 7).unwrap();
574 let prop_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
575
576 lo.write_property(
577 PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE,
578 None,
579 PropertyValue::List(vec![
580 PropertyValue::ObjectIdentifier(oid),
581 PropertyValue::Enumerated(prop_raw),
582 ]),
583 None,
584 )
585 .unwrap();
586
587 assert_eq!(
588 lo.read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
589 .unwrap(),
590 PropertyValue::List(vec![
591 PropertyValue::ObjectIdentifier(oid),
592 PropertyValue::Enumerated(prop_raw),
593 ])
594 );
595 }
596
597 #[test]
598 fn loop_write_null_clears_reference() {
599 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
600 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
601 lo.set_controlled_variable_reference(BACnetObjectPropertyReference::new(
602 oid,
603 PropertyIdentifier::PRESENT_VALUE.to_raw(),
604 ));
605
606 assert_ne!(
608 lo.read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
609 .unwrap(),
610 PropertyValue::Null
611 );
612
613 lo.write_property(
615 PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE,
616 None,
617 PropertyValue::Null,
618 None,
619 )
620 .unwrap();
621
622 assert_eq!(
623 lo.read_property(PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE, None)
624 .unwrap(),
625 PropertyValue::Null
626 );
627 }
628
629 #[test]
630 fn loop_write_reference_wrong_type_rejected() {
631 let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
632 let result = lo.write_property(
633 PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE,
634 None,
635 PropertyValue::Unsigned(42),
636 None,
637 );
638 assert!(result.is_err());
639 }
640}