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