1use bacnet_types::enums::{ObjectType, PropertyIdentifier, Reliability};
7use bacnet_types::error::Error;
8use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
9use std::borrow::Cow;
10
11use crate::common::{self, read_common_properties};
12use crate::traits::BACnetObject;
13
14mod metadata;
15
16const PROGRAM_STATE_MAX: u32 = 5;
18
19pub struct ProgramObject {
21 oid: ObjectIdentifier,
22 name: String,
23 description: String,
24 program_state: u32,
25 program_change: u32,
26 reason_for_halt: u32,
27 status_flags: StatusFlags,
28 out_of_service: bool,
29 reliability: Reliability,
30}
31
32impl ProgramObject {
33 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
35 let oid = ObjectIdentifier::new(ObjectType::PROGRAM, instance)?;
36 Ok(Self {
37 oid,
38 name: name.into(),
39 description: String::new(),
40 program_state: 0, program_change: 0,
42 reason_for_halt: 0,
43 status_flags: StatusFlags::empty(),
44 out_of_service: false,
45 reliability: Reliability::NO_FAULT_DETECTED,
46 })
47 }
48
49 pub fn set_program_state(&mut self, state: u32) {
51 self.program_state = state;
52 }
53
54 pub fn set_reason_for_halt(&mut self, reason: u32) {
56 self.reason_for_halt = reason;
57 }
58}
59
60impl BACnetObject for ProgramObject {
61 fn object_identifier(&self) -> ObjectIdentifier {
62 self.oid
63 }
64
65 fn object_name(&self) -> &str {
66 &self.name
67 }
68
69 fn read_property(
70 &self,
71 property: PropertyIdentifier,
72 array_index: Option<u32>,
73 ) -> Result<PropertyValue, Error> {
74 if let Some(result) = read_common_properties!(self, property, array_index) {
75 return result;
76 }
77 match property {
78 p if p == PropertyIdentifier::OBJECT_TYPE => {
79 Ok(PropertyValue::Enumerated(ObjectType::PROGRAM.to_raw()))
80 }
81 p if p == PropertyIdentifier::PROGRAM_STATE => {
82 Ok(PropertyValue::Enumerated(self.program_state))
83 }
84 p if p == PropertyIdentifier::PROGRAM_CHANGE => {
85 Ok(PropertyValue::Enumerated(self.program_change))
86 }
87 p if p == PropertyIdentifier::REASON_FOR_HALT => {
88 Ok(PropertyValue::Enumerated(self.reason_for_halt))
89 }
90 _ => Err(common::unknown_property_error()),
91 }
92 }
93
94 fn write_property(
95 &mut self,
96 property: PropertyIdentifier,
97 _array_index: Option<u32>,
98 value: PropertyValue,
99 _priority: Option<u8>,
100 ) -> Result<(), Error> {
101 if let Some(result) =
102 common::write_out_of_service(&mut self.out_of_service, property, &value)
103 {
104 return result;
105 }
106 if let Some(result) = common::write_description(&mut self.description, property, &value) {
107 return result;
108 }
109 match property {
110 p if p == PropertyIdentifier::PROGRAM_CHANGE => {
111 if let PropertyValue::Enumerated(v) = value {
112 self.program_change = v;
113 Ok(())
114 } else {
115 Err(common::invalid_data_type_error())
116 }
117 }
118 p if p == PropertyIdentifier::PROGRAM_STATE => {
119 if let PropertyValue::Enumerated(v) = value {
120 if v > PROGRAM_STATE_MAX {
121 return Err(common::value_out_of_range_error());
122 }
123 self.program_state = v;
124 Ok(())
125 } else {
126 Err(common::invalid_data_type_error())
127 }
128 }
129 _ => Err(crate::common::unhandled_write_error(
130 self.property_metadata().as_ref(),
131 property,
132 _array_index,
133 )),
134 }
135 }
136
137 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
138 metadata::for_object(self)
139 }
140
141 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
142 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
143 }
144}
145
146#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn program_create_and_read_defaults() {
156 let prog = ProgramObject::new(1, "PRG-1").unwrap();
157 assert_eq!(prog.object_name(), "PRG-1");
158 assert_eq!(
159 prog.read_property(PropertyIdentifier::PROGRAM_STATE, None)
160 .unwrap(),
161 PropertyValue::Enumerated(0)
162 );
163 assert_eq!(
164 prog.read_property(PropertyIdentifier::PROGRAM_CHANGE, None)
165 .unwrap(),
166 PropertyValue::Enumerated(0)
167 );
168 assert_eq!(
169 prog.read_property(PropertyIdentifier::REASON_FOR_HALT, None)
170 .unwrap(),
171 PropertyValue::Enumerated(0)
172 );
173 }
174
175 #[test]
176 fn program_object_type() {
177 let prog = ProgramObject::new(1, "PRG-1").unwrap();
178 assert_eq!(
179 prog.read_property(PropertyIdentifier::OBJECT_TYPE, None)
180 .unwrap(),
181 PropertyValue::Enumerated(ObjectType::PROGRAM.to_raw())
182 );
183 }
184
185 #[test]
186 fn program_write_program_state() {
187 let mut prog = ProgramObject::new(1, "PRG-1").unwrap();
188 prog.write_property(
189 PropertyIdentifier::PROGRAM_STATE,
190 None,
191 PropertyValue::Enumerated(2),
192 None,
193 )
194 .unwrap();
195 assert_eq!(
196 prog.read_property(PropertyIdentifier::PROGRAM_STATE, None)
197 .unwrap(),
198 PropertyValue::Enumerated(2)
199 );
200 }
201
202 #[test]
203 fn program_write_program_state_out_of_range() {
204 let mut prog = ProgramObject::new(1, "PRG-1").unwrap();
205 let result = prog.write_property(
206 PropertyIdentifier::PROGRAM_STATE,
207 None,
208 PropertyValue::Enumerated(99),
209 None,
210 );
211 assert!(result.is_err());
212 }
213
214 #[test]
215 fn program_write_program_state_wrong_type() {
216 let mut prog = ProgramObject::new(1, "PRG-1").unwrap();
217 let result = prog.write_property(
218 PropertyIdentifier::PROGRAM_STATE,
219 None,
220 PropertyValue::Unsigned(2),
221 None,
222 );
223 assert!(result.is_err());
224 }
225
226 #[test]
227 fn program_write_program_change() {
228 let mut prog = ProgramObject::new(1, "PRG-1").unwrap();
229 prog.write_property(
230 PropertyIdentifier::PROGRAM_CHANGE,
231 None,
232 PropertyValue::Enumerated(1),
233 None,
234 )
235 .unwrap();
236 assert_eq!(
237 prog.read_property(PropertyIdentifier::PROGRAM_CHANGE, None)
238 .unwrap(),
239 PropertyValue::Enumerated(1)
240 );
241 }
242
243 #[test]
244 fn program_set_program_state() {
245 let mut prog = ProgramObject::new(1, "PRG-1").unwrap();
246 prog.set_program_state(4);
247 assert_eq!(
248 prog.read_property(PropertyIdentifier::PROGRAM_STATE, None)
249 .unwrap(),
250 PropertyValue::Enumerated(4)
251 );
252 }
253
254 #[test]
255 fn program_set_reason_for_halt() {
256 let mut prog = ProgramObject::new(1, "PRG-1").unwrap();
257 prog.set_reason_for_halt(3);
258 assert_eq!(
259 prog.read_property(PropertyIdentifier::REASON_FOR_HALT, None)
260 .unwrap(),
261 PropertyValue::Enumerated(3)
262 );
263 }
264
265 #[test]
266 fn program_property_list() {
267 let prog = ProgramObject::new(1, "PRG-1").unwrap();
268 let list = prog.property_list();
269 assert!(list.contains(&PropertyIdentifier::PROGRAM_STATE));
270 assert!(list.contains(&PropertyIdentifier::PROGRAM_CHANGE));
271 assert!(list.contains(&PropertyIdentifier::REASON_FOR_HALT));
272 }
273
274 #[test]
275 fn program_write_description() {
276 let mut prog = ProgramObject::new(1, "PRG-1").unwrap();
277 prog.write_property(
278 PropertyIdentifier::DESCRIPTION,
279 None,
280 PropertyValue::CharacterString("Test program".into()),
281 None,
282 )
283 .unwrap();
284 assert_eq!(
285 prog.read_property(PropertyIdentifier::DESCRIPTION, None)
286 .unwrap(),
287 PropertyValue::CharacterString("Test program".into())
288 );
289 }
290}