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 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#[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}