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