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