1use 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
15pub 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 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 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 _ => Err(common::unknown_property_error()),
98 }
99 }
100
101 fn write_property(
102 &mut self,
103 property: PropertyIdentifier,
104 _array_index: Option<u32>,
105 value: PropertyValue,
106 _priority: Option<u8>,
107 ) -> Result<(), Error> {
108 if let Some(result) =
109 common::write_out_of_service(&mut self.out_of_service, property, &value)
110 {
111 return result;
112 }
113 if let Some(result) = common::write_description(&mut self.description, property, &value) {
114 return result;
115 }
116 match property {
117 p if p == PropertyIdentifier::PRESENT_VALUE => {
118 if let PropertyValue::Unsigned(v) = value {
119 if v as usize >= self.stage_names.len() {
120 return Err(common::value_out_of_range_error());
121 }
122 self.present_value = v;
123 Ok(())
124 } else {
125 Err(common::invalid_data_type_error())
126 }
127 }
128 _ => Err(common::write_access_denied_error()),
129 }
130 }
131
132 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
133 static PROPS: &[PropertyIdentifier] = &[
134 PropertyIdentifier::OBJECT_IDENTIFIER,
135 PropertyIdentifier::OBJECT_NAME,
136 PropertyIdentifier::DESCRIPTION,
137 PropertyIdentifier::OBJECT_TYPE,
138 PropertyIdentifier::PRESENT_VALUE,
139 PropertyIdentifier::STAGE_NAMES,
140 PropertyIdentifier::TARGET_REFERENCES,
141 PropertyIdentifier::STATUS_FLAGS,
142 PropertyIdentifier::OUT_OF_SERVICE,
143 PropertyIdentifier::RELIABILITY,
144 ];
145 Cow::Borrowed(PROPS)
146 }
147}
148
149#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn staging_create_and_read_defaults() {
159 let stg = StagingObject::new(1, "STG-1", 3).unwrap();
160 assert_eq!(stg.object_name(), "STG-1");
161 assert_eq!(
162 stg.read_property(PropertyIdentifier::PRESENT_VALUE, None)
163 .unwrap(),
164 PropertyValue::Unsigned(0)
165 );
166 }
167
168 #[test]
169 fn staging_object_type() {
170 let stg = StagingObject::new(1, "STG-1", 3).unwrap();
171 assert_eq!(
172 stg.read_property(PropertyIdentifier::OBJECT_TYPE, None)
173 .unwrap(),
174 PropertyValue::Enumerated(ObjectType::STAGING.to_raw())
175 );
176 }
177
178 #[test]
179 fn staging_read_stage_names() {
180 let stg = StagingObject::new(1, "STG-1", 3).unwrap();
181 let val = stg
182 .read_property(PropertyIdentifier::STAGE_NAMES, None)
183 .unwrap();
184 assert_eq!(
185 val,
186 PropertyValue::List(vec![
187 PropertyValue::CharacterString("Stage 0".into()),
188 PropertyValue::CharacterString("Stage 1".into()),
189 PropertyValue::CharacterString("Stage 2".into()),
190 ])
191 );
192 }
193
194 #[test]
195 fn staging_set_stage_name() {
196 let mut stg = StagingObject::new(1, "STG-1", 2).unwrap();
197 stg.set_stage_name(0, "Off");
198 stg.set_stage_name(1, "On");
199 let val = stg
200 .read_property(PropertyIdentifier::STAGE_NAMES, None)
201 .unwrap();
202 assert_eq!(
203 val,
204 PropertyValue::List(vec![
205 PropertyValue::CharacterString("Off".into()),
206 PropertyValue::CharacterString("On".into()),
207 ])
208 );
209 }
210
211 #[test]
212 fn staging_write_present_value() {
213 let mut stg = StagingObject::new(1, "STG-1", 3).unwrap();
214 stg.write_property(
215 PropertyIdentifier::PRESENT_VALUE,
216 None,
217 PropertyValue::Unsigned(2),
218 None,
219 )
220 .unwrap();
221 assert_eq!(
222 stg.read_property(PropertyIdentifier::PRESENT_VALUE, None)
223 .unwrap(),
224 PropertyValue::Unsigned(2)
225 );
226 }
227
228 #[test]
229 fn staging_write_present_value_out_of_range() {
230 let mut stg = StagingObject::new(1, "STG-1", 3).unwrap();
231 let result = stg.write_property(
232 PropertyIdentifier::PRESENT_VALUE,
233 None,
234 PropertyValue::Unsigned(5),
235 None,
236 );
237 assert!(result.is_err());
238 }
239
240 #[test]
241 fn staging_write_present_value_wrong_type() {
242 let mut stg = StagingObject::new(1, "STG-1", 3).unwrap();
243 let result = stg.write_property(
244 PropertyIdentifier::PRESENT_VALUE,
245 None,
246 PropertyValue::Real(1.0),
247 None,
248 );
249 assert!(result.is_err());
250 }
251
252 #[test]
253 fn staging_read_target_references() {
254 let stg = StagingObject::new(1, "STG-1", 2).unwrap();
255 let val = stg
256 .read_property(PropertyIdentifier::TARGET_REFERENCES, None)
257 .unwrap();
258 assert_eq!(
259 val,
260 PropertyValue::List(vec![
261 PropertyValue::OctetString(vec![]),
262 PropertyValue::OctetString(vec![]),
263 ])
264 );
265 }
266
267 #[test]
268 fn staging_property_list() {
269 let stg = StagingObject::new(1, "STG-1", 3).unwrap();
270 let list = stg.property_list();
271 assert!(list.contains(&PropertyIdentifier::PRESENT_VALUE));
272 assert!(list.contains(&PropertyIdentifier::STAGE_NAMES));
273 assert!(list.contains(&PropertyIdentifier::TARGET_REFERENCES));
274 }
275}