Skip to main content

bacnet_objects/
program.rs

1//! Program object (type 16) per ASHRAE 135-2020 Clause 12.22.
2//!
3//! The Program object represents an application program running within
4//! a BACnet device. It exposes the program's lifecycle state.
5
6use 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
16/// Maximum valid program state value (unloaded = 5).
17const PROGRAM_STATE_MAX: u32 = 5;
18
19/// BACnet Program object — represents an application program lifecycle.
20pub 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    /// Create a new Program object in the idle state.
34    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, // idle
41            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    /// Set the program state directly (application use).
50    pub fn set_program_state(&mut self, state: u32) {
51        self.program_state = state;
52    }
53
54    /// Set the reason for halt.
55    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// ---------------------------------------------------------------------------
147// Tests
148// ---------------------------------------------------------------------------
149
150#[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}