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