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