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