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