1use 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
14pub struct CommandObject {
16 oid: ObjectIdentifier,
17 name: String,
18 description: String,
19 present_value: u64,
20 in_process: bool,
21 all_writes_successful: bool,
22 action: Vec<Vec<u8>>,
23 status_flags: StatusFlags,
24 out_of_service: bool,
25 reliability: u32,
26}
27
28impl CommandObject {
29 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
31 let oid = ObjectIdentifier::new(ObjectType::COMMAND, instance)?;
32 Ok(Self {
33 oid,
34 name: name.into(),
35 description: String::new(),
36 present_value: 0,
37 in_process: false,
38 all_writes_successful: true,
39 action: Vec::new(),
40 status_flags: StatusFlags::empty(),
41 out_of_service: false,
42 reliability: 0,
43 })
44 }
45
46 pub fn set_action(&mut self, action: Vec<Vec<u8>>) {
48 self.action = action;
49 }
50}
51
52impl BACnetObject for CommandObject {
53 fn object_identifier(&self) -> ObjectIdentifier {
54 self.oid
55 }
56
57 fn object_name(&self) -> &str {
58 &self.name
59 }
60
61 fn read_property(
62 &self,
63 property: PropertyIdentifier,
64 array_index: Option<u32>,
65 ) -> Result<PropertyValue, Error> {
66 if let Some(result) = read_common_properties!(self, property, array_index) {
67 return result;
68 }
69 match property {
70 p if p == PropertyIdentifier::OBJECT_TYPE => {
71 Ok(PropertyValue::Enumerated(ObjectType::COMMAND.to_raw()))
72 }
73 p if p == PropertyIdentifier::PRESENT_VALUE => {
74 Ok(PropertyValue::Unsigned(self.present_value))
75 }
76 p if p == PropertyIdentifier::IN_PROCESS => Ok(PropertyValue::Boolean(self.in_process)),
77 p if p == PropertyIdentifier::ALL_WRITES_SUCCESSFUL => {
78 Ok(PropertyValue::Boolean(self.all_writes_successful))
79 }
80 p if p == PropertyIdentifier::ACTION => {
81 let items: Vec<PropertyValue> = self
82 .action
83 .iter()
84 .map(|a| PropertyValue::OctetString(a.clone()))
85 .collect();
86 Ok(PropertyValue::List(items))
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::PRESENT_VALUE => {
109 if let PropertyValue::Unsigned(v) = value {
110 self.present_value = v;
111 Ok(())
113 } else {
114 Err(common::invalid_data_type_error())
115 }
116 }
117 p if p == PropertyIdentifier::ACTION => {
118 Err(common::write_access_denied_error())
120 }
121 _ => Err(common::write_access_denied_error()),
122 }
123 }
124
125 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
126 static PROPS: &[PropertyIdentifier] = &[
127 PropertyIdentifier::OBJECT_IDENTIFIER,
128 PropertyIdentifier::OBJECT_NAME,
129 PropertyIdentifier::DESCRIPTION,
130 PropertyIdentifier::OBJECT_TYPE,
131 PropertyIdentifier::PRESENT_VALUE,
132 PropertyIdentifier::IN_PROCESS,
133 PropertyIdentifier::ALL_WRITES_SUCCESSFUL,
134 PropertyIdentifier::ACTION,
135 PropertyIdentifier::STATUS_FLAGS,
136 PropertyIdentifier::OUT_OF_SERVICE,
137 PropertyIdentifier::RELIABILITY,
138 ];
139 Cow::Borrowed(PROPS)
140 }
141}
142
143#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn command_create_and_read_defaults() {
153 let cmd = CommandObject::new(1, "CMD-1").unwrap();
154 assert_eq!(cmd.object_name(), "CMD-1");
155 assert_eq!(
156 cmd.read_property(PropertyIdentifier::PRESENT_VALUE, None)
157 .unwrap(),
158 PropertyValue::Unsigned(0)
159 );
160 assert_eq!(
161 cmd.read_property(PropertyIdentifier::IN_PROCESS, None)
162 .unwrap(),
163 PropertyValue::Boolean(false)
164 );
165 assert_eq!(
166 cmd.read_property(PropertyIdentifier::ALL_WRITES_SUCCESSFUL, None)
167 .unwrap(),
168 PropertyValue::Boolean(true)
169 );
170 }
171
172 #[test]
173 fn command_object_type() {
174 let cmd = CommandObject::new(1, "CMD-1").unwrap();
175 assert_eq!(
176 cmd.read_property(PropertyIdentifier::OBJECT_TYPE, None)
177 .unwrap(),
178 PropertyValue::Enumerated(ObjectType::COMMAND.to_raw())
179 );
180 }
181
182 #[test]
183 fn command_write_present_value() {
184 let mut cmd = CommandObject::new(1, "CMD-1").unwrap();
185 cmd.write_property(
186 PropertyIdentifier::PRESENT_VALUE,
187 None,
188 PropertyValue::Unsigned(3),
189 None,
190 )
191 .unwrap();
192 assert_eq!(
193 cmd.read_property(PropertyIdentifier::PRESENT_VALUE, None)
194 .unwrap(),
195 PropertyValue::Unsigned(3)
196 );
197 }
198
199 #[test]
200 fn command_write_present_value_wrong_type() {
201 let mut cmd = CommandObject::new(1, "CMD-1").unwrap();
202 let result = cmd.write_property(
203 PropertyIdentifier::PRESENT_VALUE,
204 None,
205 PropertyValue::Real(1.0),
206 None,
207 );
208 assert!(result.is_err());
209 }
210
211 #[test]
212 fn command_action_read_only() {
213 let mut cmd = CommandObject::new(1, "CMD-1").unwrap();
214 let result = cmd.write_property(
215 PropertyIdentifier::ACTION,
216 None,
217 PropertyValue::OctetString(vec![1, 2, 3]),
218 None,
219 );
220 assert!(result.is_err());
221 }
222
223 #[test]
224 fn command_read_action_empty() {
225 let cmd = CommandObject::new(1, "CMD-1").unwrap();
226 assert_eq!(
227 cmd.read_property(PropertyIdentifier::ACTION, None).unwrap(),
228 PropertyValue::List(vec![])
229 );
230 }
231
232 #[test]
233 fn command_read_action_with_data() {
234 let mut cmd = CommandObject::new(1, "CMD-1").unwrap();
235 cmd.set_action(vec![vec![1, 2, 3], vec![4, 5]]);
236 assert_eq!(
237 cmd.read_property(PropertyIdentifier::ACTION, None).unwrap(),
238 PropertyValue::List(vec![
239 PropertyValue::OctetString(vec![1, 2, 3]),
240 PropertyValue::OctetString(vec![4, 5]),
241 ])
242 );
243 }
244
245 #[test]
246 fn command_property_list() {
247 let cmd = CommandObject::new(1, "CMD-1").unwrap();
248 let list = cmd.property_list();
249 assert!(list.contains(&PropertyIdentifier::PRESENT_VALUE));
250 assert!(list.contains(&PropertyIdentifier::IN_PROCESS));
251 assert!(list.contains(&PropertyIdentifier::ALL_WRITES_SUCCESSFUL));
252 assert!(list.contains(&PropertyIdentifier::ACTION));
253 }
254}