Skip to main content

bacnet_rs/object/
multistate.rs

1//! Multi-state Object Types Implementation
2//!
3//! This module implements the Multi-state Input, Multi-state Output, and Multi-state Value
4//! object types as defined in ASHRAE 135. These objects represent multi-position values.
5
6use crate::object::{
7    event_state::EventState, reliability::Reliability, BacnetObject, ObjectError, ObjectIdentifier,
8    ObjectType, PropertyIdentifier, PropertyValue, Result,
9};
10
11#[cfg(not(feature = "std"))]
12use alloc::{string::String, vec::Vec};
13
14/// Multi-state Input object
15#[derive(Debug, Clone)]
16pub struct MultiStateInput {
17    /// Object identifier
18    pub identifier: ObjectIdentifier,
19    /// Object name
20    pub object_name: String,
21    /// Present value (state 1..N)
22    pub present_value: u32,
23    /// Description
24    pub description: String,
25    /// Device type
26    pub device_type: String,
27    /// Status flags (4 bits: in_alarm, fault, overridden, out_of_service)
28    pub status_flags: u8,
29    /// Event state
30    pub event_state: EventState,
31    /// Reliability
32    pub reliability: Reliability,
33    /// Out of service
34    pub out_of_service: bool,
35    /// Number of states
36    pub number_of_states: u32,
37    /// State text array
38    pub state_text: Vec<String>,
39}
40
41/// Multi-state Output object
42#[derive(Debug, Clone)]
43pub struct MultiStateOutput {
44    /// Object identifier
45    pub identifier: ObjectIdentifier,
46    /// Object name
47    pub object_name: String,
48    /// Present value (state 1..N)
49    pub present_value: u32,
50    /// Description
51    pub description: String,
52    /// Device type
53    pub device_type: String,
54    /// Status flags
55    pub status_flags: u8,
56    /// Event state
57    pub event_state: EventState,
58    /// Reliability
59    pub reliability: Reliability,
60    /// Out of service
61    pub out_of_service: bool,
62    /// Number of states
63    pub number_of_states: u32,
64    /// State text array
65    pub state_text: Vec<String>,
66    /// Priority array (16 levels)
67    pub priority_array: [Option<u32>; 16],
68    /// Relinquish default
69    pub relinquish_default: u32,
70}
71
72/// Multi-state Value object
73#[derive(Debug, Clone)]
74pub struct MultiStateValue {
75    /// Object identifier
76    pub identifier: ObjectIdentifier,
77    /// Object name
78    pub object_name: String,
79    /// Present value (state 1..N)
80    pub present_value: u32,
81    /// Description
82    pub description: String,
83    /// Status flags
84    pub status_flags: u8,
85    /// Event state
86    pub event_state: EventState,
87    /// Reliability
88    pub reliability: Reliability,
89    /// Out of service
90    pub out_of_service: bool,
91    /// Number of states
92    pub number_of_states: u32,
93    /// State text array
94    pub state_text: Vec<String>,
95    /// Priority array (16 levels)
96    pub priority_array: [Option<u32>; 16],
97    /// Relinquish default
98    pub relinquish_default: u32,
99}
100
101impl MultiStateInput {
102    /// Create a new Multi-state Input object
103    pub fn new(instance: u32, object_name: String, number_of_states: u32) -> Self {
104        let mut state_text = Vec::with_capacity(number_of_states as usize);
105        for i in 1..=number_of_states {
106            state_text.push(format!("State {}", i));
107        }
108
109        Self {
110            identifier: ObjectIdentifier::new(ObjectType::MultiStateInput, instance),
111            object_name,
112            present_value: 1,
113            description: String::new(),
114            device_type: String::new(),
115            status_flags: 0,
116            event_state: EventState::Normal,
117            reliability: Reliability::NoFaultDetected,
118            out_of_service: false,
119            number_of_states,
120            state_text,
121        }
122    }
123
124    /// Set the present value (validates range)
125    pub fn set_present_value(&mut self, value: u32) -> Result<()> {
126        if value < 1 || value > self.number_of_states {
127            return Err(ObjectError::InvalidValue(format!(
128                "Value must be between 1 and {}",
129                self.number_of_states
130            )));
131        }
132        self.present_value = value;
133        Ok(())
134    }
135
136    /// Get the current state text
137    pub fn get_state_text(&self) -> Option<&str> {
138        if self.present_value > 0 && self.present_value <= self.state_text.len() as u32 {
139            Some(&self.state_text[(self.present_value - 1) as usize])
140        } else {
141            None
142        }
143    }
144
145    /// Set state text for a specific state
146    pub fn set_state_text(&mut self, state: u32, text: String) -> Result<()> {
147        if state < 1 || state > self.number_of_states {
148            return Err(ObjectError::InvalidValue(format!(
149                "State must be between 1 and {}",
150                self.number_of_states
151            )));
152        }
153        self.state_text[(state - 1) as usize] = text;
154        Ok(())
155    }
156}
157
158impl MultiStateOutput {
159    /// Create a new Multi-state Output object
160    pub fn new(instance: u32, object_name: String, number_of_states: u32) -> Self {
161        let mut state_text = Vec::with_capacity(number_of_states as usize);
162        for i in 1..=number_of_states {
163            state_text.push(format!("State {}", i));
164        }
165
166        Self {
167            identifier: ObjectIdentifier::new(ObjectType::MultiStateOutput, instance),
168            object_name,
169            present_value: 1,
170            description: String::new(),
171            device_type: String::new(),
172            status_flags: 0,
173            event_state: EventState::Normal,
174            reliability: Reliability::NoFaultDetected,
175            out_of_service: false,
176            number_of_states,
177            state_text,
178            priority_array: [None; 16],
179            relinquish_default: 1,
180        }
181    }
182
183    /// Write to priority array at specified priority level (1-16)
184    pub fn write_priority(&mut self, priority: u8, value: Option<u32>) -> Result<()> {
185        if !(1..=16).contains(&priority) {
186            return Err(ObjectError::InvalidValue(
187                "Priority must be 1-16".to_string(),
188            ));
189        }
190
191        if let Some(val) = value {
192            if val < 1 || val > self.number_of_states {
193                return Err(ObjectError::InvalidValue(format!(
194                    "Value must be between 1 and {}",
195                    self.number_of_states
196                )));
197            }
198        }
199
200        self.priority_array[(priority - 1) as usize] = value;
201        self.update_present_value();
202        Ok(())
203    }
204
205    /// Update present value based on priority array
206    fn update_present_value(&mut self) {
207        // Find highest priority non-null value
208        if let Some(value) = self.priority_array.iter().flatten().next() {
209            self.present_value = *value;
210            return;
211        }
212        // If all priorities are null, use relinquish default
213        self.present_value = self.relinquish_default;
214    }
215
216    /// Get the effective priority level for current present value
217    pub fn get_effective_priority(&self) -> Option<u8> {
218        for (i, priority_value) in self.priority_array.iter().enumerate() {
219            if priority_value.is_some() {
220                return Some((i + 1) as u8);
221            }
222        }
223        None
224    }
225}
226
227impl MultiStateValue {
228    /// Create a new Multi-state Value object
229    pub fn new(instance: u32, object_name: String, number_of_states: u32) -> Self {
230        let mut state_text = Vec::with_capacity(number_of_states as usize);
231        for i in 1..=number_of_states {
232            state_text.push(format!("State {}", i));
233        }
234
235        Self {
236            identifier: ObjectIdentifier::new(ObjectType::MultiStateValue, instance),
237            object_name,
238            present_value: 1,
239            description: String::new(),
240            status_flags: 0,
241            event_state: EventState::Normal,
242            reliability: Reliability::NoFaultDetected,
243            out_of_service: false,
244            number_of_states,
245            state_text,
246            priority_array: [None; 16],
247            relinquish_default: 1,
248        }
249    }
250
251    /// Write to priority array at specified priority level (1-16)
252    pub fn write_priority(&mut self, priority: u8, value: Option<u32>) -> Result<()> {
253        if !(1..=16).contains(&priority) {
254            return Err(ObjectError::InvalidValue(
255                "Priority must be 1-16".to_string(),
256            ));
257        }
258
259        if let Some(val) = value {
260            if val < 1 || val > self.number_of_states {
261                return Err(ObjectError::InvalidValue(format!(
262                    "Value must be between 1 and {}",
263                    self.number_of_states
264                )));
265            }
266        }
267
268        self.priority_array[(priority - 1) as usize] = value;
269        self.update_present_value();
270        Ok(())
271    }
272
273    /// Update present value based on priority array
274    fn update_present_value(&mut self) {
275        // Find highest priority non-null value
276        if let Some(value) = self.priority_array.iter().flatten().next() {
277            self.present_value = *value;
278            return;
279        }
280        // If all priorities are null, use relinquish default
281        self.present_value = self.relinquish_default;
282    }
283}
284
285impl BacnetObject for MultiStateInput {
286    fn identifier(&self) -> ObjectIdentifier {
287        self.identifier
288    }
289
290    fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue> {
291        match property {
292            PropertyIdentifier::ObjectIdentifier => {
293                Ok(PropertyValue::ObjectIdentifier(self.identifier))
294            }
295            PropertyIdentifier::ObjectName => {
296                Ok(PropertyValue::CharacterString(self.object_name.clone()))
297            }
298            PropertyIdentifier::ObjectType => Ok(PropertyValue::Enumerated(u32::from(
299                ObjectType::MultiStateInput,
300            ))),
301            PropertyIdentifier::PresentValue => {
302                Ok(PropertyValue::UnsignedInteger(self.present_value))
303            }
304            PropertyIdentifier::OutOfService => Ok(PropertyValue::Boolean(self.out_of_service)),
305            _ => Err(ObjectError::UnknownProperty),
306        }
307    }
308
309    fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()> {
310        match property {
311            PropertyIdentifier::ObjectName => {
312                if let PropertyValue::CharacterString(name) = value {
313                    self.object_name = name;
314                    Ok(())
315                } else {
316                    Err(ObjectError::InvalidPropertyType)
317                }
318            }
319            PropertyIdentifier::OutOfService => {
320                if let PropertyValue::Boolean(oos) = value {
321                    self.out_of_service = oos;
322                    Ok(())
323                } else {
324                    Err(ObjectError::InvalidPropertyType)
325                }
326            }
327            _ => Err(ObjectError::PropertyNotWritable),
328        }
329    }
330
331    fn is_property_writable(&self, property: PropertyIdentifier) -> bool {
332        matches!(
333            property,
334            PropertyIdentifier::ObjectName | PropertyIdentifier::OutOfService
335        )
336    }
337
338    fn property_list(&self) -> Vec<PropertyIdentifier> {
339        vec![
340            PropertyIdentifier::ObjectIdentifier,
341            PropertyIdentifier::ObjectName,
342            PropertyIdentifier::ObjectType,
343            PropertyIdentifier::PresentValue,
344            PropertyIdentifier::OutOfService,
345        ]
346    }
347}
348
349impl BacnetObject for MultiStateOutput {
350    fn identifier(&self) -> ObjectIdentifier {
351        self.identifier
352    }
353
354    fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue> {
355        match property {
356            PropertyIdentifier::ObjectIdentifier => {
357                Ok(PropertyValue::ObjectIdentifier(self.identifier))
358            }
359            PropertyIdentifier::ObjectName => {
360                Ok(PropertyValue::CharacterString(self.object_name.clone()))
361            }
362            PropertyIdentifier::ObjectType => Ok(PropertyValue::Enumerated(u32::from(
363                ObjectType::MultiStateOutput,
364            ))),
365            PropertyIdentifier::PresentValue => {
366                Ok(PropertyValue::UnsignedInteger(self.present_value))
367            }
368            PropertyIdentifier::OutOfService => Ok(PropertyValue::Boolean(self.out_of_service)),
369            PropertyIdentifier::PriorityArray => {
370                let array: Vec<PropertyValue> = self
371                    .priority_array
372                    .iter()
373                    .map(|&v| match v {
374                        Some(val) => PropertyValue::UnsignedInteger(val),
375                        None => PropertyValue::Null,
376                    })
377                    .collect();
378                Ok(PropertyValue::Array(array))
379            }
380            _ => Err(ObjectError::UnknownProperty),
381        }
382    }
383
384    fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()> {
385        match property {
386            PropertyIdentifier::ObjectName => {
387                if let PropertyValue::CharacterString(name) = value {
388                    self.object_name = name;
389                    Ok(())
390                } else {
391                    Err(ObjectError::InvalidPropertyType)
392                }
393            }
394            PropertyIdentifier::PresentValue => {
395                if let PropertyValue::UnsignedInteger(val) = value {
396                    // Write to priority 8 (manual operator) by default
397                    self.write_priority(8, Some(val))
398                } else {
399                    Err(ObjectError::InvalidPropertyType)
400                }
401            }
402            PropertyIdentifier::OutOfService => {
403                if let PropertyValue::Boolean(oos) = value {
404                    self.out_of_service = oos;
405                    Ok(())
406                } else {
407                    Err(ObjectError::InvalidPropertyType)
408                }
409            }
410            _ => Err(ObjectError::PropertyNotWritable),
411        }
412    }
413
414    fn is_property_writable(&self, property: PropertyIdentifier) -> bool {
415        matches!(
416            property,
417            PropertyIdentifier::ObjectName
418                | PropertyIdentifier::PresentValue
419                | PropertyIdentifier::OutOfService
420        )
421    }
422
423    fn property_list(&self) -> Vec<PropertyIdentifier> {
424        vec![
425            PropertyIdentifier::ObjectIdentifier,
426            PropertyIdentifier::ObjectName,
427            PropertyIdentifier::ObjectType,
428            PropertyIdentifier::PresentValue,
429            PropertyIdentifier::OutOfService,
430            PropertyIdentifier::PriorityArray,
431        ]
432    }
433}
434
435impl BacnetObject for MultiStateValue {
436    fn identifier(&self) -> ObjectIdentifier {
437        self.identifier
438    }
439
440    fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue> {
441        match property {
442            PropertyIdentifier::ObjectIdentifier => {
443                Ok(PropertyValue::ObjectIdentifier(self.identifier))
444            }
445            PropertyIdentifier::ObjectName => {
446                Ok(PropertyValue::CharacterString(self.object_name.clone()))
447            }
448            PropertyIdentifier::ObjectType => Ok(PropertyValue::Enumerated(u32::from(
449                ObjectType::MultiStateValue,
450            ))),
451            PropertyIdentifier::PresentValue => {
452                Ok(PropertyValue::UnsignedInteger(self.present_value))
453            }
454            PropertyIdentifier::OutOfService => Ok(PropertyValue::Boolean(self.out_of_service)),
455            PropertyIdentifier::PriorityArray => {
456                let array: Vec<PropertyValue> = self
457                    .priority_array
458                    .iter()
459                    .map(|&v| match v {
460                        Some(val) => PropertyValue::UnsignedInteger(val),
461                        None => PropertyValue::Null,
462                    })
463                    .collect();
464                Ok(PropertyValue::Array(array))
465            }
466            _ => Err(ObjectError::UnknownProperty),
467        }
468    }
469
470    fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()> {
471        match property {
472            PropertyIdentifier::ObjectName => {
473                if let PropertyValue::CharacterString(name) = value {
474                    self.object_name = name;
475                    Ok(())
476                } else {
477                    Err(ObjectError::InvalidPropertyType)
478                }
479            }
480            PropertyIdentifier::PresentValue => {
481                if let PropertyValue::UnsignedInteger(val) = value {
482                    // Write to priority 8 (manual operator) by default
483                    self.write_priority(8, Some(val))
484                } else {
485                    Err(ObjectError::InvalidPropertyType)
486                }
487            }
488            PropertyIdentifier::OutOfService => {
489                if let PropertyValue::Boolean(oos) = value {
490                    self.out_of_service = oos;
491                    Ok(())
492                } else {
493                    Err(ObjectError::InvalidPropertyType)
494                }
495            }
496            _ => Err(ObjectError::PropertyNotWritable),
497        }
498    }
499
500    fn is_property_writable(&self, property: PropertyIdentifier) -> bool {
501        matches!(
502            property,
503            PropertyIdentifier::ObjectName
504                | PropertyIdentifier::PresentValue
505                | PropertyIdentifier::OutOfService
506        )
507    }
508
509    fn property_list(&self) -> Vec<PropertyIdentifier> {
510        vec![
511            PropertyIdentifier::ObjectIdentifier,
512            PropertyIdentifier::ObjectName,
513            PropertyIdentifier::ObjectType,
514            PropertyIdentifier::PresentValue,
515            PropertyIdentifier::OutOfService,
516            PropertyIdentifier::PriorityArray,
517        ]
518    }
519}
520
521#[cfg(test)]
522mod tests {
523    use super::*;
524
525    #[test]
526    fn test_multistate_input_creation() {
527        let msi = MultiStateInput::new(1, "Mode Selector".to_string(), 5);
528        assert_eq!(msi.identifier.instance, 1);
529        assert_eq!(msi.object_name, "Mode Selector");
530        assert_eq!(msi.number_of_states, 5);
531        assert_eq!(msi.present_value, 1);
532        assert_eq!(msi.state_text.len(), 5);
533    }
534
535    #[test]
536    fn test_multistate_state_text() {
537        let mut msi = MultiStateInput::new(1, "Mode".to_string(), 3);
538
539        // Set custom state text
540        msi.set_state_text(1, "OFF".to_string()).unwrap();
541        msi.set_state_text(2, "AUTO".to_string()).unwrap();
542        msi.set_state_text(3, "MANUAL".to_string()).unwrap();
543
544        assert_eq!(msi.get_state_text(), Some("OFF"));
545
546        msi.set_present_value(2).unwrap();
547        assert_eq!(msi.get_state_text(), Some("AUTO"));
548
549        // Test invalid state
550        assert!(msi.set_present_value(4).is_err());
551    }
552
553    #[test]
554    fn test_multistate_output_priority() {
555        let mut mso = MultiStateOutput::new(1, "Sequence Control".to_string(), 4);
556
557        // Write to priority 8
558        mso.write_priority(8, Some(3)).unwrap();
559        assert_eq!(mso.present_value, 3);
560        assert_eq!(mso.get_effective_priority(), Some(8));
561
562        // Write to higher priority 3
563        mso.write_priority(3, Some(2)).unwrap();
564        assert_eq!(mso.present_value, 2);
565        assert_eq!(mso.get_effective_priority(), Some(3));
566
567        // Test invalid value
568        assert!(mso.write_priority(3, Some(5)).is_err());
569
570        // Release priority 3
571        mso.write_priority(3, None).unwrap();
572        assert_eq!(mso.present_value, 3); // Back to priority 8 value
573    }
574
575    #[test]
576    fn test_multistate_properties() {
577        let mut msv = MultiStateValue::new(1, "Operating Mode".to_string(), 4);
578
579        // Test property access
580        let name = msv.get_property(PropertyIdentifier::ObjectName).unwrap();
581        if let PropertyValue::CharacterString(n) = name {
582            assert_eq!(n, "Operating Mode");
583        } else {
584            panic!("Expected CharacterString");
585        }
586
587        // Test property modification
588        msv.set_property(
589            PropertyIdentifier::PresentValue,
590            PropertyValue::UnsignedInteger(3),
591        )
592        .unwrap();
593        assert_eq!(msv.present_value, 3);
594    }
595}