1use bacnet_encoding::constructed::encode_action_list;
12use bacnet_types::constructed::{BACnetActionCommand, BACnetActionList};
13use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType, PropertyIdentifier, Reliability};
14use bacnet_types::error::Error;
15use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
16use bytes::BytesMut;
17use std::borrow::Cow;
18use std::sync::atomic::{AtomicU64, Ordering};
19use std::sync::Arc;
20
21use crate::common::{self, read_common_properties};
22use crate::traits::BACnetObject;
23
24mod metadata;
25
26#[doc(hidden)]
30#[derive(Debug, Clone, PartialEq)]
31pub struct CommandRun {
32 pub source: ObjectIdentifier,
34 pub generation: u64,
36 pub plan: RunPlan,
38 pub chain: Arc<[ObjectIdentifier]>,
42}
43
44pub(crate) fn next_generation() -> u64 {
48 static NEXT: AtomicU64 = AtomicU64::new(1);
49 NEXT.fetch_add(1, Ordering::Relaxed)
50}
51
52#[doc(hidden)]
54#[derive(Debug, Clone, PartialEq)]
55pub enum RunPlan {
56 Actions(Vec<BACnetActionCommand>),
59 Channel(crate::channel::ChannelDistribution),
61}
62
63#[doc(hidden)]
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum WriteFailure {
69 Configuration,
74 Process,
78 Communication,
81}
82
83impl WriteFailure {
84 pub fn reliability(self) -> Reliability {
86 match self {
87 Self::Configuration => Reliability::CONFIGURATION_ERROR,
88 Self::Process => Reliability::PROCESS_ERROR,
89 Self::Communication => Reliability::COMMUNICATION_FAILURE,
90 }
91 }
92}
93
94pub struct CommandObject {
96 oid: ObjectIdentifier,
97 name: String,
98 description: String,
99 present_value: u64,
100 in_process: bool,
101 all_writes_successful: bool,
102 action: Vec<BACnetActionList>,
103 action_text: Option<Vec<String>>,
104 status_flags: StatusFlags,
105 reliability: Reliability,
106 generation: u64,
109 running: Option<usize>,
111 pending_run: Option<CommandRun>,
112}
113
114impl CommandObject {
115 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
117 let oid = ObjectIdentifier::new(ObjectType::COMMAND, instance)?;
118 Ok(Self {
119 oid,
120 name: name.into(),
121 description: String::new(),
122 present_value: 0,
123 in_process: false,
124 all_writes_successful: true,
125 action: Vec::new(),
126 action_text: None,
127 status_flags: StatusFlags::empty(),
128 reliability: Reliability::NO_FAULT_DETECTED,
129 generation: next_generation(),
130 running: None,
131 pending_run: None,
132 })
133 }
134
135 pub fn set_action(&mut self, action: Vec<BACnetActionList>) -> Result<(), Error> {
146 let mut scratch = BytesMut::new();
147 for list in &action {
148 for command in &list.commands {
149 crate::device_reference::check_device_member(command.device_identifier)?;
150 }
151 encode_action_list(&mut scratch, list)
152 .map_err(|_| common::value_out_of_range_error())?;
153 scratch.clear();
154 }
155 if let Some(texts) = &mut self.action_text {
156 texts.resize(action.len(), String::new());
157 }
158 self.action = action;
159 self.generation = next_generation();
160 self.in_process = false;
161 self.running = None;
162 self.pending_run = None;
163 Ok(())
164 }
165
166 pub fn set_action_text(&mut self, texts: Vec<String>) -> Result<(), Error> {
172 if texts.len() != self.action.len() {
173 return Err(common::value_out_of_range_error());
174 }
175 self.action_text = Some(texts);
176 Ok(())
177 }
178
179 fn select(&mut self, selected: u64) -> Result<(), Error> {
182 if self.in_process {
185 return Err(common::protocol_error(ErrorClass::OBJECT, ErrorCode::BUSY));
186 }
187 let index = match usize::try_from(selected) {
188 Ok(0) => None,
189 Ok(n) if n <= self.action.len() => Some(n - 1),
190 _ => return Err(common::value_out_of_range_error()),
191 };
192 self.present_value = selected;
193 self.generation = next_generation();
194 let commands = index.map_or_else(Vec::new, |i| self.action[i].commands.clone());
195 if commands.is_empty() {
196 self.all_writes_successful = true;
197 return Ok(());
198 }
199 self.in_process = true;
200 self.all_writes_successful = false;
201 self.running = index;
202 self.pending_run = Some(CommandRun {
203 source: self.oid,
204 generation: self.generation,
205 plan: RunPlan::Actions(commands),
206 chain: Arc::from([]),
207 });
208 Ok(())
209 }
210}
211
212impl BACnetObject for CommandObject {
213 fn object_identifier(&self) -> ObjectIdentifier {
214 self.oid
215 }
216
217 fn object_name(&self) -> &str {
218 &self.name
219 }
220
221 fn read_property(
222 &self,
223 property: PropertyIdentifier,
224 array_index: Option<u32>,
225 ) -> Result<PropertyValue, Error> {
226 if let Some(result) =
229 read_common_properties!(self, property, array_index, no_out_of_service)
230 {
231 return result;
232 }
233 match property {
234 p if p == PropertyIdentifier::OBJECT_TYPE => {
235 Ok(PropertyValue::Enumerated(ObjectType::COMMAND.to_raw()))
236 }
237 p if p == PropertyIdentifier::PRESENT_VALUE => {
238 Ok(PropertyValue::Unsigned(self.present_value))
239 }
240 p if p == PropertyIdentifier::IN_PROCESS => Ok(PropertyValue::Boolean(self.in_process)),
241 p if p == PropertyIdentifier::ALL_WRITES_SUCCESSFUL => {
242 Ok(PropertyValue::Boolean(self.all_writes_successful))
243 }
244 p if p == PropertyIdentifier::ACTION => {
248 let lists = self
249 .action
250 .iter()
251 .map(|list| {
252 let mut encoded = BytesMut::new();
253 encode_action_list(&mut encoded, list)?;
254 Ok(PropertyValue::ApplicationData(encoded.to_vec()))
255 })
256 .collect::<Result<Vec<_>, Error>>()?;
257 common::read_array(lists, array_index)
258 }
259 p if p == PropertyIdentifier::ACTION_TEXT => match &self.action_text {
260 Some(texts) => common::read_array(
261 texts
262 .iter()
263 .cloned()
264 .map(PropertyValue::CharacterString)
265 .collect(),
266 array_index,
267 ),
268 None => Err(common::unknown_property_error()),
269 },
270 _ => Err(common::unknown_property_error()),
271 }
272 }
273
274 fn write_property(
275 &mut self,
276 property: PropertyIdentifier,
277 array_index: Option<u32>,
278 value: PropertyValue,
279 _priority: Option<u8>,
280 ) -> Result<(), Error> {
281 if let Some(result) = common::write_description(&mut self.description, property, &value) {
282 return result;
283 }
284 match property {
285 p if p == PropertyIdentifier::PRESENT_VALUE => match value {
286 PropertyValue::Unsigned(selected) => self.select(selected),
287 _ => Err(common::invalid_data_type_error()),
288 },
289 _ => Err(crate::common::unhandled_write_error(
292 self.property_metadata().as_ref(),
293 property,
294 array_index,
295 )),
296 }
297 }
298
299 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
300 metadata::for_object(self)
301 }
302
303 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
304 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
305 }
306
307 fn supports_subscribe_cov_property(&self) -> bool {
308 true
312 }
313
314 fn take_command_run_internal(&mut self) -> Option<CommandRun> {
315 self.pending_run.take()
316 }
317
318 fn command_generation_internal(&self) -> Option<u64> {
319 Some(self.generation)
320 }
321
322 fn record_command_write_internal(
323 &mut self,
324 generation: u64,
325 command: usize,
326 success: bool,
327 ) -> bool {
328 if generation != self.generation || !self.in_process {
329 return false;
330 }
331 let Some(list) = self.running.and_then(|index| self.action.get_mut(index)) else {
332 return false;
333 };
334 let Some(made) = list.commands.get_mut(command) else {
335 return false;
336 };
337 made.write_successful = success;
338 if !success && made.quit_on_failure {
341 for skipped in &mut list.commands[command + 1..] {
342 skipped.write_successful = false;
343 }
344 }
345 true
346 }
347
348 fn complete_command_run_internal(
349 &mut self,
350 generation: u64,
351 outcome: Result<(), WriteFailure>,
352 ) -> bool {
353 if generation != self.generation || !self.in_process {
354 return false;
355 }
356 self.in_process = false;
357 self.all_writes_successful = outcome.is_ok();
359 self.running = None;
360 true
361 }
362}
363
364#[cfg(test)]
365mod tests;