Skip to main content

bacnet_objects/
program.rs

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