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 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
17/// Maximum valid program state value (unloaded = 5).
18const PROGRAM_STATE_MAX: u32 = 5;
19
20/// BACnet Program object — represents an application program lifecycle.
21pub 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    /// Create a new Program object in the idle state.
35    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, // idle
42            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    /// Set the program state directly (application use).
51    pub fn set_program_state(&mut self, state: u32) {
52        self.program_state = state;
53    }
54
55    /// Set the reason for halt.
56    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// ---------------------------------------------------------------------------
148// Tests
149// ---------------------------------------------------------------------------
150
151#[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}