1use std::borrow::Cow;
8
9use bacnet_encoding::constructed::{decode_stage_limit_value, encode_stage_limit_value};
10use bacnet_types::constructed::{BACnetDeviceObjectReference, BACnetStageLimitValue};
11use bacnet_types::enums::{
12 ErrorClass, ErrorCode, EventState, ObjectType, PropertyIdentifier, Reliability,
13};
14use bacnet_types::error::Error;
15use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
16use bytes::BytesMut;
17
18use crate::common::{self, read_common_properties};
19use crate::device_reference;
20use crate::property_metadata::{
21 property_list_from_metadata, PropertyConformance, PropertyMetadata, PropertyWriteCapability,
22};
23use crate::traits::BACnetObject;
24
25mod metadata;
26use metadata::STAGING_PROPERTY_METADATA;
27
28#[derive(Debug, Clone, PartialEq)]
35pub struct StagingConfig {
36 pub present_value: f32,
38 pub min_present_value: f32,
40 pub units: u32,
42 pub priority_for_writing: u8,
44 pub stages: Vec<BACnetStageLimitValue>,
46 pub target_references: Vec<BACnetDeviceObjectReference>,
48 pub stage_names: Option<Vec<String>>,
50}
51
52#[doc(hidden)]
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct StagingTargetWrite {
56 pub object_identifier: ObjectIdentifier,
58 pub active: bool,
60}
61
62#[doc(hidden)]
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct StagingWritePlan {
66 pub source: ObjectIdentifier,
68 pub generation: u64,
70 pub priority: u8,
72 pub writes: Vec<StagingTargetWrite>,
74}
75
76pub struct StagingObject {
78 oid: ObjectIdentifier,
79 name: String,
80 description: String,
81 present_value: f32,
82 present_stage: u64,
83 stages: Vec<BACnetStageLimitValue>,
84 stage_names: Option<Vec<String>>,
85 target_references: Vec<BACnetDeviceObjectReference>,
86 priority_for_writing: u8,
87 min_present_value: f32,
88 units: u32,
89 cov_increment: f32,
90 status_flags: StatusFlags,
91 out_of_service: bool,
92 reliability: Reliability,
93 generation: u64,
94 pending_plan: Option<StagingWritePlan>,
95}
96
97impl StagingObject {
98 pub fn new(
105 instance: u32,
106 name: impl Into<String>,
107 config: StagingConfig,
108 ) -> Result<Self, Error> {
109 validate_config(&config)?;
110 let oid = ObjectIdentifier::new(ObjectType::STAGING, instance)?;
111 let present_value = config.present_value.clamp(
112 config.min_present_value,
113 config.stages.last().unwrap().limit,
114 );
115 Ok(Self {
116 oid,
117 name: name.into(),
118 description: String::new(),
119 present_value,
120 present_stage: 0,
121 stages: config.stages,
122 stage_names: config.stage_names,
123 target_references: config.target_references,
124 priority_for_writing: config.priority_for_writing,
125 min_present_value: config.min_present_value,
126 units: config.units,
127 cov_increment: 0.0,
128 status_flags: StatusFlags::empty(),
129 out_of_service: false,
130 reliability: Reliability::NO_FAULT_DETECTED,
131 generation: 0,
132 pending_plan: None,
133 })
134 }
135
136 fn max_present_value(&self) -> f32 {
137 self.stages
138 .last()
139 .expect("validated Staging configuration has at least two stages")
140 .limit
141 }
142
143 fn select_stage(&self, value: f32) -> u64 {
144 self.stages
145 .iter()
146 .position(|stage| stage.limit >= value)
147 .map_or(self.stages.len() as u64, |index| index as u64 + 1)
148 }
149
150 fn retained_by_hysteresis(&self, value: f32) -> bool {
151 let Some(index) = self
152 .present_stage
153 .checked_sub(1)
154 .map(|stage| stage as usize)
155 else {
156 return false;
157 };
158 let Some(stage) = self.stages.get(index) else {
159 return false;
160 };
161 let lower = if index == 0 {
162 self.min_present_value
163 } else {
164 let prior = &self.stages[index - 1];
165 prior.limit - prior.deadband
166 };
167 value >= lower && value <= stage.limit + stage.deadband
168 }
169
170 fn evaluate(&mut self, force_reapply: bool) {
171 let previous = self.present_stage;
172 if previous != 0 && self.retained_by_hysteresis(self.present_value) && !force_reapply {
173 return;
174 }
175 let selected = self.select_stage(self.present_value);
176 self.present_stage = selected;
177 if previous != selected || force_reapply {
178 self.queue_current_stage_plan();
179 }
180 }
181
182 fn queue_current_stage_plan(&mut self) {
183 self.generation = self.generation.wrapping_add(1);
184 if self.out_of_service || self.present_stage == 0 {
185 self.pending_plan = None;
186 return;
187 }
188 let values = &self.stages[self.present_stage as usize - 1].values;
189 let writes = self
190 .target_references
191 .iter()
192 .zip(values)
193 .filter(|(reference, _)| {
194 reference.object_identifier.instance_number() != ObjectIdentifier::MAX_INSTANCE
195 })
196 .map(|(reference, active)| StagingTargetWrite {
197 object_identifier: reference.object_identifier,
198 active: *active,
199 })
200 .collect();
201 self.pending_plan = Some(StagingWritePlan {
202 source: self.oid,
203 generation: self.generation,
204 priority: self.priority_for_writing,
205 writes,
206 });
207 }
208
209 fn reset_and_reevaluate(&mut self) {
210 self.present_stage = 0;
211 self.present_value = self
212 .present_value
213 .clamp(self.min_present_value, self.max_present_value());
214 self.evaluate(true);
215 }
216
217 fn replace_stages(
218 &mut self,
219 array_index: Option<u32>,
220 value: PropertyValue,
221 ) -> Result<(), Error> {
222 let mut candidate = self.stages.clone();
223 match array_index {
224 None => {
225 let PropertyValue::List(values) = value else {
226 return Err(common::invalid_data_type_error());
227 };
228 if values.len() != candidate.len() {
229 return Err(common::value_out_of_range_error());
230 }
231 candidate = values
232 .into_iter()
233 .map(decode_stage_property_value)
234 .collect::<Result<_, _>>()?;
235 }
236 Some(0) => return Err(common::write_access_denied_error()),
237 Some(index) => {
238 let Some(stage) = candidate.get_mut((index - 1) as usize) else {
239 return Err(common::invalid_array_index_error());
240 };
241 *stage = decode_stage_property_value(value)?;
242 }
243 }
244 validate_ladder(
245 &candidate,
246 self.min_present_value,
247 self.target_references.len(),
248 )?;
249 if candidate != self.stages {
250 self.stages = candidate;
251 self.reset_and_reevaluate();
252 }
253 Ok(())
254 }
255
256 fn replace_target_references(
257 &mut self,
258 array_index: Option<u32>,
259 value: PropertyValue,
260 ) -> Result<(), Error> {
261 let mut candidate = self.target_references.clone();
262 match array_index {
263 None => {
264 let references: Vec<BACnetDeviceObjectReference> =
265 device_reference::decode_references(&value)?;
266 if references.len() != candidate.len() {
267 return Err(common::value_out_of_range_error());
268 }
269 candidate = references;
270 }
271 Some(0) => return Err(common::write_access_denied_error()),
272 Some(index) => {
273 let Some(reference) = candidate.get_mut((index - 1) as usize) else {
274 return Err(common::invalid_array_index_error());
275 };
276 *reference = device_reference::decode_reference(&value)?;
277 }
278 }
279 validate_target_references(&candidate)?;
280 if candidate != self.target_references {
281 self.target_references = candidate;
282 self.reset_and_reevaluate();
283 }
284 Ok(())
285 }
286
287 fn replace_stage_names(
288 &mut self,
289 array_index: Option<u32>,
290 value: PropertyValue,
291 ) -> Result<(), Error> {
292 let Some(names) = &self.stage_names else {
293 return Err(if array_index.is_none() {
294 common::unknown_property_error()
295 } else {
296 common::write_access_denied_error()
297 });
298 };
299 let mut candidate = names.clone();
300 match array_index {
301 None => {
302 let PropertyValue::List(values) = value else {
303 return Err(common::invalid_data_type_error());
304 };
305 if values.len() != candidate.len() {
306 return Err(common::value_out_of_range_error());
307 }
308 candidate = values
309 .into_iter()
310 .map(|value| match value {
311 PropertyValue::CharacterString(name) => Ok(name),
312 _ => Err(common::invalid_data_type_error()),
313 })
314 .collect::<Result<_, _>>()?;
315 }
316 Some(0) => return Err(common::write_access_denied_error()),
317 Some(index) => {
318 let Some(name) = candidate.get_mut((index - 1) as usize) else {
319 return Err(common::invalid_array_index_error());
320 };
321 let PropertyValue::CharacterString(value) = value else {
322 return Err(common::invalid_data_type_error());
323 };
324 *name = value;
325 }
326 }
327 self.stage_names = Some(candidate);
328 Ok(())
329 }
330
331 fn metadata(&self) -> Vec<PropertyMetadata> {
332 let mut rows = STAGING_PROPERTY_METADATA.to_vec();
333 if self.stage_names.is_some() {
334 let target_index = rows
335 .iter()
336 .position(|row| row.property_identifier == PropertyIdentifier::TARGET_REFERENCES)
337 .unwrap();
338 rows.insert(
339 target_index,
340 PropertyMetadata::new(
341 PropertyIdentifier::STAGE_NAMES,
342 PropertyConformance::Optional,
343 None,
344 PropertyWriteCapability::Always,
345 ),
346 );
347 }
348 rows
349 }
350}
351
352impl BACnetObject for StagingObject {
353 fn object_identifier(&self) -> ObjectIdentifier {
354 self.oid
355 }
356
357 fn object_name(&self) -> &str {
358 &self.name
359 }
360
361 fn read_property(
362 &self,
363 property: PropertyIdentifier,
364 array_index: Option<u32>,
365 ) -> Result<PropertyValue, Error> {
366 if let Some(result) = read_common_properties!(self, property, array_index) {
367 return result;
368 }
369 match property {
370 PropertyIdentifier::OBJECT_TYPE => {
371 Ok(PropertyValue::Enumerated(ObjectType::STAGING.to_raw()))
372 }
373 PropertyIdentifier::PRESENT_VALUE => Ok(PropertyValue::Real(self.present_value)),
374 PropertyIdentifier::PRESENT_STAGE if self.present_stage == 0 => Err(protocol_error(
375 ErrorClass::PROPERTY,
376 ErrorCode::VALUE_NOT_INITIALIZED,
377 )),
378 PropertyIdentifier::PRESENT_STAGE => Ok(PropertyValue::Unsigned(self.present_stage)),
379 PropertyIdentifier::STAGES => common::read_array(
380 self.stages
381 .iter()
382 .map(|stage| {
383 let mut encoded = BytesMut::new();
384 encode_stage_limit_value(&mut encoded, stage);
385 PropertyValue::ApplicationData(encoded.to_vec())
386 })
387 .collect(),
388 array_index,
389 ),
390 PropertyIdentifier::STAGE_NAMES => {
391 let Some(names) = &self.stage_names else {
392 return Err(common::unknown_property_error());
393 };
394 common::read_array(
395 names
396 .iter()
397 .cloned()
398 .map(PropertyValue::CharacterString)
399 .collect(),
400 array_index,
401 )
402 }
403 PropertyIdentifier::TARGET_REFERENCES => common::read_array(
404 device_reference::reference_elements(&self.target_references),
405 array_index,
406 ),
407 PropertyIdentifier::EVENT_STATE => {
408 Ok(PropertyValue::Enumerated(EventState::NORMAL.to_raw()))
409 }
410 PropertyIdentifier::UNITS => Ok(PropertyValue::Enumerated(self.units)),
411 PropertyIdentifier::PRIORITY_FOR_WRITING => {
412 Ok(PropertyValue::Unsigned(self.priority_for_writing.into()))
413 }
414 PropertyIdentifier::MIN_PRES_VALUE => Ok(PropertyValue::Real(self.min_present_value)),
415 PropertyIdentifier::MAX_PRES_VALUE => Ok(PropertyValue::Real(self.max_present_value())),
416 PropertyIdentifier::COV_INCREMENT => Ok(PropertyValue::Real(self.cov_increment)),
417 _ => Err(common::unknown_property_error()),
418 }
419 }
420
421 fn write_property(
422 &mut self,
423 property: PropertyIdentifier,
424 array_index: Option<u32>,
425 value: PropertyValue,
426 _priority: Option<u8>,
427 ) -> Result<(), Error> {
428 if property != PropertyIdentifier::STAGES
429 && property != PropertyIdentifier::STAGE_NAMES
430 && property != PropertyIdentifier::TARGET_REFERENCES
431 && array_index.is_some()
432 {
433 return Err(common::property_is_not_an_array_error());
434 }
435 if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
436 return result;
437 }
438 if let Some(result) = common::write_description(&mut self.description, property, &value) {
439 return result;
440 }
441 if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
442 {
443 return result;
444 }
445 match property {
446 PropertyIdentifier::PRESENT_VALUE => {
447 let PropertyValue::Real(value) = value else {
448 return Err(common::invalid_data_type_error());
449 };
450 common::reject_non_finite(value)?;
451 self.present_value = value.clamp(self.min_present_value, self.max_present_value());
452 self.evaluate(false);
453 Ok(())
454 }
455 PropertyIdentifier::OUT_OF_SERVICE => {
456 let PropertyValue::Boolean(value) = value else {
457 return Err(common::invalid_data_type_error());
458 };
459 if value == self.out_of_service {
460 return Ok(());
461 }
462 self.out_of_service = value;
463 if value {
464 self.generation = self.generation.wrapping_add(1);
465 self.pending_plan = None;
466 } else {
467 self.evaluate(true);
468 }
469 Ok(())
470 }
471 PropertyIdentifier::RELIABILITY => {
472 if !self.out_of_service {
473 return Err(common::write_access_denied_error());
474 }
475 let PropertyValue::Enumerated(raw) = value else {
476 return Err(common::invalid_data_type_error());
477 };
478 let value = Reliability::from_raw(raw);
479 if !common::is_reliability_value_valid(value) {
480 return Err(common::value_out_of_range_error());
481 }
482 self.reliability = value;
483 Ok(())
484 }
485 PropertyIdentifier::STAGES => self.replace_stages(array_index, value),
486 PropertyIdentifier::STAGE_NAMES => self.replace_stage_names(array_index, value),
487 PropertyIdentifier::TARGET_REFERENCES => {
488 self.replace_target_references(array_index, value)
489 }
490 PropertyIdentifier::PRIORITY_FOR_WRITING => {
491 let PropertyValue::Unsigned(value) = value else {
492 return Err(common::invalid_data_type_error());
493 };
494 let priority = u8::try_from(value)
495 .ok()
496 .filter(|value| (1..=16).contains(value))
497 .ok_or_else(common::value_out_of_range_error)?;
498 if priority != self.priority_for_writing {
499 self.priority_for_writing = priority;
500 self.reset_and_reevaluate();
501 }
502 Ok(())
503 }
504 PropertyIdentifier::MIN_PRES_VALUE => {
505 let PropertyValue::Real(value) = value else {
506 return Err(common::invalid_data_type_error());
507 };
508 common::reject_non_finite(value)?;
509 validate_min_present_value(value, &self.stages)?;
510 if value != self.min_present_value {
511 self.min_present_value = value;
512 self.reset_and_reevaluate();
513 }
514 Ok(())
515 }
516 _ => Err(crate::common::unhandled_write_error(
517 self.property_metadata().as_ref(),
518 property,
519 array_index,
520 )),
521 }
522 }
523
524 fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
525 Cow::Owned(self.metadata())
526 }
527
528 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
529 property_list_from_metadata(&self.metadata())
530 }
531
532 fn supports_cov(&self) -> bool {
535 true
536 }
537
538 fn cov_increment(&self) -> Option<f64> {
539 Some(f64::from(self.cov_increment))
540 }
541
542 fn take_staging_write_plan_internal(&mut self) -> Option<StagingWritePlan> {
543 if self.present_stage == 0 {
544 self.evaluate(false);
545 }
546 self.pending_plan.take()
547 }
548
549 fn staging_generation_internal(&self) -> Option<u64> {
550 Some(self.generation)
551 }
552
553 fn complete_staging_write_plan_internal(&mut self, generation: u64, success: bool) -> bool {
554 if generation != self.generation || self.out_of_service {
555 return false;
556 }
557 let reliability = if success {
558 Reliability::NO_FAULT_DETECTED
559 } else {
560 Reliability::UNRELIABLE_OTHER
561 };
562 if reliability == self.reliability {
563 return false;
564 }
565 self.reliability = reliability;
566 true
567 }
568}
569
570fn decode_stage_property_value(value: PropertyValue) -> Result<BACnetStageLimitValue, Error> {
571 let PropertyValue::ApplicationData(bytes) = value else {
572 return Err(common::invalid_data_type_error());
573 };
574 let (stage, consumed) =
575 decode_stage_limit_value(&bytes, 0).map_err(|_| common::invalid_data_encoding_error())?;
576 if consumed != bytes.len() {
577 return Err(common::invalid_data_encoding_error());
578 }
579 Ok(stage)
580}
581
582fn validate_config(config: &StagingConfig) -> Result<(), Error> {
583 common::reject_non_finite(config.present_value)?;
584 if !(1..=16).contains(&config.priority_for_writing) {
585 return Err(common::value_out_of_range_error());
586 }
587 if let Some(names) = &config.stage_names {
588 if names.len() != config.stages.len() {
589 return Err(common::value_out_of_range_error());
590 }
591 }
592 validate_target_references(&config.target_references)?;
593 validate_ladder(
594 &config.stages,
595 config.min_present_value,
596 config.target_references.len(),
597 )
598}
599
600fn validate_target_references(references: &[BACnetDeviceObjectReference]) -> Result<(), Error> {
601 for reference in references {
602 device_reference::check_local_member(reference.device_identifier)?;
607 if !matches!(
608 reference.object_identifier.object_type(),
609 ObjectType::BINARY_OUTPUT
610 | ObjectType::BINARY_VALUE
611 | ObjectType::BINARY_LIGHTING_OUTPUT
612 ) {
613 return Err(common::value_out_of_range_error());
614 }
615 }
616 Ok(())
617}
618
619fn validate_ladder(
620 stages: &[BACnetStageLimitValue],
621 min_present_value: f32,
622 target_count: usize,
623) -> Result<(), Error> {
624 if stages.len() <= 1 {
625 return Err(common::value_out_of_range_error());
626 }
627 for stage in stages {
628 if !stage.limit.is_finite() || !stage.deadband.is_finite() || stage.deadband < 0.0 {
629 return Err(common::value_out_of_range_error());
630 }
631 if stage.values.len() != target_count {
632 return Err(common::value_out_of_range_error());
633 }
634 }
635 for adjacent in stages.windows(2) {
636 if adjacent[0].limit >= adjacent[1].limit {
637 return Err(common::value_out_of_range_error());
638 }
639 if adjacent[0].limit + adjacent[0].deadband > adjacent[1].limit - adjacent[1].deadband {
640 return Err(common::value_out_of_range_error());
641 }
642 }
643 validate_min_present_value(min_present_value, stages)
644}
645
646fn validate_min_present_value(
647 min_present_value: f32,
648 stages: &[BACnetStageLimitValue],
649) -> Result<(), Error> {
650 if !min_present_value.is_finite() || min_present_value >= stages[0].limit - stages[0].deadband {
651 return Err(common::value_out_of_range_error());
652 }
653 Ok(())
654}
655
656fn protocol_error(class: ErrorClass, code: ErrorCode) -> Error {
657 Error::Protocol {
658 class: class.to_raw() as u32,
659 code: code.to_raw() as u32,
660 }
661}
662
663#[cfg(test)]
664mod tests;