1use bacnet_types::constructed::BACnetObjectPropertyReference;
13use bacnet_types::enums::{
14 Action, EngineeringUnits, ErrorClass, ErrorCode, EventState, ObjectType, PropertyIdentifier,
15 Reliability,
16};
17use bacnet_types::error::Error;
18use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
19use std::borrow::Cow;
20
21use crate::common::{self, read_property_list_property};
22use crate::reference::{self, ReferenceFrame};
23use crate::traits::BACnetObject;
24
25mod metadata;
26
27pub struct LoopObject {
29 oid: ObjectIdentifier,
30 name: String,
31 description: String,
32 present_value: f32,
33 setpoint: f32,
34 controlled_variable_value: f32,
35 cov_increment: f32,
36 proportional_constant: f32,
37 integral_constant: f32,
38 derivative_constant: f32,
39 output_units: EngineeringUnits,
40 controlled_variable_units: EngineeringUnits,
41 proportional_constant_units: EngineeringUnits,
42 integral_constant_units: EngineeringUnits,
43 derivative_constant_units: EngineeringUnits,
44 action: Action,
45 priority_for_writing: u8,
46 update_interval: u32,
47 out_of_service: bool,
48 reliability: Reliability,
49 reliability_before_out_of_service: Option<Reliability>,
52 status_flags: StatusFlags,
53 controlled_variable_reference: Option<BACnetObjectPropertyReference>,
54 manipulated_variable_reference: Option<BACnetObjectPropertyReference>,
55 setpoint_reference: Option<BACnetObjectPropertyReference>,
56}
57
58impl LoopObject {
59 pub fn new(
62 instance: u32,
63 name: impl Into<String>,
64 output_units: EngineeringUnits,
65 ) -> Result<Self, Error> {
66 let oid = ObjectIdentifier::new(ObjectType::LOOP, instance)?;
67 Ok(Self {
68 oid,
69 name: name.into(),
70 description: String::new(),
71 present_value: 0.0,
72 setpoint: 0.0,
73 controlled_variable_value: 0.0,
74 cov_increment: 0.0,
75 proportional_constant: 1.0,
76 integral_constant: 0.0,
77 derivative_constant: 0.0,
78 output_units,
79 controlled_variable_units: EngineeringUnits::NO_UNITS,
80 proportional_constant_units: EngineeringUnits::NO_UNITS,
81 integral_constant_units: EngineeringUnits::NO_UNITS,
82 derivative_constant_units: EngineeringUnits::NO_UNITS,
83 action: Action::DIRECT,
84 priority_for_writing: 16, update_interval: 1000, out_of_service: false,
87 reliability: Reliability::NO_FAULT_DETECTED,
88 reliability_before_out_of_service: None,
89 status_flags: StatusFlags::empty(),
90 controlled_variable_reference: None,
91 manipulated_variable_reference: None,
92 setpoint_reference: None,
93 })
94 }
95
96 pub fn set_present_value(&mut self, value: f32) {
102 self.present_value = value;
103 }
104
105 pub fn set_controlled_variable_value(&mut self, value: f32) {
114 self.controlled_variable_value = value;
115 }
116
117 fn apply_present_value(&mut self, value: PropertyValue) -> Result<(), Error> {
122 write_finite_real(&mut self.present_value, value)
123 }
124
125 pub fn set_description(&mut self, desc: impl Into<String>) {
127 self.description = desc.into();
128 }
129
130 pub fn set_controlled_variable_reference(&mut self, r: BACnetObjectPropertyReference) {
134 self.controlled_variable_reference = reference::set_or_unset(r);
135 }
136
137 pub fn set_manipulated_variable_reference(&mut self, r: BACnetObjectPropertyReference) {
141 self.manipulated_variable_reference = reference::set_or_unset(r);
142 }
143
144 pub fn set_setpoint_reference(&mut self, r: BACnetObjectPropertyReference) {
147 self.setpoint_reference = Some(r);
148 }
149
150 pub fn set_controlled_variable_units(&mut self, units: EngineeringUnits) -> Result<(), Error> {
157 self.controlled_variable_units = checked_units(units)?;
158 Ok(())
159 }
160
161 pub fn set_proportional_constant_units(
166 &mut self,
167 units: EngineeringUnits,
168 ) -> Result<(), Error> {
169 self.proportional_constant_units = checked_units(units)?;
170 Ok(())
171 }
172
173 pub fn set_integral_constant_units(&mut self, units: EngineeringUnits) -> Result<(), Error> {
176 self.integral_constant_units = checked_units(units)?;
177 Ok(())
178 }
179
180 pub fn set_derivative_constant_units(&mut self, units: EngineeringUnits) -> Result<(), Error> {
183 self.derivative_constant_units = checked_units(units)?;
184 Ok(())
185 }
186
187 pub fn set_priority_for_writing(&mut self, priority: u8) -> Result<(), Error> {
194 if !(1..=16).contains(&priority) {
195 return Err(common::value_out_of_range_error());
196 }
197 self.priority_for_writing = priority;
198 Ok(())
199 }
200}
201
202fn checked_units(units: EngineeringUnits) -> Result<EngineeringUnits, Error> {
204 if units.to_raw() > 65_535 {
205 return Err(common::value_out_of_range_error());
206 }
207 Ok(units)
208}
209
210fn write_finite_real(target: &mut f32, value: PropertyValue) -> Result<(), Error> {
212 let PropertyValue::Real(v) = value else {
213 return Err(common::invalid_data_type_error());
214 };
215 common::reject_non_finite(v)?;
216 *target = v;
217 Ok(())
218}
219
220impl BACnetObject for LoopObject {
221 fn object_identifier(&self) -> ObjectIdentifier {
222 self.oid
223 }
224
225 fn object_name(&self) -> &str {
226 &self.name
227 }
228
229 fn read_property(
230 &self,
231 property: PropertyIdentifier,
232 array_index: Option<u32>,
233 ) -> Result<PropertyValue, Error> {
234 match property {
235 p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
236 Ok(PropertyValue::ObjectIdentifier(self.oid))
237 }
238 p if p == PropertyIdentifier::OBJECT_NAME => {
239 Ok(PropertyValue::CharacterString(self.name.clone()))
240 }
241 p if p == PropertyIdentifier::DESCRIPTION => {
242 Ok(PropertyValue::CharacterString(self.description.clone()))
243 }
244 p if p == PropertyIdentifier::OBJECT_TYPE => {
245 Ok(PropertyValue::Enumerated(ObjectType::LOOP.to_raw()))
246 }
247 p if p == PropertyIdentifier::PRESENT_VALUE => {
248 Ok(PropertyValue::Real(self.present_value))
249 }
250 p if p == PropertyIdentifier::SETPOINT => Ok(PropertyValue::Real(self.setpoint)),
251 p if p == PropertyIdentifier::CONTROLLED_VARIABLE_VALUE => {
252 Ok(PropertyValue::Real(self.controlled_variable_value))
253 }
254 p if p == PropertyIdentifier::COV_INCREMENT => {
255 Ok(PropertyValue::Real(self.cov_increment))
256 }
257 p if p == PropertyIdentifier::PROPORTIONAL_CONSTANT => {
258 Ok(PropertyValue::Real(self.proportional_constant))
259 }
260 p if p == PropertyIdentifier::INTEGRAL_CONSTANT => {
261 Ok(PropertyValue::Real(self.integral_constant))
262 }
263 p if p == PropertyIdentifier::DERIVATIVE_CONSTANT => {
264 Ok(PropertyValue::Real(self.derivative_constant))
265 }
266 p if p == PropertyIdentifier::OUTPUT_UNITS => {
267 Ok(PropertyValue::Enumerated(self.output_units.to_raw()))
268 }
269 p if p == PropertyIdentifier::CONTROLLED_VARIABLE_UNITS => Ok(
270 PropertyValue::Enumerated(self.controlled_variable_units.to_raw()),
271 ),
272 p if p == PropertyIdentifier::PROPORTIONAL_CONSTANT_UNITS => Ok(
273 PropertyValue::Enumerated(self.proportional_constant_units.to_raw()),
274 ),
275 p if p == PropertyIdentifier::INTEGRAL_CONSTANT_UNITS => Ok(PropertyValue::Enumerated(
276 self.integral_constant_units.to_raw(),
277 )),
278 p if p == PropertyIdentifier::DERIVATIVE_CONSTANT_UNITS => Ok(
279 PropertyValue::Enumerated(self.derivative_constant_units.to_raw()),
280 ),
281 p if p == PropertyIdentifier::ACTION => {
282 Ok(PropertyValue::Enumerated(self.action.to_raw()))
283 }
284 p if p == PropertyIdentifier::PRIORITY_FOR_WRITING => {
285 Ok(PropertyValue::Unsigned(self.priority_for_writing.into()))
286 }
287 p if p == PropertyIdentifier::UPDATE_INTERVAL => {
288 Ok(PropertyValue::Unsigned(self.update_interval as u64))
289 }
290 p if p == PropertyIdentifier::STATUS_FLAGS => Ok(common::compute_status_flags(
294 self.status_flags,
295 self.reliability,
296 self.out_of_service,
297 EventState::NORMAL,
298 )),
299 p if p == PropertyIdentifier::EVENT_STATE => {
300 Ok(PropertyValue::Enumerated(EventState::NORMAL.to_raw()))
301 }
302 p if p == PropertyIdentifier::RELIABILITY => {
303 Ok(PropertyValue::Enumerated(self.reliability.to_raw()))
304 }
305 p if p == PropertyIdentifier::OUT_OF_SERVICE => {
306 Ok(PropertyValue::Boolean(self.out_of_service))
307 }
308 p if p == PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE => {
312 Ok(reference::object_property_reference_value(
313 self.controlled_variable_reference.as_ref(),
314 ObjectType::ANALOG_INPUT,
315 ))
316 }
317 p if p == PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE => {
318 Ok(reference::object_property_reference_value(
319 self.manipulated_variable_reference.as_ref(),
320 ObjectType::ANALOG_OUTPUT,
321 ))
322 }
323 p if p == PropertyIdentifier::SETPOINT_REFERENCE => Ok(
324 reference::setpoint_reference_value(self.setpoint_reference.as_ref()),
325 ),
326 p if p == PropertyIdentifier::PROPERTY_LIST => {
327 read_property_list_property(&self.property_list(), array_index)
328 }
329 _ => Err(Error::Protocol {
330 class: ErrorClass::PROPERTY,
331 code: ErrorCode::UNKNOWN_PROPERTY,
332 }),
333 }
334 }
335
336 fn write_property(
337 &mut self,
338 property: PropertyIdentifier,
339 _array_index: Option<u32>,
340 value: PropertyValue,
341 _priority: Option<u8>,
342 ) -> Result<(), Error> {
343 if property == PropertyIdentifier::PRESENT_VALUE {
348 if !self.out_of_service {
349 return Err(common::write_access_denied_error());
350 }
351 return self.apply_present_value(value);
352 }
353 if let Some(result) = common::write_out_of_service_with_reliability_restore(
354 &mut self.out_of_service,
355 &mut self.reliability,
356 &mut self.reliability_before_out_of_service,
357 property,
358 &value,
359 ) {
360 return result;
361 }
362 if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
363 {
364 return result;
365 }
366 match property {
367 p if p == PropertyIdentifier::SETPOINT => write_finite_real(&mut self.setpoint, value),
368 p if p == PropertyIdentifier::PROPORTIONAL_CONSTANT => {
369 write_finite_real(&mut self.proportional_constant, value)
370 }
371 p if p == PropertyIdentifier::INTEGRAL_CONSTANT => {
372 write_finite_real(&mut self.integral_constant, value)
373 }
374 p if p == PropertyIdentifier::DERIVATIVE_CONSTANT => {
375 write_finite_real(&mut self.derivative_constant, value)
376 }
377 p if p == PropertyIdentifier::ACTION => {
379 let PropertyValue::Enumerated(raw) = value else {
380 return Err(common::invalid_data_type_error());
381 };
382 let action = Action::from_raw(raw);
383 if action != Action::DIRECT && action != Action::REVERSE {
384 return Err(common::value_out_of_range_error());
385 }
386 self.action = action;
387 Ok(())
388 }
389 p if p == PropertyIdentifier::UPDATE_INTERVAL => {
390 if let PropertyValue::Unsigned(v) = value {
391 self.update_interval = common::u64_to_u32(v)?;
392 return Ok(());
393 }
394 Err(Error::Protocol {
395 class: ErrorClass::PROPERTY,
396 code: ErrorCode::INVALID_DATA_TYPE,
397 })
398 }
399 p if p == PropertyIdentifier::RELIABILITY => {
409 if !self.out_of_service {
410 return Err(common::write_access_denied_error());
411 }
412 if let PropertyValue::Enumerated(raw) = value {
413 let v = Reliability::from_raw(raw);
414 if !common::is_reliability_value_valid(v) {
415 return Err(common::value_out_of_range_error());
416 }
417 self.reliability = v;
418 return Ok(());
419 }
420 Err(Error::Protocol {
421 class: ErrorClass::PROPERTY,
422 code: ErrorCode::INVALID_DATA_TYPE,
423 })
424 }
425 p if p == PropertyIdentifier::DESCRIPTION => {
426 if let PropertyValue::CharacterString(s) = value {
427 self.description = s;
428 return Ok(());
429 }
430 Err(Error::Protocol {
431 class: ErrorClass::PROPERTY,
432 code: ErrorCode::INVALID_DATA_TYPE,
433 })
434 }
435 p if p == PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE => {
440 self.controlled_variable_reference =
441 reference::decode_reference_write(&value, ReferenceFrame::Bare)?;
442 Ok(())
443 }
444 p if p == PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE => {
445 self.manipulated_variable_reference =
446 reference::decode_reference_write(&value, ReferenceFrame::Bare)?;
447 Ok(())
448 }
449 p if p == PropertyIdentifier::SETPOINT_REFERENCE => {
452 self.setpoint_reference =
453 reference::decode_reference_write(&value, ReferenceFrame::Setpoint)?;
454 Ok(())
455 }
456 _ => Err(crate::common::unhandled_write_error(
457 self.property_metadata().as_ref(),
458 property,
459 _array_index,
460 )),
461 }
462 }
463
464 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
465 metadata::for_object(self)
466 }
467
468 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
469 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
470 }
471
472 fn supports_cov(&self) -> bool {
473 true
474 }
475
476 fn cov_increment(&self) -> Option<f64> {
477 Some(f64::from(self.cov_increment))
478 }
479
480 fn set_present_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
481 if self.out_of_service {
484 return Err(common::write_access_denied_error());
485 }
486 self.apply_present_value(value)
487 }
488
489 fn set_controlled_variable_value_internal(
490 &mut self,
491 value: PropertyValue,
492 ) -> Result<(), Error> {
493 write_finite_real(&mut self.controlled_variable_value, value)
497 }
498
499 fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
500 if self.out_of_service {
505 return Err(common::write_access_denied_error());
506 }
507 if !common::is_reliability_value_valid(reliability) {
508 return Err(common::value_out_of_range_error());
509 }
510 self.reliability = reliability;
511 Ok(())
512 }
513}
514
515#[cfg(test)]
516mod tests;
517
518#[cfg(test)]
519mod property_set_tests;
520
521#[cfg(test)]
522mod reference_tests;