Skip to main content

bacnet_objects/
analog.rs

1//! Analog Input (type 0), Analog Output (type 1), and Analog Value (type 2) objects.
2//!
3//! Per ASHRAE 135-2020 Clauses 12.1 (AI), 12.2 (AO), and 12.3 (AV).
4
5use bacnet_types::enums::{ObjectType, PropertyIdentifier};
6use bacnet_types::error::Error;
7use bacnet_types::primitives::{BACnetTimeStamp, ObjectIdentifier, PropertyValue, StatusFlags};
8use std::borrow::Cow;
9
10use crate::common::{self, read_common_properties, read_event_properties, write_event_properties};
11use crate::event::{EventStateChange, OutOfRangeDetector};
12use crate::traits::BACnetObject;
13
14// ---------------------------------------------------------------------------
15// AnalogInput (type 0)
16// ---------------------------------------------------------------------------
17
18/// BACnet Analog Input object.
19pub struct AnalogInputObject {
20    oid: ObjectIdentifier,
21    name: String,
22    description: String,
23    present_value: f32,
24    units: u32,
25    out_of_service: bool,
26    status_flags: StatusFlags,
27    /// COV_Increment: minimum change threshold for COV notifications.
28    /// Default 0.0 means notify on any write (including no-change).
29    /// Set to a positive value for delta-based filtering.
30    cov_increment: f32,
31    event_detector: OutOfRangeDetector,
32    /// Reliability: 0 = NO_FAULT_DETECTED.
33    reliability: u32,
34    /// Optional minimum present value for fault detection.
35    min_pres_value: Option<f32>,
36    /// Optional maximum present value for fault detection.
37    max_pres_value: Option<f32>,
38    /// Event_Time_Stamps[3]: to-offnormal, to-fault, to-normal.
39    event_time_stamps: [BACnetTimeStamp; 3],
40    /// Event_Message_Texts[3]: to-offnormal, to-fault, to-normal.
41    event_message_texts: [String; 3],
42}
43
44impl AnalogInputObject {
45    /// Create a new Analog Input object.
46    pub fn new(instance: u32, name: impl Into<String>, units: u32) -> Result<Self, Error> {
47        let _oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, instance)?;
48        Ok(Self {
49            oid: _oid,
50            name: name.into(),
51            description: String::new(),
52            present_value: 0.0,
53            units,
54            out_of_service: false,
55            status_flags: StatusFlags::empty(),
56            cov_increment: 0.0,
57            event_detector: OutOfRangeDetector::default(),
58            reliability: 0,
59            min_pres_value: None,
60            max_pres_value: None,
61            event_time_stamps: [
62                BACnetTimeStamp::SequenceNumber(0),
63                BACnetTimeStamp::SequenceNumber(0),
64                BACnetTimeStamp::SequenceNumber(0),
65            ],
66            event_message_texts: [String::new(), String::new(), String::new()],
67        })
68    }
69
70    /// Set the present value (used by the application to update sensor readings).
71    pub fn set_present_value(&mut self, value: f32) {
72        debug_assert!(
73            value.is_finite(),
74            "set_present_value called with non-finite value"
75        );
76        self.present_value = value;
77    }
78
79    /// Set the description string.
80    pub fn set_description(&mut self, desc: impl Into<String>) {
81        self.description = desc.into();
82    }
83
84    /// Set the minimum present value for fault detection.
85    pub fn set_min_pres_value(&mut self, value: f32) {
86        self.min_pres_value = Some(value);
87    }
88
89    /// Set the maximum present value for fault detection.
90    pub fn set_max_pres_value(&mut self, value: f32) {
91        self.max_pres_value = Some(value);
92    }
93}
94
95impl BACnetObject for AnalogInputObject {
96    fn object_identifier(&self) -> ObjectIdentifier {
97        self.oid
98    }
99
100    fn object_name(&self) -> &str {
101        &self.name
102    }
103
104    fn read_property(
105        &self,
106        property: PropertyIdentifier,
107        array_index: Option<u32>,
108    ) -> Result<PropertyValue, Error> {
109        if let Some(result) = read_common_properties!(self, property, array_index) {
110            return result;
111        }
112        if let Some(result) = read_event_properties!(self, property) {
113            return result;
114        }
115        match property {
116            p if p == PropertyIdentifier::OBJECT_TYPE => {
117                Ok(PropertyValue::Enumerated(ObjectType::ANALOG_INPUT.to_raw()))
118            }
119            p if p == PropertyIdentifier::PRESENT_VALUE => {
120                Ok(PropertyValue::Real(self.present_value))
121            }
122            p if p == PropertyIdentifier::UNITS => Ok(PropertyValue::Enumerated(self.units)),
123            p if p == PropertyIdentifier::COV_INCREMENT => {
124                Ok(PropertyValue::Real(self.cov_increment))
125            }
126            p if p == PropertyIdentifier::MIN_PRES_VALUE => match self.min_pres_value {
127                Some(v) => Ok(PropertyValue::Real(v)),
128                None => Err(common::unknown_property_error()),
129            },
130            p if p == PropertyIdentifier::MAX_PRES_VALUE => match self.max_pres_value {
131                Some(v) => Ok(PropertyValue::Real(v)),
132                None => Err(common::unknown_property_error()),
133            },
134            _ => Err(common::unknown_property_error()),
135        }
136    }
137
138    fn write_property(
139        &mut self,
140        property: PropertyIdentifier,
141        _array_index: Option<u32>,
142        value: PropertyValue,
143        _priority: Option<u8>,
144    ) -> Result<(), Error> {
145        // AI present-value is writable only when out-of-service
146        if property == PropertyIdentifier::PRESENT_VALUE {
147            if !self.out_of_service {
148                return Err(common::write_access_denied_error());
149            }
150            if let PropertyValue::Real(v) = value {
151                if !v.is_finite() {
152                    return Err(common::value_out_of_range_error());
153                }
154                self.present_value = v;
155                return Ok(());
156            }
157            return Err(common::invalid_data_type_error());
158        }
159        if let Some(result) =
160            common::write_out_of_service(&mut self.out_of_service, property, &value)
161        {
162            return result;
163        }
164        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
165            return result;
166        }
167        if let Some(result) = common::write_description(&mut self.description, property, &value) {
168            return result;
169        }
170        if property == PropertyIdentifier::RELIABILITY {
171            if let PropertyValue::Enumerated(v) = value {
172                self.reliability = v;
173                return Ok(());
174            }
175            return Err(common::invalid_data_type_error());
176        }
177        if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
178        {
179            return result;
180        }
181        if let Some(result) = write_event_properties!(self, property, value) {
182            return result;
183        }
184        Err(common::write_access_denied_error())
185    }
186
187    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
188        static PROPS: &[PropertyIdentifier] = &[
189            PropertyIdentifier::OBJECT_IDENTIFIER,
190            PropertyIdentifier::OBJECT_NAME,
191            PropertyIdentifier::DESCRIPTION,
192            PropertyIdentifier::OBJECT_TYPE,
193            PropertyIdentifier::PRESENT_VALUE,
194            PropertyIdentifier::STATUS_FLAGS,
195            PropertyIdentifier::EVENT_STATE,
196            PropertyIdentifier::OUT_OF_SERVICE,
197            PropertyIdentifier::UNITS,
198            PropertyIdentifier::COV_INCREMENT,
199            PropertyIdentifier::HIGH_LIMIT,
200            PropertyIdentifier::LOW_LIMIT,
201            PropertyIdentifier::DEADBAND,
202            PropertyIdentifier::LIMIT_ENABLE,
203            PropertyIdentifier::EVENT_ENABLE,
204            PropertyIdentifier::NOTIFY_TYPE,
205            PropertyIdentifier::NOTIFICATION_CLASS,
206            PropertyIdentifier::TIME_DELAY,
207            PropertyIdentifier::RELIABILITY,
208            PropertyIdentifier::ACKED_TRANSITIONS,
209            PropertyIdentifier::EVENT_TIME_STAMPS,
210            PropertyIdentifier::EVENT_MESSAGE_TEXTS,
211        ];
212        Cow::Borrowed(PROPS)
213    }
214
215    fn supports_cov(&self) -> bool {
216        true
217    }
218
219    fn cov_increment(&self) -> Option<f32> {
220        Some(self.cov_increment)
221    }
222
223    fn evaluate_intrinsic_reporting(&mut self) -> Option<EventStateChange> {
224        self.event_detector.evaluate(self.present_value)
225    }
226
227    fn acknowledge_alarm(&mut self, transition_bit: u8) -> Result<(), bacnet_types::error::Error> {
228        self.event_detector.acked_transitions |= transition_bit & 0x07;
229        Ok(())
230    }
231}
232
233// ---------------------------------------------------------------------------
234// AnalogOutput (type 1)
235// ---------------------------------------------------------------------------
236
237/// BACnet Analog Output object.
238pub struct AnalogOutputObject {
239    oid: ObjectIdentifier,
240    name: String,
241    description: String,
242    present_value: f32,
243    units: u32,
244    out_of_service: bool,
245    status_flags: StatusFlags,
246    priority_array: [Option<f32>; 16],
247    relinquish_default: f32,
248    /// COV_Increment: minimum change threshold for COV notifications.
249    /// Default 0.0 means notify on any write (including no-change).
250    /// Set to a positive value for delta-based filtering.
251    cov_increment: f32,
252    event_detector: OutOfRangeDetector,
253    reliability: u32,
254    min_pres_value: Option<f32>,
255    max_pres_value: Option<f32>,
256    event_time_stamps: [BACnetTimeStamp; 3],
257    event_message_texts: [String; 3],
258    /// Value source tracking.
259    value_source: common::ValueSourceTracking,
260}
261
262impl AnalogOutputObject {
263    /// Create a new Analog Output object.
264    pub fn new(instance: u32, name: impl Into<String>, units: u32) -> Result<Self, Error> {
265        let oid = ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, instance)?;
266        Ok(Self {
267            oid,
268            name: name.into(),
269            description: String::new(),
270            present_value: 0.0,
271            units,
272            out_of_service: false,
273            status_flags: StatusFlags::empty(),
274            priority_array: [None; 16],
275            relinquish_default: 0.0,
276            cov_increment: 0.0,
277            event_detector: OutOfRangeDetector::default(),
278            reliability: 0,
279            min_pres_value: None,
280            max_pres_value: None,
281            event_time_stamps: [
282                BACnetTimeStamp::SequenceNumber(0),
283                BACnetTimeStamp::SequenceNumber(0),
284                BACnetTimeStamp::SequenceNumber(0),
285            ],
286            event_message_texts: [String::new(), String::new(), String::new()],
287            value_source: common::ValueSourceTracking::default(),
288        })
289    }
290
291    /// Set the description string.
292    pub fn set_description(&mut self, desc: impl Into<String>) {
293        self.description = desc.into();
294    }
295
296    /// Set the minimum present value for fault detection.
297    pub fn set_min_pres_value(&mut self, value: f32) {
298        self.min_pres_value = Some(value);
299    }
300
301    /// Set the maximum present value for fault detection.
302    pub fn set_max_pres_value(&mut self, value: f32) {
303        self.max_pres_value = Some(value);
304    }
305
306    /// Recalculate present-value from the priority array.
307    fn recalculate_present_value(&mut self) {
308        self.present_value =
309            common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
310    }
311}
312
313impl BACnetObject for AnalogOutputObject {
314    fn object_identifier(&self) -> ObjectIdentifier {
315        self.oid
316    }
317
318    fn object_name(&self) -> &str {
319        &self.name
320    }
321
322    fn read_property(
323        &self,
324        property: PropertyIdentifier,
325        array_index: Option<u32>,
326    ) -> Result<PropertyValue, Error> {
327        if let Some(result) = read_common_properties!(self, property, array_index) {
328            return result;
329        }
330        if let Some(result) = read_event_properties!(self, property) {
331            return result;
332        }
333        match property {
334            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
335                ObjectType::ANALOG_OUTPUT.to_raw(),
336            )),
337            p if p == PropertyIdentifier::PRESENT_VALUE => {
338                Ok(PropertyValue::Real(self.present_value))
339            }
340            p if p == PropertyIdentifier::UNITS => Ok(PropertyValue::Enumerated(self.units)),
341            p if p == PropertyIdentifier::PRIORITY_ARRAY => {
342                common::read_priority_array!(self, array_index, PropertyValue::Real)
343            }
344            p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
345                Ok(PropertyValue::Real(self.relinquish_default))
346            }
347            p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
348                Ok(common::current_command_priority(&self.priority_array))
349            }
350            p if p == PropertyIdentifier::VALUE_SOURCE => {
351                Ok(self.value_source.value_source.clone())
352            }
353            p if p == PropertyIdentifier::LAST_COMMAND_TIME => Ok(PropertyValue::Unsigned(
354                match self.value_source.last_command_time {
355                    BACnetTimeStamp::SequenceNumber(n) => n,
356                    _ => 0,
357                },
358            )),
359            p if p == PropertyIdentifier::COV_INCREMENT => {
360                Ok(PropertyValue::Real(self.cov_increment))
361            }
362            p if p == PropertyIdentifier::MIN_PRES_VALUE => match self.min_pres_value {
363                Some(v) => Ok(PropertyValue::Real(v)),
364                None => Err(common::unknown_property_error()),
365            },
366            p if p == PropertyIdentifier::MAX_PRES_VALUE => match self.max_pres_value {
367                Some(v) => Ok(PropertyValue::Real(v)),
368                None => Err(common::unknown_property_error()),
369            },
370            _ => Err(common::unknown_property_error()),
371        }
372    }
373
374    fn write_property(
375        &mut self,
376        property: PropertyIdentifier,
377        array_index: Option<u32>,
378        value: PropertyValue,
379        priority: Option<u8>,
380    ) -> Result<(), Error> {
381        common::write_priority_array_direct!(self, property, array_index, value, |v| {
382            if let PropertyValue::Real(f) = v {
383                if !f.is_finite() {
384                    return Err(common::value_out_of_range_error());
385                }
386                Ok(f)
387            } else {
388                Err(common::invalid_data_type_error())
389            }
390        });
391        if property == PropertyIdentifier::PRESENT_VALUE {
392            return common::write_priority_array!(self, value, priority, |v| {
393                if let PropertyValue::Real(f) = v {
394                    if !f.is_finite() {
395                        return Err(common::value_out_of_range_error());
396                    }
397                    Ok(f)
398                } else {
399                    Err(common::invalid_data_type_error())
400                }
401            });
402        }
403        if let Some(result) =
404            common::write_out_of_service(&mut self.out_of_service, property, &value)
405        {
406            return result;
407        }
408        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
409            return result;
410        }
411        if let Some(result) = common::write_description(&mut self.description, property, &value) {
412            return result;
413        }
414        if property == PropertyIdentifier::RELIABILITY {
415            if let PropertyValue::Enumerated(v) = value {
416                self.reliability = v;
417                return Ok(());
418            }
419            return Err(common::invalid_data_type_error());
420        }
421        if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
422        {
423            return result;
424        }
425        if let Some(result) = write_event_properties!(self, property, value) {
426            return result;
427        }
428        Err(common::write_access_denied_error())
429    }
430
431    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
432        static PROPS: &[PropertyIdentifier] = &[
433            PropertyIdentifier::OBJECT_IDENTIFIER,
434            PropertyIdentifier::OBJECT_NAME,
435            PropertyIdentifier::DESCRIPTION,
436            PropertyIdentifier::OBJECT_TYPE,
437            PropertyIdentifier::PRESENT_VALUE,
438            PropertyIdentifier::STATUS_FLAGS,
439            PropertyIdentifier::EVENT_STATE,
440            PropertyIdentifier::OUT_OF_SERVICE,
441            PropertyIdentifier::UNITS,
442            PropertyIdentifier::PRIORITY_ARRAY,
443            PropertyIdentifier::RELINQUISH_DEFAULT,
444            PropertyIdentifier::CURRENT_COMMAND_PRIORITY,
445            PropertyIdentifier::COV_INCREMENT,
446            PropertyIdentifier::HIGH_LIMIT,
447            PropertyIdentifier::LOW_LIMIT,
448            PropertyIdentifier::DEADBAND,
449            PropertyIdentifier::LIMIT_ENABLE,
450            PropertyIdentifier::EVENT_ENABLE,
451            PropertyIdentifier::NOTIFY_TYPE,
452            PropertyIdentifier::NOTIFICATION_CLASS,
453            PropertyIdentifier::TIME_DELAY,
454            PropertyIdentifier::RELIABILITY,
455            PropertyIdentifier::ACKED_TRANSITIONS,
456            PropertyIdentifier::EVENT_TIME_STAMPS,
457            PropertyIdentifier::EVENT_MESSAGE_TEXTS,
458        ];
459        Cow::Borrowed(PROPS)
460    }
461
462    fn supports_cov(&self) -> bool {
463        true
464    }
465
466    fn cov_increment(&self) -> Option<f32> {
467        Some(self.cov_increment)
468    }
469
470    fn evaluate_intrinsic_reporting(&mut self) -> Option<EventStateChange> {
471        self.event_detector.evaluate(self.present_value)
472    }
473
474    fn acknowledge_alarm(&mut self, transition_bit: u8) -> Result<(), bacnet_types::error::Error> {
475        self.event_detector.acked_transitions |= transition_bit & 0x07;
476        Ok(())
477    }
478}
479
480// ---------------------------------------------------------------------------
481// AnalogValue (type 2)
482// ---------------------------------------------------------------------------
483
484/// BACnet Analog Value object.
485pub struct AnalogValueObject {
486    oid: ObjectIdentifier,
487    name: String,
488    description: String,
489    present_value: f32,
490    units: u32,
491    out_of_service: bool,
492    status_flags: StatusFlags,
493    /// 16-level priority array. `None` = no command at that level.
494    priority_array: [Option<f32>; 16],
495    relinquish_default: f32,
496    /// COV_Increment: minimum change threshold for COV notifications.
497    /// Default 0.0 means notify on any write (including no-change).
498    /// Set to a positive value for delta-based filtering.
499    cov_increment: f32,
500    event_detector: OutOfRangeDetector,
501    /// Reliability: 0 = NO_FAULT_DETECTED.
502    reliability: u32,
503    min_pres_value: Option<f32>,
504    max_pres_value: Option<f32>,
505    event_time_stamps: [BACnetTimeStamp; 3],
506    event_message_texts: [String; 3],
507}
508
509impl AnalogValueObject {
510    /// Create a new Analog Value object.
511    pub fn new(instance: u32, name: impl Into<String>, units: u32) -> Result<Self, Error> {
512        let oid = ObjectIdentifier::new(ObjectType::ANALOG_VALUE, instance)?;
513        Ok(Self {
514            oid,
515            name: name.into(),
516            description: String::new(),
517            present_value: 0.0,
518            units,
519            out_of_service: false,
520            status_flags: StatusFlags::empty(),
521            priority_array: [None; 16],
522            relinquish_default: 0.0,
523            cov_increment: 0.0,
524            event_detector: OutOfRangeDetector::default(),
525            reliability: 0,
526            min_pres_value: None,
527            max_pres_value: None,
528            event_time_stamps: [
529                BACnetTimeStamp::SequenceNumber(0),
530                BACnetTimeStamp::SequenceNumber(0),
531                BACnetTimeStamp::SequenceNumber(0),
532            ],
533            event_message_texts: [String::new(), String::new(), String::new()],
534        })
535    }
536
537    /// Set the present value directly (bypasses priority array; use when out-of-service
538    /// or for initialisation before the priority-array mechanism takes over).
539    pub fn set_present_value(&mut self, value: f32) {
540        debug_assert!(
541            value.is_finite(),
542            "set_present_value called with non-finite value"
543        );
544        self.present_value = value;
545    }
546
547    /// Set the description string.
548    pub fn set_description(&mut self, desc: impl Into<String>) {
549        self.description = desc.into();
550    }
551
552    /// Set the minimum present value for fault detection.
553    pub fn set_min_pres_value(&mut self, value: f32) {
554        self.min_pres_value = Some(value);
555    }
556
557    /// Set the maximum present value for fault detection.
558    pub fn set_max_pres_value(&mut self, value: f32) {
559        self.max_pres_value = Some(value);
560    }
561
562    /// Recalculate present-value from the priority array.
563    fn recalculate_present_value(&mut self) {
564        self.present_value =
565            common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
566    }
567}
568
569impl BACnetObject for AnalogValueObject {
570    fn object_identifier(&self) -> ObjectIdentifier {
571        self.oid
572    }
573
574    fn object_name(&self) -> &str {
575        &self.name
576    }
577
578    fn read_property(
579        &self,
580        property: PropertyIdentifier,
581        array_index: Option<u32>,
582    ) -> Result<PropertyValue, Error> {
583        if let Some(result) = read_common_properties!(self, property, array_index) {
584            return result;
585        }
586        if let Some(result) = read_event_properties!(self, property) {
587            return result;
588        }
589        match property {
590            p if p == PropertyIdentifier::OBJECT_TYPE => {
591                Ok(PropertyValue::Enumerated(ObjectType::ANALOG_VALUE.to_raw()))
592            }
593            p if p == PropertyIdentifier::PRESENT_VALUE => {
594                Ok(PropertyValue::Real(self.present_value))
595            }
596            p if p == PropertyIdentifier::UNITS => Ok(PropertyValue::Enumerated(self.units)),
597            p if p == PropertyIdentifier::PRIORITY_ARRAY => {
598                common::read_priority_array!(self, array_index, PropertyValue::Real)
599            }
600            p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
601                Ok(PropertyValue::Real(self.relinquish_default))
602            }
603            p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
604                Ok(common::current_command_priority(&self.priority_array))
605            }
606            p if p == PropertyIdentifier::COV_INCREMENT => {
607                Ok(PropertyValue::Real(self.cov_increment))
608            }
609            p if p == PropertyIdentifier::MIN_PRES_VALUE => match self.min_pres_value {
610                Some(v) => Ok(PropertyValue::Real(v)),
611                None => Err(common::unknown_property_error()),
612            },
613            p if p == PropertyIdentifier::MAX_PRES_VALUE => match self.max_pres_value {
614                Some(v) => Ok(PropertyValue::Real(v)),
615                None => Err(common::unknown_property_error()),
616            },
617            _ => Err(common::unknown_property_error()),
618        }
619    }
620
621    fn write_property(
622        &mut self,
623        property: PropertyIdentifier,
624        array_index: Option<u32>,
625        value: PropertyValue,
626        priority: Option<u8>,
627    ) -> Result<(), Error> {
628        common::write_priority_array_direct!(self, property, array_index, value, |v| {
629            if let PropertyValue::Real(f) = v {
630                if !f.is_finite() {
631                    return Err(common::value_out_of_range_error());
632                }
633                Ok(f)
634            } else {
635                Err(common::invalid_data_type_error())
636            }
637        });
638        if property == PropertyIdentifier::PRESENT_VALUE {
639            return common::write_priority_array!(self, value, priority, |v| {
640                if let PropertyValue::Real(f) = v {
641                    if !f.is_finite() {
642                        return Err(common::value_out_of_range_error());
643                    }
644                    Ok(f)
645                } else {
646                    Err(common::invalid_data_type_error())
647                }
648            });
649        }
650        if let Some(result) =
651            common::write_out_of_service(&mut self.out_of_service, property, &value)
652        {
653            return result;
654        }
655        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
656            return result;
657        }
658        if let Some(result) = common::write_description(&mut self.description, property, &value) {
659            return result;
660        }
661        if property == PropertyIdentifier::RELIABILITY {
662            if let PropertyValue::Enumerated(v) = value {
663                self.reliability = v;
664                return Ok(());
665            }
666            return Err(common::invalid_data_type_error());
667        }
668        if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
669        {
670            return result;
671        }
672        if let Some(result) = write_event_properties!(self, property, value) {
673            return result;
674        }
675        Err(common::write_access_denied_error())
676    }
677
678    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
679        static PROPS: &[PropertyIdentifier] = &[
680            PropertyIdentifier::OBJECT_IDENTIFIER,
681            PropertyIdentifier::OBJECT_NAME,
682            PropertyIdentifier::DESCRIPTION,
683            PropertyIdentifier::OBJECT_TYPE,
684            PropertyIdentifier::PRESENT_VALUE,
685            PropertyIdentifier::STATUS_FLAGS,
686            PropertyIdentifier::EVENT_STATE,
687            PropertyIdentifier::OUT_OF_SERVICE,
688            PropertyIdentifier::UNITS,
689            PropertyIdentifier::PRIORITY_ARRAY,
690            PropertyIdentifier::RELINQUISH_DEFAULT,
691            PropertyIdentifier::CURRENT_COMMAND_PRIORITY,
692            PropertyIdentifier::COV_INCREMENT,
693            PropertyIdentifier::HIGH_LIMIT,
694            PropertyIdentifier::LOW_LIMIT,
695            PropertyIdentifier::DEADBAND,
696            PropertyIdentifier::LIMIT_ENABLE,
697            PropertyIdentifier::EVENT_ENABLE,
698            PropertyIdentifier::NOTIFY_TYPE,
699            PropertyIdentifier::NOTIFICATION_CLASS,
700            PropertyIdentifier::TIME_DELAY,
701            PropertyIdentifier::RELIABILITY,
702            PropertyIdentifier::ACKED_TRANSITIONS,
703            PropertyIdentifier::EVENT_TIME_STAMPS,
704            PropertyIdentifier::EVENT_MESSAGE_TEXTS,
705        ];
706        Cow::Borrowed(PROPS)
707    }
708
709    fn supports_cov(&self) -> bool {
710        true
711    }
712
713    fn cov_increment(&self) -> Option<f32> {
714        Some(self.cov_increment)
715    }
716
717    fn evaluate_intrinsic_reporting(&mut self) -> Option<EventStateChange> {
718        self.event_detector.evaluate(self.present_value)
719    }
720
721    fn acknowledge_alarm(&mut self, transition_bit: u8) -> Result<(), bacnet_types::error::Error> {
722        self.event_detector.acked_transitions |= transition_bit & 0x07;
723        Ok(())
724    }
725}
726
727// ---------------------------------------------------------------------------
728// Tests
729// ---------------------------------------------------------------------------
730
731#[cfg(test)]
732mod tests {
733    use super::*;
734    use crate::event::LimitEnable;
735    use bacnet_types::enums::EventState;
736
737    // --- AnalogInput ---
738
739    #[test]
740    fn ai_read_present_value() {
741        let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap(); // 62 = degrees-fahrenheit
742        ai.set_present_value(72.5);
743        let val = ai
744            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
745            .unwrap();
746        assert_eq!(val, PropertyValue::Real(72.5));
747    }
748
749    #[test]
750    fn ai_read_units() {
751        let ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
752        let val = ai.read_property(PropertyIdentifier::UNITS, None).unwrap();
753        assert_eq!(val, PropertyValue::Enumerated(62));
754    }
755
756    #[test]
757    fn ai_write_present_value_denied_when_in_service() {
758        let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
759        let result = ai.write_property(
760            PropertyIdentifier::PRESENT_VALUE,
761            None,
762            PropertyValue::Real(99.0),
763            None,
764        );
765        assert!(result.is_err());
766    }
767
768    #[test]
769    fn ai_write_present_value_allowed_when_out_of_service() {
770        let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
771        ai.write_property(
772            PropertyIdentifier::OUT_OF_SERVICE,
773            None,
774            PropertyValue::Boolean(true),
775            None,
776        )
777        .unwrap();
778        ai.write_property(
779            PropertyIdentifier::PRESENT_VALUE,
780            None,
781            PropertyValue::Real(99.0),
782            None,
783        )
784        .unwrap();
785        let val = ai
786            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
787            .unwrap();
788        assert_eq!(val, PropertyValue::Real(99.0));
789    }
790
791    #[test]
792    fn ai_read_unknown_property() {
793        let ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
794        let result = ai.read_property(PropertyIdentifier::PRIORITY_ARRAY, None);
795        assert!(result.is_err());
796    }
797
798    // --- AnalogOutput ---
799
800    #[test]
801    fn ao_write_with_priority() {
802        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
803
804        // Write at priority 8
805        ao.write_property(
806            PropertyIdentifier::PRESENT_VALUE,
807            None,
808            PropertyValue::Real(50.0),
809            Some(8),
810        )
811        .unwrap();
812
813        let val = ao
814            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
815            .unwrap();
816        assert_eq!(val, PropertyValue::Real(50.0));
817
818        // Priority array at index 8 should have the value
819        let slot = ao
820            .read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(8))
821            .unwrap();
822        assert_eq!(slot, PropertyValue::Real(50.0));
823
824        // Priority array at index 1 should be Null
825        let slot = ao
826            .read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(1))
827            .unwrap();
828        assert_eq!(slot, PropertyValue::Null);
829    }
830
831    #[test]
832    fn ao_relinquish_falls_to_default() {
833        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
834
835        // Write at priority 16 (lowest)
836        ao.write_property(
837            PropertyIdentifier::PRESENT_VALUE,
838            None,
839            PropertyValue::Real(75.0),
840            Some(16),
841        )
842        .unwrap();
843        assert_eq!(
844            ao.read_property(PropertyIdentifier::PRESENT_VALUE, None)
845                .unwrap(),
846            PropertyValue::Real(75.0)
847        );
848
849        // Relinquish (write Null)
850        ao.write_property(
851            PropertyIdentifier::PRESENT_VALUE,
852            None,
853            PropertyValue::Null,
854            Some(16),
855        )
856        .unwrap();
857
858        // Should fall back to relinquish-default (0.0)
859        assert_eq!(
860            ao.read_property(PropertyIdentifier::PRESENT_VALUE, None)
861                .unwrap(),
862            PropertyValue::Real(0.0)
863        );
864    }
865
866    #[test]
867    fn ao_higher_priority_wins() {
868        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
869
870        ao.write_property(
871            PropertyIdentifier::PRESENT_VALUE,
872            None,
873            PropertyValue::Real(10.0),
874            Some(16),
875        )
876        .unwrap();
877        ao.write_property(
878            PropertyIdentifier::PRESENT_VALUE,
879            None,
880            PropertyValue::Real(90.0),
881            Some(8),
882        )
883        .unwrap();
884
885        // Priority 8 wins over 16
886        assert_eq!(
887            ao.read_property(PropertyIdentifier::PRESENT_VALUE, None)
888                .unwrap(),
889            PropertyValue::Real(90.0)
890        );
891    }
892
893    // --- Intrinsic Reporting ---
894
895    #[test]
896    fn ai_read_event_state_default_normal() {
897        let ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
898        let val = ai
899            .read_property(PropertyIdentifier::EVENT_STATE, None)
900            .unwrap();
901        assert_eq!(val, PropertyValue::Enumerated(EventState::NORMAL.to_raw()));
902    }
903
904    #[test]
905    fn ai_read_write_high_limit() {
906        let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
907        ai.write_property(
908            PropertyIdentifier::HIGH_LIMIT,
909            None,
910            PropertyValue::Real(85.0),
911            None,
912        )
913        .unwrap();
914        assert_eq!(
915            ai.read_property(PropertyIdentifier::HIGH_LIMIT, None)
916                .unwrap(),
917            PropertyValue::Real(85.0)
918        );
919    }
920
921    #[test]
922    fn ai_read_write_low_limit() {
923        let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
924        ai.write_property(
925            PropertyIdentifier::LOW_LIMIT,
926            None,
927            PropertyValue::Real(15.0),
928            None,
929        )
930        .unwrap();
931        assert_eq!(
932            ai.read_property(PropertyIdentifier::LOW_LIMIT, None)
933                .unwrap(),
934            PropertyValue::Real(15.0)
935        );
936    }
937
938    #[test]
939    fn ai_read_write_deadband() {
940        let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
941        ai.write_property(
942            PropertyIdentifier::DEADBAND,
943            None,
944            PropertyValue::Real(2.5),
945            None,
946        )
947        .unwrap();
948        assert_eq!(
949            ai.read_property(PropertyIdentifier::DEADBAND, None)
950                .unwrap(),
951            PropertyValue::Real(2.5)
952        );
953    }
954
955    #[test]
956    fn ai_deadband_reject_negative() {
957        let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
958        let result = ai.write_property(
959            PropertyIdentifier::DEADBAND,
960            None,
961            PropertyValue::Real(-1.0),
962            None,
963        );
964        assert!(result.is_err());
965    }
966
967    #[test]
968    fn ai_read_write_limit_enable() {
969        let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
970        let enable_both = LimitEnable::BOTH.to_bits();
971        ai.write_property(
972            PropertyIdentifier::LIMIT_ENABLE,
973            None,
974            PropertyValue::BitString {
975                unused_bits: 6,
976                data: vec![enable_both],
977            },
978            None,
979        )
980        .unwrap();
981        let val = ai
982            .read_property(PropertyIdentifier::LIMIT_ENABLE, None)
983            .unwrap();
984        if let PropertyValue::BitString { data, .. } = val {
985            let le = LimitEnable::from_bits(data[0]);
986            assert!(le.low_limit_enable);
987            assert!(le.high_limit_enable);
988        } else {
989            panic!("Expected BitString");
990        }
991    }
992
993    #[test]
994    fn ai_intrinsic_reporting_triggers_on_present_value_change() {
995        let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
996        // Configure: high=80, low=20, deadband=2, both limits enabled
997        ai.write_property(
998            PropertyIdentifier::HIGH_LIMIT,
999            None,
1000            PropertyValue::Real(80.0),
1001            None,
1002        )
1003        .unwrap();
1004        ai.write_property(
1005            PropertyIdentifier::LOW_LIMIT,
1006            None,
1007            PropertyValue::Real(20.0),
1008            None,
1009        )
1010        .unwrap();
1011        ai.write_property(
1012            PropertyIdentifier::DEADBAND,
1013            None,
1014            PropertyValue::Real(2.0),
1015            None,
1016        )
1017        .unwrap();
1018        ai.write_property(
1019            PropertyIdentifier::LIMIT_ENABLE,
1020            None,
1021            PropertyValue::BitString {
1022                unused_bits: 6,
1023                data: vec![LimitEnable::BOTH.to_bits()],
1024            },
1025            None,
1026        )
1027        .unwrap();
1028        ai.write_property(
1029            PropertyIdentifier::EVENT_ENABLE,
1030            None,
1031            PropertyValue::BitString {
1032                unused_bits: 5,
1033                data: vec![0x07 << 5], // all transitions enabled
1034            },
1035            None,
1036        )
1037        .unwrap();
1038
1039        // Normal value — no transition
1040        ai.set_present_value(50.0);
1041        assert!(ai.evaluate_intrinsic_reporting().is_none());
1042
1043        // Go above high limit
1044        ai.set_present_value(81.0);
1045        let change = ai.evaluate_intrinsic_reporting().unwrap();
1046        assert_eq!(change.from, EventState::NORMAL);
1047        assert_eq!(change.to, EventState::HIGH_LIMIT);
1048
1049        // Verify event_state property reads correctly
1050        assert_eq!(
1051            ai.read_property(PropertyIdentifier::EVENT_STATE, None)
1052                .unwrap(),
1053            PropertyValue::Enumerated(EventState::HIGH_LIMIT.to_raw())
1054        );
1055
1056        // Drop below deadband threshold → back to NORMAL
1057        ai.set_present_value(77.0);
1058        let change = ai.evaluate_intrinsic_reporting().unwrap();
1059        assert_eq!(change.to, EventState::NORMAL);
1060    }
1061
1062    #[test]
1063    fn ao_intrinsic_reporting_after_priority_write() {
1064        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1065        ao.write_property(
1066            PropertyIdentifier::HIGH_LIMIT,
1067            None,
1068            PropertyValue::Real(80.0),
1069            None,
1070        )
1071        .unwrap();
1072        ao.write_property(
1073            PropertyIdentifier::LOW_LIMIT,
1074            None,
1075            PropertyValue::Real(20.0),
1076            None,
1077        )
1078        .unwrap();
1079        ao.write_property(
1080            PropertyIdentifier::DEADBAND,
1081            None,
1082            PropertyValue::Real(2.0),
1083            None,
1084        )
1085        .unwrap();
1086        ao.write_property(
1087            PropertyIdentifier::LIMIT_ENABLE,
1088            None,
1089            PropertyValue::BitString {
1090                unused_bits: 6,
1091                data: vec![LimitEnable::BOTH.to_bits()],
1092            },
1093            None,
1094        )
1095        .unwrap();
1096        ao.write_property(
1097            PropertyIdentifier::EVENT_ENABLE,
1098            None,
1099            PropertyValue::BitString {
1100                unused_bits: 5,
1101                data: vec![0x07 << 5], // all transitions enabled
1102            },
1103            None,
1104        )
1105        .unwrap();
1106
1107        // Write a high value via priority array
1108        ao.write_property(
1109            PropertyIdentifier::PRESENT_VALUE,
1110            None,
1111            PropertyValue::Real(85.0),
1112            Some(8),
1113        )
1114        .unwrap();
1115        let change = ao.evaluate_intrinsic_reporting().unwrap();
1116        assert_eq!(change.to, EventState::HIGH_LIMIT);
1117    }
1118
1119    #[test]
1120    fn ai_read_reliability_default() {
1121        let ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
1122        let val = ai
1123            .read_property(PropertyIdentifier::RELIABILITY, None)
1124            .unwrap();
1125        assert_eq!(val, PropertyValue::Enumerated(0)); // NO_FAULT_DETECTED
1126    }
1127
1128    #[test]
1129    fn ai_description_read_write() {
1130        let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
1131        // Default description is empty
1132        let val = ai
1133            .read_property(PropertyIdentifier::DESCRIPTION, None)
1134            .unwrap();
1135        assert_eq!(val, PropertyValue::CharacterString(String::new()));
1136        // Write a description
1137        ai.write_property(
1138            PropertyIdentifier::DESCRIPTION,
1139            None,
1140            PropertyValue::CharacterString("Zone temperature sensor".into()),
1141            None,
1142        )
1143        .unwrap();
1144        let val = ai
1145            .read_property(PropertyIdentifier::DESCRIPTION, None)
1146            .unwrap();
1147        assert_eq!(
1148            val,
1149            PropertyValue::CharacterString("Zone temperature sensor".into())
1150        );
1151    }
1152
1153    #[test]
1154    fn ai_set_description_convenience() {
1155        let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
1156        ai.set_description("Supply air temperature");
1157        assert_eq!(
1158            ai.read_property(PropertyIdentifier::DESCRIPTION, None)
1159                .unwrap(),
1160            PropertyValue::CharacterString("Supply air temperature".into())
1161        );
1162    }
1163
1164    #[test]
1165    fn ai_description_in_property_list() {
1166        let ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
1167        assert!(ai
1168            .property_list()
1169            .contains(&PropertyIdentifier::DESCRIPTION));
1170    }
1171
1172    #[test]
1173    fn ao_description_read_write() {
1174        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1175        ao.write_property(
1176            PropertyIdentifier::DESCRIPTION,
1177            None,
1178            PropertyValue::CharacterString("Chilled water valve".into()),
1179            None,
1180        )
1181        .unwrap();
1182        assert_eq!(
1183            ao.read_property(PropertyIdentifier::DESCRIPTION, None)
1184                .unwrap(),
1185            PropertyValue::CharacterString("Chilled water valve".into())
1186        );
1187    }
1188
1189    #[test]
1190    fn ao_description_in_property_list() {
1191        let ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1192        assert!(ao
1193            .property_list()
1194            .contains(&PropertyIdentifier::DESCRIPTION));
1195    }
1196
1197    #[test]
1198    fn ao_read_reliability_default() {
1199        let ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1200        let val = ao
1201            .read_property(PropertyIdentifier::RELIABILITY, None)
1202            .unwrap();
1203        assert_eq!(val, PropertyValue::Enumerated(0)); // NO_FAULT_DETECTED
1204    }
1205
1206    // --- Priority array bounds tests ---
1207
1208    #[test]
1209    fn ao_priority_array_index_zero_returns_size() {
1210        let ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1211        let val = ao
1212            .read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(0))
1213            .unwrap();
1214        assert_eq!(val, PropertyValue::Unsigned(16));
1215    }
1216
1217    #[test]
1218    fn ao_priority_array_index_out_of_bounds() {
1219        let ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1220        // Index 17 is out of bounds (valid: 0-16)
1221        let result = ao.read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(17));
1222        assert!(result.is_err());
1223    }
1224
1225    #[test]
1226    fn ao_priority_array_index_far_out_of_bounds() {
1227        let ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1228        // Large index well beyond valid range
1229        let result = ao.read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(100));
1230        assert!(result.is_err());
1231    }
1232
1233    #[test]
1234    fn ao_priority_array_index_u32_max_out_of_bounds() {
1235        let ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1236        let result = ao.read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(u32::MAX));
1237        assert!(result.is_err());
1238    }
1239
1240    // --- WriteProperty with invalid priority tests ---
1241
1242    #[test]
1243    fn ao_write_with_priority_zero_rejected() {
1244        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1245        // Priority 0 is invalid (valid range is 1-16)
1246        let result = ao.write_property(
1247            PropertyIdentifier::PRESENT_VALUE,
1248            None,
1249            PropertyValue::Real(50.0),
1250            Some(0),
1251        );
1252        assert!(result.is_err());
1253    }
1254
1255    #[test]
1256    fn ao_write_with_priority_17_rejected() {
1257        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1258        // Priority 17 is invalid (valid range is 1-16)
1259        let result = ao.write_property(
1260            PropertyIdentifier::PRESENT_VALUE,
1261            None,
1262            PropertyValue::Real(50.0),
1263            Some(17),
1264        );
1265        assert!(result.is_err());
1266    }
1267
1268    #[test]
1269    fn ao_write_with_priority_255_rejected() {
1270        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1271        // Priority 255 is invalid
1272        let result = ao.write_property(
1273            PropertyIdentifier::PRESENT_VALUE,
1274            None,
1275            PropertyValue::Real(50.0),
1276            Some(255),
1277        );
1278        assert!(result.is_err());
1279    }
1280
1281    #[test]
1282    fn ao_write_with_all_valid_priorities() {
1283        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1284        // All priorities 1 through 16 should succeed
1285        for prio in 1..=16u8 {
1286            ao.write_property(
1287                PropertyIdentifier::PRESENT_VALUE,
1288                None,
1289                PropertyValue::Real(prio as f32),
1290                Some(prio),
1291            )
1292            .unwrap();
1293        }
1294        // Present value should be the highest priority (priority 1)
1295        let val = ao
1296            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
1297            .unwrap();
1298        assert_eq!(val, PropertyValue::Real(1.0));
1299    }
1300
1301    #[test]
1302    fn ao_priority_array_read_all_slots_none_by_default() {
1303        let ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1304        // Read entire array (no index)
1305        let val = ao
1306            .read_property(PropertyIdentifier::PRIORITY_ARRAY, None)
1307            .unwrap();
1308        if let PropertyValue::List(elements) = val {
1309            assert_eq!(elements.len(), 16);
1310            for elem in &elements {
1311                assert_eq!(elem, &PropertyValue::Null);
1312            }
1313        } else {
1314            panic!("Expected List for priority array without index");
1315        }
1316    }
1317
1318    // --- Direct PRIORITY_ARRAY writes ---
1319
1320    #[test]
1321    fn ao_direct_priority_array_write_value() {
1322        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1323        // Write directly to PRIORITY_ARRAY[5]
1324        ao.write_property(
1325            PropertyIdentifier::PRIORITY_ARRAY,
1326            Some(5),
1327            PropertyValue::Real(42.0),
1328            None,
1329        )
1330        .unwrap();
1331        // present_value should reflect the written value
1332        assert_eq!(
1333            ao.read_property(PropertyIdentifier::PRESENT_VALUE, None)
1334                .unwrap(),
1335            PropertyValue::Real(42.0)
1336        );
1337        // Slot 5 should have the value
1338        assert_eq!(
1339            ao.read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(5))
1340                .unwrap(),
1341            PropertyValue::Real(42.0)
1342        );
1343    }
1344
1345    #[test]
1346    fn ao_direct_priority_array_relinquish() {
1347        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1348        // Write a value at priority 5
1349        ao.write_property(
1350            PropertyIdentifier::PRIORITY_ARRAY,
1351            Some(5),
1352            PropertyValue::Real(42.0),
1353            None,
1354        )
1355        .unwrap();
1356        // Relinquish with Null
1357        ao.write_property(
1358            PropertyIdentifier::PRIORITY_ARRAY,
1359            Some(5),
1360            PropertyValue::Null,
1361            None,
1362        )
1363        .unwrap();
1364        // Should fall back to relinquish default (0.0)
1365        assert_eq!(
1366            ao.read_property(PropertyIdentifier::PRESENT_VALUE, None)
1367                .unwrap(),
1368            PropertyValue::Real(0.0)
1369        );
1370        assert_eq!(
1371            ao.read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(5))
1372                .unwrap(),
1373            PropertyValue::Null
1374        );
1375    }
1376
1377    #[test]
1378    fn ao_direct_priority_array_no_index_error() {
1379        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1380        // Writing PRIORITY_ARRAY without array_index should error
1381        let result = ao.write_property(
1382            PropertyIdentifier::PRIORITY_ARRAY,
1383            None,
1384            PropertyValue::Real(42.0),
1385            None,
1386        );
1387        assert!(result.is_err());
1388    }
1389
1390    #[test]
1391    fn ao_direct_priority_array_index_zero_error() {
1392        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1393        let result = ao.write_property(
1394            PropertyIdentifier::PRIORITY_ARRAY,
1395            Some(0),
1396            PropertyValue::Real(42.0),
1397            None,
1398        );
1399        assert!(result.is_err());
1400    }
1401
1402    #[test]
1403    fn ao_direct_priority_array_index_17_error() {
1404        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
1405        let result = ao.write_property(
1406            PropertyIdentifier::PRIORITY_ARRAY,
1407            Some(17),
1408            PropertyValue::Real(42.0),
1409            None,
1410        );
1411        assert!(result.is_err());
1412    }
1413
1414    // --- AnalogValue ---
1415
1416    #[test]
1417    fn av_read_present_value_default() {
1418        let av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1419        let val = av
1420            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
1421            .unwrap();
1422        assert_eq!(val, PropertyValue::Real(0.0));
1423    }
1424
1425    #[test]
1426    fn av_set_present_value() {
1427        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1428        av.set_present_value(42.5);
1429        let val = av
1430            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
1431            .unwrap();
1432        assert_eq!(val, PropertyValue::Real(42.5));
1433    }
1434
1435    #[test]
1436    fn av_read_object_type_returns_analog_value() {
1437        let av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1438        let val = av
1439            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
1440            .unwrap();
1441        assert_eq!(
1442            val,
1443            PropertyValue::Enumerated(ObjectType::ANALOG_VALUE.to_raw())
1444        );
1445    }
1446
1447    #[test]
1448    fn av_read_units() {
1449        let av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1450        let val = av.read_property(PropertyIdentifier::UNITS, None).unwrap();
1451        assert_eq!(val, PropertyValue::Enumerated(62));
1452    }
1453
1454    #[test]
1455    fn av_write_with_priority() {
1456        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1457
1458        // Write at priority 8
1459        av.write_property(
1460            PropertyIdentifier::PRESENT_VALUE,
1461            None,
1462            PropertyValue::Real(55.0),
1463            Some(8),
1464        )
1465        .unwrap();
1466
1467        let val = av
1468            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
1469            .unwrap();
1470        assert_eq!(val, PropertyValue::Real(55.0));
1471
1472        // Priority array at index 8 should have the value
1473        let slot = av
1474            .read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(8))
1475            .unwrap();
1476        assert_eq!(slot, PropertyValue::Real(55.0));
1477
1478        // Priority array at index 1 should be Null
1479        let slot = av
1480            .read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(1))
1481            .unwrap();
1482        assert_eq!(slot, PropertyValue::Null);
1483    }
1484
1485    #[test]
1486    fn av_relinquish_falls_to_default() {
1487        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1488
1489        // Write at priority 16 (lowest)
1490        av.write_property(
1491            PropertyIdentifier::PRESENT_VALUE,
1492            None,
1493            PropertyValue::Real(75.0),
1494            Some(16),
1495        )
1496        .unwrap();
1497        assert_eq!(
1498            av.read_property(PropertyIdentifier::PRESENT_VALUE, None)
1499                .unwrap(),
1500            PropertyValue::Real(75.0)
1501        );
1502
1503        // Relinquish (write Null)
1504        av.write_property(
1505            PropertyIdentifier::PRESENT_VALUE,
1506            None,
1507            PropertyValue::Null,
1508            Some(16),
1509        )
1510        .unwrap();
1511
1512        // Should fall back to relinquish-default (0.0)
1513        assert_eq!(
1514            av.read_property(PropertyIdentifier::PRESENT_VALUE, None)
1515                .unwrap(),
1516            PropertyValue::Real(0.0)
1517        );
1518    }
1519
1520    #[test]
1521    fn av_higher_priority_wins() {
1522        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1523
1524        av.write_property(
1525            PropertyIdentifier::PRESENT_VALUE,
1526            None,
1527            PropertyValue::Real(10.0),
1528            Some(16),
1529        )
1530        .unwrap();
1531        av.write_property(
1532            PropertyIdentifier::PRESENT_VALUE,
1533            None,
1534            PropertyValue::Real(90.0),
1535            Some(8),
1536        )
1537        .unwrap();
1538
1539        // Priority 8 wins over 16
1540        assert_eq!(
1541            av.read_property(PropertyIdentifier::PRESENT_VALUE, None)
1542                .unwrap(),
1543            PropertyValue::Real(90.0)
1544        );
1545    }
1546
1547    #[test]
1548    fn av_priority_array_read_all_slots_none_by_default() {
1549        let av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1550        let val = av
1551            .read_property(PropertyIdentifier::PRIORITY_ARRAY, None)
1552            .unwrap();
1553        if let PropertyValue::List(elements) = val {
1554            assert_eq!(elements.len(), 16);
1555            for elem in &elements {
1556                assert_eq!(elem, &PropertyValue::Null);
1557            }
1558        } else {
1559            panic!("Expected List for priority array without index");
1560        }
1561    }
1562
1563    #[test]
1564    fn av_priority_array_index_zero_returns_size() {
1565        let av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1566        let val = av
1567            .read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(0))
1568            .unwrap();
1569        assert_eq!(val, PropertyValue::Unsigned(16));
1570    }
1571
1572    #[test]
1573    fn av_priority_array_index_out_of_bounds() {
1574        let av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1575        let result = av.read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(17));
1576        assert!(result.is_err());
1577    }
1578
1579    #[test]
1580    fn av_priority_array_index_u32_max_out_of_bounds() {
1581        let av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1582        let result = av.read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(u32::MAX));
1583        assert!(result.is_err());
1584    }
1585
1586    #[test]
1587    fn av_write_with_priority_zero_rejected() {
1588        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1589        let result = av.write_property(
1590            PropertyIdentifier::PRESENT_VALUE,
1591            None,
1592            PropertyValue::Real(50.0),
1593            Some(0),
1594        );
1595        assert!(result.is_err());
1596    }
1597
1598    #[test]
1599    fn av_write_with_priority_17_rejected() {
1600        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1601        let result = av.write_property(
1602            PropertyIdentifier::PRESENT_VALUE,
1603            None,
1604            PropertyValue::Real(50.0),
1605            Some(17),
1606        );
1607        assert!(result.is_err());
1608    }
1609
1610    #[test]
1611    fn av_write_with_all_valid_priorities() {
1612        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1613        for prio in 1..=16u8 {
1614            av.write_property(
1615                PropertyIdentifier::PRESENT_VALUE,
1616                None,
1617                PropertyValue::Real(prio as f32),
1618                Some(prio),
1619            )
1620            .unwrap();
1621        }
1622        // Present value should be the highest priority (priority 1)
1623        let val = av
1624            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
1625            .unwrap();
1626        assert_eq!(val, PropertyValue::Real(1.0));
1627    }
1628
1629    #[test]
1630    fn av_direct_priority_array_write_value() {
1631        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1632        av.write_property(
1633            PropertyIdentifier::PRIORITY_ARRAY,
1634            Some(5),
1635            PropertyValue::Real(42.0),
1636            None,
1637        )
1638        .unwrap();
1639        assert_eq!(
1640            av.read_property(PropertyIdentifier::PRESENT_VALUE, None)
1641                .unwrap(),
1642            PropertyValue::Real(42.0)
1643        );
1644        assert_eq!(
1645            av.read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(5))
1646                .unwrap(),
1647            PropertyValue::Real(42.0)
1648        );
1649    }
1650
1651    #[test]
1652    fn av_direct_priority_array_relinquish() {
1653        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1654        av.write_property(
1655            PropertyIdentifier::PRIORITY_ARRAY,
1656            Some(5),
1657            PropertyValue::Real(42.0),
1658            None,
1659        )
1660        .unwrap();
1661        av.write_property(
1662            PropertyIdentifier::PRIORITY_ARRAY,
1663            Some(5),
1664            PropertyValue::Null,
1665            None,
1666        )
1667        .unwrap();
1668        assert_eq!(
1669            av.read_property(PropertyIdentifier::PRESENT_VALUE, None)
1670                .unwrap(),
1671            PropertyValue::Real(0.0)
1672        );
1673        assert_eq!(
1674            av.read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(5))
1675                .unwrap(),
1676            PropertyValue::Null
1677        );
1678    }
1679
1680    #[test]
1681    fn av_direct_priority_array_no_index_error() {
1682        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1683        let result = av.write_property(
1684            PropertyIdentifier::PRIORITY_ARRAY,
1685            None,
1686            PropertyValue::Real(42.0),
1687            None,
1688        );
1689        assert!(result.is_err());
1690    }
1691
1692    #[test]
1693    fn av_direct_priority_array_index_zero_error() {
1694        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1695        let result = av.write_property(
1696            PropertyIdentifier::PRIORITY_ARRAY,
1697            Some(0),
1698            PropertyValue::Real(42.0),
1699            None,
1700        );
1701        assert!(result.is_err());
1702    }
1703
1704    #[test]
1705    fn av_direct_priority_array_index_17_error() {
1706        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1707        let result = av.write_property(
1708            PropertyIdentifier::PRIORITY_ARRAY,
1709            Some(17),
1710            PropertyValue::Real(42.0),
1711            None,
1712        );
1713        assert!(result.is_err());
1714    }
1715
1716    #[test]
1717    fn av_intrinsic_reporting_normal_to_high_limit_to_normal() {
1718        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1719        // Configure: high=80, low=20, deadband=2, both limits enabled
1720        av.write_property(
1721            PropertyIdentifier::HIGH_LIMIT,
1722            None,
1723            PropertyValue::Real(80.0),
1724            None,
1725        )
1726        .unwrap();
1727        av.write_property(
1728            PropertyIdentifier::LOW_LIMIT,
1729            None,
1730            PropertyValue::Real(20.0),
1731            None,
1732        )
1733        .unwrap();
1734        av.write_property(
1735            PropertyIdentifier::DEADBAND,
1736            None,
1737            PropertyValue::Real(2.0),
1738            None,
1739        )
1740        .unwrap();
1741        av.write_property(
1742            PropertyIdentifier::LIMIT_ENABLE,
1743            None,
1744            PropertyValue::BitString {
1745                unused_bits: 6,
1746                data: vec![LimitEnable::BOTH.to_bits()],
1747            },
1748            None,
1749        )
1750        .unwrap();
1751        av.write_property(
1752            PropertyIdentifier::EVENT_ENABLE,
1753            None,
1754            PropertyValue::BitString {
1755                unused_bits: 5,
1756                data: vec![0x07 << 5],
1757            },
1758            None,
1759        )
1760        .unwrap();
1761
1762        // Normal value — no transition
1763        av.set_present_value(50.0);
1764        assert!(av.evaluate_intrinsic_reporting().is_none());
1765
1766        // Go above high limit
1767        av.set_present_value(81.0);
1768        let change = av.evaluate_intrinsic_reporting().unwrap();
1769        assert_eq!(change.from, EventState::NORMAL);
1770        assert_eq!(change.to, EventState::HIGH_LIMIT);
1771
1772        // Verify event_state property reads correctly
1773        assert_eq!(
1774            av.read_property(PropertyIdentifier::EVENT_STATE, None)
1775                .unwrap(),
1776            PropertyValue::Enumerated(EventState::HIGH_LIMIT.to_raw())
1777        );
1778
1779        // Drop below deadband threshold → back to NORMAL
1780        av.set_present_value(77.0);
1781        let change = av.evaluate_intrinsic_reporting().unwrap();
1782        assert_eq!(change.to, EventState::NORMAL);
1783    }
1784
1785    #[test]
1786    fn av_intrinsic_reporting_after_priority_write() {
1787        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1788        av.write_property(
1789            PropertyIdentifier::HIGH_LIMIT,
1790            None,
1791            PropertyValue::Real(80.0),
1792            None,
1793        )
1794        .unwrap();
1795        av.write_property(
1796            PropertyIdentifier::LOW_LIMIT,
1797            None,
1798            PropertyValue::Real(20.0),
1799            None,
1800        )
1801        .unwrap();
1802        av.write_property(
1803            PropertyIdentifier::DEADBAND,
1804            None,
1805            PropertyValue::Real(2.0),
1806            None,
1807        )
1808        .unwrap();
1809        av.write_property(
1810            PropertyIdentifier::LIMIT_ENABLE,
1811            None,
1812            PropertyValue::BitString {
1813                unused_bits: 6,
1814                data: vec![LimitEnable::BOTH.to_bits()],
1815            },
1816            None,
1817        )
1818        .unwrap();
1819        av.write_property(
1820            PropertyIdentifier::EVENT_ENABLE,
1821            None,
1822            PropertyValue::BitString {
1823                unused_bits: 5,
1824                data: vec![0x07 << 5],
1825            },
1826            None,
1827        )
1828        .unwrap();
1829
1830        // Write a high value via priority array
1831        av.write_property(
1832            PropertyIdentifier::PRESENT_VALUE,
1833            None,
1834            PropertyValue::Real(85.0),
1835            Some(8),
1836        )
1837        .unwrap();
1838        let change = av.evaluate_intrinsic_reporting().unwrap();
1839        assert_eq!(change.to, EventState::HIGH_LIMIT);
1840    }
1841
1842    #[test]
1843    fn av_read_reliability_default() {
1844        let av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1845        let val = av
1846            .read_property(PropertyIdentifier::RELIABILITY, None)
1847            .unwrap();
1848        assert_eq!(val, PropertyValue::Enumerated(0)); // NO_FAULT_DETECTED
1849    }
1850
1851    #[test]
1852    fn av_description_read_write() {
1853        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1854        // Default description is empty
1855        let val = av
1856            .read_property(PropertyIdentifier::DESCRIPTION, None)
1857            .unwrap();
1858        assert_eq!(val, PropertyValue::CharacterString(String::new()));
1859        // Write a description
1860        av.write_property(
1861            PropertyIdentifier::DESCRIPTION,
1862            None,
1863            PropertyValue::CharacterString("Setpoint".into()),
1864            None,
1865        )
1866        .unwrap();
1867        let val = av
1868            .read_property(PropertyIdentifier::DESCRIPTION, None)
1869            .unwrap();
1870        assert_eq!(val, PropertyValue::CharacterString("Setpoint".into()));
1871    }
1872
1873    #[test]
1874    fn av_set_description_convenience() {
1875        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1876        av.set_description("Zone temperature setpoint");
1877        assert_eq!(
1878            av.read_property(PropertyIdentifier::DESCRIPTION, None)
1879                .unwrap(),
1880            PropertyValue::CharacterString("Zone temperature setpoint".into())
1881        );
1882    }
1883
1884    #[test]
1885    fn av_description_in_property_list() {
1886        let av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1887        assert!(av
1888            .property_list()
1889            .contains(&PropertyIdentifier::DESCRIPTION));
1890    }
1891
1892    #[test]
1893    fn av_property_list_includes_priority_array_and_relinquish_default() {
1894        let av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1895        let list = av.property_list();
1896        assert!(list.contains(&PropertyIdentifier::PRIORITY_ARRAY));
1897        assert!(list.contains(&PropertyIdentifier::RELINQUISH_DEFAULT));
1898        assert!(list.contains(&PropertyIdentifier::COV_INCREMENT));
1899        assert!(list.contains(&PropertyIdentifier::UNITS));
1900    }
1901
1902    #[test]
1903    fn av_read_event_state_default_normal() {
1904        let av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1905        let val = av
1906            .read_property(PropertyIdentifier::EVENT_STATE, None)
1907            .unwrap();
1908        assert_eq!(val, PropertyValue::Enumerated(EventState::NORMAL.to_raw()));
1909    }
1910
1911    #[test]
1912    fn av_cov_increment_read_write() {
1913        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1914        av.write_property(
1915            PropertyIdentifier::COV_INCREMENT,
1916            None,
1917            PropertyValue::Real(1.5),
1918            None,
1919        )
1920        .unwrap();
1921        assert_eq!(
1922            av.read_property(PropertyIdentifier::COV_INCREMENT, None)
1923                .unwrap(),
1924            PropertyValue::Real(1.5)
1925        );
1926        assert_eq!(av.cov_increment(), Some(1.5));
1927    }
1928
1929    #[test]
1930    fn av_read_relinquish_default() {
1931        let av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1932        let val = av
1933            .read_property(PropertyIdentifier::RELINQUISH_DEFAULT, None)
1934            .unwrap();
1935        assert_eq!(val, PropertyValue::Real(0.0));
1936    }
1937
1938    #[test]
1939    fn av_unknown_property_returns_error() {
1940        let av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
1941        // File-object property does not exist on AV
1942        let result = av.read_property(PropertyIdentifier::FILE_SIZE, None);
1943        assert!(result.is_err());
1944    }
1945
1946    // --- PROPERTY_LIST ---
1947
1948    #[test]
1949    fn ai_property_list_returns_full_list() {
1950        let ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
1951        let result = ai
1952            .read_property(PropertyIdentifier::PROPERTY_LIST, None)
1953            .unwrap();
1954        if let PropertyValue::List(elements) = result {
1955            assert!(!elements.is_empty());
1956            assert!(matches!(elements[0], PropertyValue::Enumerated(_)));
1957        } else {
1958            panic!("Expected PropertyValue::List");
1959        }
1960    }
1961
1962    #[test]
1963    fn ai_property_list_index_zero_returns_count() {
1964        let ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
1965        // Property_List excludes OBJECT_IDENTIFIER, OBJECT_NAME,
1966        // OBJECT_TYPE, and PROPERTY_LIST itself.
1967        let filtered_count = ai
1968            .property_list()
1969            .iter()
1970            .filter(|p| {
1971                **p != PropertyIdentifier::OBJECT_IDENTIFIER
1972                    && **p != PropertyIdentifier::OBJECT_NAME
1973                    && **p != PropertyIdentifier::OBJECT_TYPE
1974                    && **p != PropertyIdentifier::PROPERTY_LIST
1975            })
1976            .count() as u64;
1977        let result = ai
1978            .read_property(PropertyIdentifier::PROPERTY_LIST, Some(0))
1979            .unwrap();
1980        assert_eq!(result, PropertyValue::Unsigned(filtered_count));
1981    }
1982
1983    #[test]
1984    fn ai_property_list_index_one_returns_first_prop() {
1985        let ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
1986        // First property after filtering the 4 excluded ones
1987        let first_filtered = ai
1988            .property_list()
1989            .iter()
1990            .copied()
1991            .find(|p| {
1992                *p != PropertyIdentifier::OBJECT_IDENTIFIER
1993                    && *p != PropertyIdentifier::OBJECT_NAME
1994                    && *p != PropertyIdentifier::OBJECT_TYPE
1995                    && *p != PropertyIdentifier::PROPERTY_LIST
1996            })
1997            .unwrap();
1998        let result = ai
1999            .read_property(PropertyIdentifier::PROPERTY_LIST, Some(1))
2000            .unwrap();
2001        assert_eq!(result, PropertyValue::Enumerated(first_filtered.to_raw()));
2002    }
2003
2004    #[test]
2005    fn ai_property_list_invalid_index_returns_error() {
2006        let ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
2007        let count = ai.property_list().len() as u32;
2008        let result = ai.read_property(PropertyIdentifier::PROPERTY_LIST, Some(count + 1));
2009        assert!(result.is_err());
2010    }
2011}