Skip to main content

bacnet_objects/
staging.rs

1//! Staging object (type 60) per ASHRAE 135-2020 Clause 12.
2//!
3//! The Staging object organizes a set of stages (numbered 0..N-1) with
4//! human-readable names and target references. Present value indicates
5//! the current active stage.
6
7use bacnet_types::enums::{ObjectType, PropertyIdentifier};
8use bacnet_types::error::Error;
9use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
10use std::borrow::Cow;
11
12use crate::common::{self, read_common_properties};
13use crate::traits::BACnetObject;
14
15/// BACnet Staging object — manages a set of named stages.
16pub struct StagingObject {
17    oid: ObjectIdentifier,
18    name: String,
19    description: String,
20    present_value: u64,
21    stage_names: Vec<String>,
22    target_references: Vec<Vec<u8>>,
23    status_flags: StatusFlags,
24    out_of_service: bool,
25    reliability: u32,
26}
27
28impl StagingObject {
29    /// Create a new Staging object with the given number of stages.
30    ///
31    /// Stage names default to "Stage 0", "Stage 1", etc.
32    pub fn new(instance: u32, name: impl Into<String>, num_stages: usize) -> Result<Self, Error> {
33        let oid = ObjectIdentifier::new(ObjectType::STAGING, instance)?;
34        let stage_names = (0..num_stages).map(|i| format!("Stage {i}")).collect();
35        let target_references = vec![Vec::new(); num_stages];
36        Ok(Self {
37            oid,
38            name: name.into(),
39            description: String::new(),
40            present_value: 0,
41            stage_names,
42            target_references,
43            status_flags: StatusFlags::empty(),
44            out_of_service: false,
45            reliability: 0,
46        })
47    }
48
49    /// Set a stage name by index.
50    pub fn set_stage_name(&mut self, index: usize, name: impl Into<String>) {
51        if index < self.stage_names.len() {
52            self.stage_names[index] = name.into();
53        }
54    }
55}
56
57impl BACnetObject for StagingObject {
58    fn object_identifier(&self) -> ObjectIdentifier {
59        self.oid
60    }
61
62    fn object_name(&self) -> &str {
63        &self.name
64    }
65
66    fn read_property(
67        &self,
68        property: PropertyIdentifier,
69        array_index: Option<u32>,
70    ) -> Result<PropertyValue, Error> {
71        if let Some(result) = read_common_properties!(self, property, array_index) {
72            return result;
73        }
74        match property {
75            p if p == PropertyIdentifier::OBJECT_TYPE => {
76                Ok(PropertyValue::Enumerated(ObjectType::STAGING.to_raw()))
77            }
78            p if p == PropertyIdentifier::PRESENT_VALUE => {
79                Ok(PropertyValue::Unsigned(self.present_value))
80            }
81            p if p == PropertyIdentifier::STAGE_NAMES => {
82                let items: Vec<PropertyValue> = self
83                    .stage_names
84                    .iter()
85                    .map(|s| PropertyValue::CharacterString(s.clone()))
86                    .collect();
87                Ok(PropertyValue::List(items))
88            }
89            p if p == PropertyIdentifier::TARGET_REFERENCES => {
90                let items: Vec<PropertyValue> = self
91                    .target_references
92                    .iter()
93                    .map(|r| PropertyValue::OctetString(r.clone()))
94                    .collect();
95                Ok(PropertyValue::List(items))
96            }
97            p if p == PropertyIdentifier::PRESENT_STAGE => {
98                Ok(PropertyValue::Unsigned(self.present_value))
99            }
100            p if p == PropertyIdentifier::STAGES => Ok(PropertyValue::List(
101                self.stage_names
102                    .iter()
103                    .map(|n| PropertyValue::CharacterString(n.clone()))
104                    .collect(),
105            )),
106            _ => Err(common::unknown_property_error()),
107        }
108    }
109
110    fn write_property(
111        &mut self,
112        property: PropertyIdentifier,
113        _array_index: Option<u32>,
114        value: PropertyValue,
115        _priority: Option<u8>,
116    ) -> Result<(), Error> {
117        if let Some(result) =
118            common::write_out_of_service(&mut self.out_of_service, property, &value)
119        {
120            return result;
121        }
122        if let Some(result) = common::write_description(&mut self.description, property, &value) {
123            return result;
124        }
125        match property {
126            p if p == PropertyIdentifier::PRESENT_VALUE => {
127                if let PropertyValue::Unsigned(v) = value {
128                    if v as usize >= self.stage_names.len() {
129                        return Err(common::value_out_of_range_error());
130                    }
131                    self.present_value = v;
132                    Ok(())
133                } else {
134                    Err(common::invalid_data_type_error())
135                }
136            }
137            _ => Err(common::write_access_denied_error()),
138        }
139    }
140
141    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
142        static PROPS: &[PropertyIdentifier] = &[
143            PropertyIdentifier::OBJECT_IDENTIFIER,
144            PropertyIdentifier::OBJECT_NAME,
145            PropertyIdentifier::DESCRIPTION,
146            PropertyIdentifier::OBJECT_TYPE,
147            PropertyIdentifier::PRESENT_VALUE,
148            PropertyIdentifier::STAGE_NAMES,
149            PropertyIdentifier::TARGET_REFERENCES,
150            PropertyIdentifier::STATUS_FLAGS,
151            PropertyIdentifier::OUT_OF_SERVICE,
152            PropertyIdentifier::RELIABILITY,
153        ];
154        Cow::Borrowed(PROPS)
155    }
156
157    fn supports_cov(&self) -> bool {
158        true
159    }
160}
161
162// ---------------------------------------------------------------------------
163// Tests
164// ---------------------------------------------------------------------------
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn staging_create_and_read_defaults() {
172        let stg = StagingObject::new(1, "STG-1", 3).unwrap();
173        assert_eq!(stg.object_name(), "STG-1");
174        assert_eq!(
175            stg.read_property(PropertyIdentifier::PRESENT_VALUE, None)
176                .unwrap(),
177            PropertyValue::Unsigned(0)
178        );
179    }
180
181    #[test]
182    fn staging_object_type() {
183        let stg = StagingObject::new(1, "STG-1", 3).unwrap();
184        assert_eq!(
185            stg.read_property(PropertyIdentifier::OBJECT_TYPE, None)
186                .unwrap(),
187            PropertyValue::Enumerated(ObjectType::STAGING.to_raw())
188        );
189    }
190
191    #[test]
192    fn staging_read_stage_names() {
193        let stg = StagingObject::new(1, "STG-1", 3).unwrap();
194        let val = stg
195            .read_property(PropertyIdentifier::STAGE_NAMES, None)
196            .unwrap();
197        assert_eq!(
198            val,
199            PropertyValue::List(vec![
200                PropertyValue::CharacterString("Stage 0".into()),
201                PropertyValue::CharacterString("Stage 1".into()),
202                PropertyValue::CharacterString("Stage 2".into()),
203            ])
204        );
205    }
206
207    #[test]
208    fn staging_set_stage_name() {
209        let mut stg = StagingObject::new(1, "STG-1", 2).unwrap();
210        stg.set_stage_name(0, "Off");
211        stg.set_stage_name(1, "On");
212        let val = stg
213            .read_property(PropertyIdentifier::STAGE_NAMES, None)
214            .unwrap();
215        assert_eq!(
216            val,
217            PropertyValue::List(vec![
218                PropertyValue::CharacterString("Off".into()),
219                PropertyValue::CharacterString("On".into()),
220            ])
221        );
222    }
223
224    #[test]
225    fn staging_write_present_value() {
226        let mut stg = StagingObject::new(1, "STG-1", 3).unwrap();
227        stg.write_property(
228            PropertyIdentifier::PRESENT_VALUE,
229            None,
230            PropertyValue::Unsigned(2),
231            None,
232        )
233        .unwrap();
234        assert_eq!(
235            stg.read_property(PropertyIdentifier::PRESENT_VALUE, None)
236                .unwrap(),
237            PropertyValue::Unsigned(2)
238        );
239    }
240
241    #[test]
242    fn staging_write_present_value_out_of_range() {
243        let mut stg = StagingObject::new(1, "STG-1", 3).unwrap();
244        let result = stg.write_property(
245            PropertyIdentifier::PRESENT_VALUE,
246            None,
247            PropertyValue::Unsigned(5),
248            None,
249        );
250        assert!(result.is_err());
251    }
252
253    #[test]
254    fn staging_write_present_value_wrong_type() {
255        let mut stg = StagingObject::new(1, "STG-1", 3).unwrap();
256        let result = stg.write_property(
257            PropertyIdentifier::PRESENT_VALUE,
258            None,
259            PropertyValue::Real(1.0),
260            None,
261        );
262        assert!(result.is_err());
263    }
264
265    #[test]
266    fn staging_read_target_references() {
267        let stg = StagingObject::new(1, "STG-1", 2).unwrap();
268        let val = stg
269            .read_property(PropertyIdentifier::TARGET_REFERENCES, None)
270            .unwrap();
271        assert_eq!(
272            val,
273            PropertyValue::List(vec![
274                PropertyValue::OctetString(vec![]),
275                PropertyValue::OctetString(vec![]),
276            ])
277        );
278    }
279
280    #[test]
281    fn staging_property_list() {
282        let stg = StagingObject::new(1, "STG-1", 3).unwrap();
283        let list = stg.property_list();
284        assert!(list.contains(&PropertyIdentifier::PRESENT_VALUE));
285        assert!(list.contains(&PropertyIdentifier::STAGE_NAMES));
286        assert!(list.contains(&PropertyIdentifier::TARGET_REFERENCES));
287    }
288}