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: u32,
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(instance: u32, name: impl Into<String>, output_units: u32) -> Result<Self, Error> {
62 let oid = ObjectIdentifier::new(ObjectType::LOOP, instance)?;
63 Ok(Self {
64 oid,
65 name: name.into(),
66 description: String::new(),
67 present_value: 0.0,
68 setpoint: 0.0,
69 controlled_variable_value: 0.0,
70 cov_increment: 0.0,
71 proportional_constant: 1.0,
72 integral_constant: 0.0,
73 derivative_constant: 0.0,
74 output_units,
75 controlled_variable_units: EngineeringUnits::NO_UNITS,
76 proportional_constant_units: EngineeringUnits::NO_UNITS,
77 integral_constant_units: EngineeringUnits::NO_UNITS,
78 derivative_constant_units: EngineeringUnits::NO_UNITS,
79 action: Action::DIRECT,
80 priority_for_writing: 16, update_interval: 1000, out_of_service: false,
83 reliability: Reliability::NO_FAULT_DETECTED,
84 reliability_before_out_of_service: None,
85 status_flags: StatusFlags::empty(),
86 controlled_variable_reference: None,
87 manipulated_variable_reference: None,
88 setpoint_reference: None,
89 })
90 }
91
92 pub fn set_present_value(&mut self, value: f32) {
98 self.present_value = value;
99 }
100
101 pub fn set_controlled_variable_value(&mut self, value: f32) {
110 self.controlled_variable_value = value;
111 }
112
113 fn apply_present_value(&mut self, value: PropertyValue) -> Result<(), Error> {
118 write_finite_real(&mut self.present_value, value)
119 }
120
121 pub fn set_description(&mut self, desc: impl Into<String>) {
123 self.description = desc.into();
124 }
125
126 pub fn set_controlled_variable_reference(&mut self, r: BACnetObjectPropertyReference) {
130 self.controlled_variable_reference = reference::set_or_unset(r);
131 }
132
133 pub fn set_manipulated_variable_reference(&mut self, r: BACnetObjectPropertyReference) {
137 self.manipulated_variable_reference = reference::set_or_unset(r);
138 }
139
140 pub fn set_setpoint_reference(&mut self, r: BACnetObjectPropertyReference) {
143 self.setpoint_reference = Some(r);
144 }
145
146 pub fn set_controlled_variable_units(&mut self, units: EngineeringUnits) -> Result<(), Error> {
153 self.controlled_variable_units = checked_units(units)?;
154 Ok(())
155 }
156
157 pub fn set_proportional_constant_units(
162 &mut self,
163 units: EngineeringUnits,
164 ) -> Result<(), Error> {
165 self.proportional_constant_units = checked_units(units)?;
166 Ok(())
167 }
168
169 pub fn set_integral_constant_units(&mut self, units: EngineeringUnits) -> Result<(), Error> {
172 self.integral_constant_units = checked_units(units)?;
173 Ok(())
174 }
175
176 pub fn set_derivative_constant_units(&mut self, units: EngineeringUnits) -> Result<(), Error> {
179 self.derivative_constant_units = checked_units(units)?;
180 Ok(())
181 }
182
183 pub fn set_priority_for_writing(&mut self, priority: u8) -> Result<(), Error> {
190 if !(1..=16).contains(&priority) {
191 return Err(common::value_out_of_range_error());
192 }
193 self.priority_for_writing = priority;
194 Ok(())
195 }
196}
197
198fn checked_units(units: EngineeringUnits) -> Result<EngineeringUnits, Error> {
200 if units.to_raw() > 65_535 {
201 return Err(common::value_out_of_range_error());
202 }
203 Ok(units)
204}
205
206fn write_finite_real(target: &mut f32, value: PropertyValue) -> Result<(), Error> {
208 let PropertyValue::Real(v) = value else {
209 return Err(common::invalid_data_type_error());
210 };
211 common::reject_non_finite(v)?;
212 *target = v;
213 Ok(())
214}
215
216impl BACnetObject for LoopObject {
217 fn object_identifier(&self) -> ObjectIdentifier {
218 self.oid
219 }
220
221 fn object_name(&self) -> &str {
222 &self.name
223 }
224
225 fn read_property(
226 &self,
227 property: PropertyIdentifier,
228 array_index: Option<u32>,
229 ) -> Result<PropertyValue, Error> {
230 match property {
231 p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
232 Ok(PropertyValue::ObjectIdentifier(self.oid))
233 }
234 p if p == PropertyIdentifier::OBJECT_NAME => {
235 Ok(PropertyValue::CharacterString(self.name.clone()))
236 }
237 p if p == PropertyIdentifier::DESCRIPTION => {
238 Ok(PropertyValue::CharacterString(self.description.clone()))
239 }
240 p if p == PropertyIdentifier::OBJECT_TYPE => {
241 Ok(PropertyValue::Enumerated(ObjectType::LOOP.to_raw()))
242 }
243 p if p == PropertyIdentifier::PRESENT_VALUE => {
244 Ok(PropertyValue::Real(self.present_value))
245 }
246 p if p == PropertyIdentifier::SETPOINT => Ok(PropertyValue::Real(self.setpoint)),
247 p if p == PropertyIdentifier::CONTROLLED_VARIABLE_VALUE => {
248 Ok(PropertyValue::Real(self.controlled_variable_value))
249 }
250 p if p == PropertyIdentifier::COV_INCREMENT => {
251 Ok(PropertyValue::Real(self.cov_increment))
252 }
253 p if p == PropertyIdentifier::PROPORTIONAL_CONSTANT => {
254 Ok(PropertyValue::Real(self.proportional_constant))
255 }
256 p if p == PropertyIdentifier::INTEGRAL_CONSTANT => {
257 Ok(PropertyValue::Real(self.integral_constant))
258 }
259 p if p == PropertyIdentifier::DERIVATIVE_CONSTANT => {
260 Ok(PropertyValue::Real(self.derivative_constant))
261 }
262 p if p == PropertyIdentifier::OUTPUT_UNITS => {
263 Ok(PropertyValue::Enumerated(self.output_units))
264 }
265 p if p == PropertyIdentifier::CONTROLLED_VARIABLE_UNITS => Ok(
266 PropertyValue::Enumerated(self.controlled_variable_units.to_raw()),
267 ),
268 p if p == PropertyIdentifier::PROPORTIONAL_CONSTANT_UNITS => Ok(
269 PropertyValue::Enumerated(self.proportional_constant_units.to_raw()),
270 ),
271 p if p == PropertyIdentifier::INTEGRAL_CONSTANT_UNITS => Ok(PropertyValue::Enumerated(
272 self.integral_constant_units.to_raw(),
273 )),
274 p if p == PropertyIdentifier::DERIVATIVE_CONSTANT_UNITS => Ok(
275 PropertyValue::Enumerated(self.derivative_constant_units.to_raw()),
276 ),
277 p if p == PropertyIdentifier::ACTION => {
278 Ok(PropertyValue::Enumerated(self.action.to_raw()))
279 }
280 p if p == PropertyIdentifier::PRIORITY_FOR_WRITING => {
281 Ok(PropertyValue::Unsigned(self.priority_for_writing.into()))
282 }
283 p if p == PropertyIdentifier::UPDATE_INTERVAL => {
284 Ok(PropertyValue::Unsigned(self.update_interval as u64))
285 }
286 p if p == PropertyIdentifier::STATUS_FLAGS => Ok(common::compute_status_flags(
290 self.status_flags,
291 self.reliability,
292 self.out_of_service,
293 EventState::NORMAL,
294 )),
295 p if p == PropertyIdentifier::EVENT_STATE => {
296 Ok(PropertyValue::Enumerated(EventState::NORMAL.to_raw()))
297 }
298 p if p == PropertyIdentifier::RELIABILITY => {
299 Ok(PropertyValue::Enumerated(self.reliability.to_raw()))
300 }
301 p if p == PropertyIdentifier::OUT_OF_SERVICE => {
302 Ok(PropertyValue::Boolean(self.out_of_service))
303 }
304 p if p == PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE => {
308 Ok(reference::object_property_reference_value(
309 self.controlled_variable_reference.as_ref(),
310 ObjectType::ANALOG_INPUT,
311 ))
312 }
313 p if p == PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE => {
314 Ok(reference::object_property_reference_value(
315 self.manipulated_variable_reference.as_ref(),
316 ObjectType::ANALOG_OUTPUT,
317 ))
318 }
319 p if p == PropertyIdentifier::SETPOINT_REFERENCE => Ok(
320 reference::setpoint_reference_value(self.setpoint_reference.as_ref()),
321 ),
322 p if p == PropertyIdentifier::PROPERTY_LIST => {
323 read_property_list_property(&self.property_list(), array_index)
324 }
325 _ => Err(Error::Protocol {
326 class: ErrorClass::PROPERTY.to_raw() as u32,
327 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
328 }),
329 }
330 }
331
332 fn write_property(
333 &mut self,
334 property: PropertyIdentifier,
335 _array_index: Option<u32>,
336 value: PropertyValue,
337 _priority: Option<u8>,
338 ) -> Result<(), Error> {
339 if property == PropertyIdentifier::PRESENT_VALUE {
344 if !self.out_of_service {
345 return Err(common::write_access_denied_error());
346 }
347 return self.apply_present_value(value);
348 }
349 if let Some(result) = common::write_out_of_service_with_reliability_restore(
350 &mut self.out_of_service,
351 &mut self.reliability,
352 &mut self.reliability_before_out_of_service,
353 property,
354 &value,
355 ) {
356 return result;
357 }
358 if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
359 {
360 return result;
361 }
362 match property {
363 p if p == PropertyIdentifier::SETPOINT => write_finite_real(&mut self.setpoint, value),
364 p if p == PropertyIdentifier::PROPORTIONAL_CONSTANT => {
365 write_finite_real(&mut self.proportional_constant, value)
366 }
367 p if p == PropertyIdentifier::INTEGRAL_CONSTANT => {
368 write_finite_real(&mut self.integral_constant, value)
369 }
370 p if p == PropertyIdentifier::DERIVATIVE_CONSTANT => {
371 write_finite_real(&mut self.derivative_constant, value)
372 }
373 p if p == PropertyIdentifier::ACTION => {
375 let PropertyValue::Enumerated(raw) = value else {
376 return Err(common::invalid_data_type_error());
377 };
378 let action = Action::from_raw(raw);
379 if action != Action::DIRECT && action != Action::REVERSE {
380 return Err(common::value_out_of_range_error());
381 }
382 self.action = action;
383 Ok(())
384 }
385 p if p == PropertyIdentifier::UPDATE_INTERVAL => {
386 if let PropertyValue::Unsigned(v) = value {
387 self.update_interval = common::u64_to_u32(v)?;
388 return Ok(());
389 }
390 Err(Error::Protocol {
391 class: ErrorClass::PROPERTY.to_raw() as u32,
392 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
393 })
394 }
395 p if p == PropertyIdentifier::RELIABILITY => {
405 if !self.out_of_service {
406 return Err(common::write_access_denied_error());
407 }
408 if let PropertyValue::Enumerated(raw) = value {
409 let v = Reliability::from_raw(raw);
410 if !common::is_reliability_value_valid(v) {
411 return Err(common::value_out_of_range_error());
412 }
413 self.reliability = v;
414 return Ok(());
415 }
416 Err(Error::Protocol {
417 class: ErrorClass::PROPERTY.to_raw() as u32,
418 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
419 })
420 }
421 p if p == PropertyIdentifier::DESCRIPTION => {
422 if let PropertyValue::CharacterString(s) = value {
423 self.description = s;
424 return Ok(());
425 }
426 Err(Error::Protocol {
427 class: ErrorClass::PROPERTY.to_raw() as u32,
428 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
429 })
430 }
431 p if p == PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE => {
436 self.controlled_variable_reference =
437 reference::decode_reference_write(&value, ReferenceFrame::Bare)?;
438 Ok(())
439 }
440 p if p == PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE => {
441 self.manipulated_variable_reference =
442 reference::decode_reference_write(&value, ReferenceFrame::Bare)?;
443 Ok(())
444 }
445 p if p == PropertyIdentifier::SETPOINT_REFERENCE => {
448 self.setpoint_reference =
449 reference::decode_reference_write(&value, ReferenceFrame::Setpoint)?;
450 Ok(())
451 }
452 _ => Err(crate::common::unhandled_write_error(
453 self.property_metadata().as_ref(),
454 property,
455 _array_index,
456 )),
457 }
458 }
459
460 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
461 metadata::for_object(self)
462 }
463
464 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
465 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
466 }
467
468 fn supports_cov(&self) -> bool {
469 true
470 }
471
472 fn cov_increment(&self) -> Option<f64> {
473 Some(f64::from(self.cov_increment))
474 }
475
476 fn set_present_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
477 if self.out_of_service {
480 return Err(common::write_access_denied_error());
481 }
482 self.apply_present_value(value)
483 }
484
485 fn set_controlled_variable_value_internal(
486 &mut self,
487 value: PropertyValue,
488 ) -> Result<(), Error> {
489 write_finite_real(&mut self.controlled_variable_value, value)
493 }
494
495 fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
496 if self.out_of_service {
501 return Err(common::write_access_denied_error());
502 }
503 if !common::is_reliability_value_valid(reliability) {
504 return Err(common::value_out_of_range_error());
505 }
506 self.reliability = reliability;
507 Ok(())
508 }
509}
510
511#[cfg(test)]
512mod tests;
513
514#[cfg(test)]
515mod property_set_tests;
516
517#[cfg(test)]
518mod reference_tests;