Skip to main content

bacnet_objects/
elevator.rs

1//! Elevator, Escalator, and Lift objects per ASHRAE 135-2020 Clause 12.
2//!
3//! - ElevatorGroupObject (type 57): manages a group of lifts
4//! - EscalatorObject (type 58): represents an escalator
5//! - LiftObject (type 59): represents a single lift/elevator car
6
7use bacnet_types::enums::{ObjectType, PropertyIdentifier};
8use bacnet_types::error::Error;
9use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
10use std::borrow::Cow;
11
12use crate::common::{self, read_common_properties};
13use crate::traits::BACnetObject;
14
15// ===========================================================================
16// ElevatorGroupObject (type 57)
17// ===========================================================================
18
19/// BACnet Elevator Group object — manages a group of lifts.
20pub struct ElevatorGroupObject {
21    oid: ObjectIdentifier,
22    name: String,
23    description: String,
24    /// Group identifier (Unsigned).
25    group_id: u64,
26    /// List of lift ObjectIdentifiers in this group.
27    group_members: Vec<ObjectIdentifier>,
28    /// Group mode (LiftGroupMode enumerated value).
29    group_mode: u32,
30    /// Number of landing calls (stored as count).
31    landing_calls: u64,
32    /// Landing call control (Enumerated).
33    landing_call_control: u32,
34    status_flags: StatusFlags,
35    out_of_service: bool,
36    reliability: u32,
37}
38
39impl ElevatorGroupObject {
40    /// Create a new Elevator Group object with default values.
41    pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
42        let oid = ObjectIdentifier::new(ObjectType::ELEVATOR_GROUP, instance)?;
43        Ok(Self {
44            oid,
45            name: name.into(),
46            description: String::new(),
47            group_id: 0,
48            group_members: Vec::new(),
49            group_mode: 0, // Unknown
50            landing_calls: 0,
51            landing_call_control: 0,
52            status_flags: StatusFlags::empty(),
53            out_of_service: false,
54            reliability: 0,
55        })
56    }
57
58    /// Add a lift member to this elevator group.
59    pub fn add_member(&mut self, oid: ObjectIdentifier) {
60        self.group_members.push(oid);
61    }
62}
63
64impl BACnetObject for ElevatorGroupObject {
65    fn object_identifier(&self) -> ObjectIdentifier {
66        self.oid
67    }
68
69    fn object_name(&self) -> &str {
70        &self.name
71    }
72
73    fn read_property(
74        &self,
75        property: PropertyIdentifier,
76        array_index: Option<u32>,
77    ) -> Result<PropertyValue, Error> {
78        if let Some(result) = read_common_properties!(self, property, array_index) {
79            return result;
80        }
81        match property {
82            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
83                ObjectType::ELEVATOR_GROUP.to_raw(),
84            )),
85            p if p == PropertyIdentifier::GROUP_ID => Ok(PropertyValue::Unsigned(self.group_id)),
86            p if p == PropertyIdentifier::GROUP_MEMBERS => {
87                let items: Vec<PropertyValue> = self
88                    .group_members
89                    .iter()
90                    .map(|oid| PropertyValue::ObjectIdentifier(*oid))
91                    .collect();
92                Ok(PropertyValue::List(items))
93            }
94            p if p == PropertyIdentifier::GROUP_MODE => {
95                Ok(PropertyValue::Enumerated(self.group_mode))
96            }
97            p if p == PropertyIdentifier::LANDING_CALLS => {
98                Ok(PropertyValue::Unsigned(self.landing_calls))
99            }
100            p if p == PropertyIdentifier::LANDING_CALL_CONTROL => {
101                Ok(PropertyValue::Enumerated(self.landing_call_control))
102            }
103            _ => Err(common::unknown_property_error()),
104        }
105    }
106
107    fn write_property(
108        &mut self,
109        property: PropertyIdentifier,
110        _array_index: Option<u32>,
111        value: PropertyValue,
112        _priority: Option<u8>,
113    ) -> Result<(), Error> {
114        if let Some(result) =
115            common::write_out_of_service(&mut self.out_of_service, property, &value)
116        {
117            return result;
118        }
119        if let Some(result) = common::write_description(&mut self.description, property, &value) {
120            return result;
121        }
122        match property {
123            p if p == PropertyIdentifier::GROUP_ID => {
124                if let PropertyValue::Unsigned(v) = value {
125                    self.group_id = v;
126                    Ok(())
127                } else {
128                    Err(common::invalid_data_type_error())
129                }
130            }
131            p if p == PropertyIdentifier::GROUP_MODE => {
132                if let PropertyValue::Enumerated(v) = value {
133                    self.group_mode = v;
134                    Ok(())
135                } else {
136                    Err(common::invalid_data_type_error())
137                }
138            }
139            p if p == PropertyIdentifier::LANDING_CALL_CONTROL => {
140                if let PropertyValue::Enumerated(v) = value {
141                    self.landing_call_control = v;
142                    Ok(())
143                } else {
144                    Err(common::invalid_data_type_error())
145                }
146            }
147            _ => Err(common::write_access_denied_error()),
148        }
149    }
150
151    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
152        static PROPS: &[PropertyIdentifier] = &[
153            PropertyIdentifier::OBJECT_IDENTIFIER,
154            PropertyIdentifier::OBJECT_NAME,
155            PropertyIdentifier::DESCRIPTION,
156            PropertyIdentifier::OBJECT_TYPE,
157            PropertyIdentifier::GROUP_ID,
158            PropertyIdentifier::GROUP_MEMBERS,
159            PropertyIdentifier::GROUP_MODE,
160            PropertyIdentifier::LANDING_CALLS,
161            PropertyIdentifier::LANDING_CALL_CONTROL,
162            PropertyIdentifier::STATUS_FLAGS,
163            PropertyIdentifier::OUT_OF_SERVICE,
164            PropertyIdentifier::RELIABILITY,
165        ];
166        Cow::Borrowed(PROPS)
167    }
168}
169
170// ===========================================================================
171// EscalatorObject (type 58)
172// ===========================================================================
173
174/// BACnet Escalator object — represents an escalator.
175pub struct EscalatorObject {
176    oid: ObjectIdentifier,
177    name: String,
178    description: String,
179    /// Escalator mode (Enumerated: 0=unknown, 1=stop, 2=up, 3=down, 4=inspection).
180    escalator_mode: u32,
181    /// List of fault signal codes (Unsigned).
182    fault_signals: Vec<u64>,
183    /// Energy meter reading (Real).
184    energy_meter: f32,
185    /// Energy meter reference (stored as raw bytes).
186    energy_meter_ref: Vec<u8>,
187    /// Power mode (Boolean).
188    power_mode: bool,
189    /// Operation direction (Enumerated: 0=unknown, 1=up, 2=down, 3=stopped).
190    operation_direction: u32,
191    status_flags: StatusFlags,
192    out_of_service: bool,
193    reliability: u32,
194}
195
196impl EscalatorObject {
197    /// Create a new Escalator object with default values.
198    pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
199        let oid = ObjectIdentifier::new(ObjectType::ESCALATOR, instance)?;
200        Ok(Self {
201            oid,
202            name: name.into(),
203            description: String::new(),
204            escalator_mode: 0, // unknown
205            fault_signals: Vec::new(),
206            energy_meter: 0.0,
207            energy_meter_ref: Vec::new(),
208            power_mode: false,
209            operation_direction: 0, // unknown
210            status_flags: StatusFlags::empty(),
211            out_of_service: false,
212            reliability: 0,
213        })
214    }
215}
216
217impl BACnetObject for EscalatorObject {
218    fn object_identifier(&self) -> ObjectIdentifier {
219        self.oid
220    }
221
222    fn object_name(&self) -> &str {
223        &self.name
224    }
225
226    fn read_property(
227        &self,
228        property: PropertyIdentifier,
229        array_index: Option<u32>,
230    ) -> Result<PropertyValue, Error> {
231        if let Some(result) = read_common_properties!(self, property, array_index) {
232            return result;
233        }
234        match property {
235            p if p == PropertyIdentifier::OBJECT_TYPE => {
236                Ok(PropertyValue::Enumerated(ObjectType::ESCALATOR.to_raw()))
237            }
238            p if p == PropertyIdentifier::ESCALATOR_MODE => {
239                Ok(PropertyValue::Enumerated(self.escalator_mode))
240            }
241            p if p == PropertyIdentifier::FAULT_SIGNALS => {
242                let items: Vec<PropertyValue> = self
243                    .fault_signals
244                    .iter()
245                    .map(|v| PropertyValue::Unsigned(*v))
246                    .collect();
247                Ok(PropertyValue::List(items))
248            }
249            p if p == PropertyIdentifier::ENERGY_METER => {
250                Ok(PropertyValue::Real(self.energy_meter))
251            }
252            p if p == PropertyIdentifier::ENERGY_METER_REF => {
253                Ok(PropertyValue::OctetString(self.energy_meter_ref.clone()))
254            }
255            p if p == PropertyIdentifier::POWER_MODE => Ok(PropertyValue::Boolean(self.power_mode)),
256            p if p == PropertyIdentifier::OPERATION_DIRECTION => {
257                Ok(PropertyValue::Enumerated(self.operation_direction))
258            }
259            _ => Err(common::unknown_property_error()),
260        }
261    }
262
263    fn write_property(
264        &mut self,
265        property: PropertyIdentifier,
266        _array_index: Option<u32>,
267        value: PropertyValue,
268        _priority: Option<u8>,
269    ) -> Result<(), Error> {
270        if let Some(result) =
271            common::write_out_of_service(&mut self.out_of_service, property, &value)
272        {
273            return result;
274        }
275        if let Some(result) = common::write_description(&mut self.description, property, &value) {
276            return result;
277        }
278        match property {
279            p if p == PropertyIdentifier::ESCALATOR_MODE => {
280                if let PropertyValue::Enumerated(v) = value {
281                    if v > 4 {
282                        return Err(common::value_out_of_range_error());
283                    }
284                    self.escalator_mode = v;
285                    Ok(())
286                } else {
287                    Err(common::invalid_data_type_error())
288                }
289            }
290            p if p == PropertyIdentifier::OPERATION_DIRECTION => {
291                if let PropertyValue::Enumerated(v) = value {
292                    if v > 3 {
293                        return Err(common::value_out_of_range_error());
294                    }
295                    self.operation_direction = v;
296                    Ok(())
297                } else {
298                    Err(common::invalid_data_type_error())
299                }
300            }
301            _ => Err(common::write_access_denied_error()),
302        }
303    }
304
305    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
306        static PROPS: &[PropertyIdentifier] = &[
307            PropertyIdentifier::OBJECT_IDENTIFIER,
308            PropertyIdentifier::OBJECT_NAME,
309            PropertyIdentifier::DESCRIPTION,
310            PropertyIdentifier::OBJECT_TYPE,
311            PropertyIdentifier::ESCALATOR_MODE,
312            PropertyIdentifier::FAULT_SIGNALS,
313            PropertyIdentifier::ENERGY_METER,
314            PropertyIdentifier::ENERGY_METER_REF,
315            PropertyIdentifier::POWER_MODE,
316            PropertyIdentifier::OPERATION_DIRECTION,
317            PropertyIdentifier::STATUS_FLAGS,
318            PropertyIdentifier::OUT_OF_SERVICE,
319            PropertyIdentifier::RELIABILITY,
320        ];
321        Cow::Borrowed(PROPS)
322    }
323}
324
325// ===========================================================================
326// LiftObject (type 59)
327// ===========================================================================
328
329/// BACnet Lift object — represents a single lift/elevator car.
330pub struct LiftObject {
331    oid: ObjectIdentifier,
332    name: String,
333    description: String,
334    /// Tracking value (Unsigned — current floor).
335    tracking_value: u64,
336    /// Car position (Unsigned).
337    car_position: u64,
338    /// Car moving direction (Enumerated: 0=unknown, 1=stopped, 2=up, 3=down).
339    car_moving_direction: u32,
340    /// Car door status (List of Unsigned).
341    car_door_status: Vec<u64>,
342    /// Car load as a percentage (Unsigned).
343    car_load: u64,
344    /// Number of landing doors (stored as count).
345    landing_doors: u64,
346    /// Floor text labels (List of String).
347    floor_text: Vec<String>,
348    /// Energy meter reading (Real).
349    energy_meter: f32,
350    status_flags: StatusFlags,
351    out_of_service: bool,
352    reliability: u32,
353}
354
355impl LiftObject {
356    /// Create a new Lift object with the given number of floors.
357    ///
358    /// Floor text is initialized to "Floor 1", "Floor 2", etc.
359    pub fn new(instance: u32, name: impl Into<String>, num_floors: usize) -> Result<Self, Error> {
360        let oid = ObjectIdentifier::new(ObjectType::LIFT, instance)?;
361        let floor_text = (1..=num_floors).map(|i| format!("Floor {i}")).collect();
362        Ok(Self {
363            oid,
364            name: name.into(),
365            description: String::new(),
366            tracking_value: 1,
367            car_position: 1,
368            car_moving_direction: 1, // stopped
369            car_door_status: Vec::new(),
370            car_load: 0,
371            landing_doors: num_floors as u64,
372            floor_text,
373            energy_meter: 0.0,
374            status_flags: StatusFlags::empty(),
375            out_of_service: false,
376            reliability: 0,
377        })
378    }
379}
380
381impl BACnetObject for LiftObject {
382    fn object_identifier(&self) -> ObjectIdentifier {
383        self.oid
384    }
385
386    fn object_name(&self) -> &str {
387        &self.name
388    }
389
390    fn read_property(
391        &self,
392        property: PropertyIdentifier,
393        array_index: Option<u32>,
394    ) -> Result<PropertyValue, Error> {
395        if let Some(result) = read_common_properties!(self, property, array_index) {
396            return result;
397        }
398        match property {
399            p if p == PropertyIdentifier::OBJECT_TYPE => {
400                Ok(PropertyValue::Enumerated(ObjectType::LIFT.to_raw()))
401            }
402            p if p == PropertyIdentifier::TRACKING_VALUE => {
403                Ok(PropertyValue::Unsigned(self.tracking_value))
404            }
405            p if p == PropertyIdentifier::CAR_POSITION => {
406                Ok(PropertyValue::Unsigned(self.car_position))
407            }
408            p if p == PropertyIdentifier::CAR_MOVING_DIRECTION => {
409                Ok(PropertyValue::Enumerated(self.car_moving_direction))
410            }
411            p if p == PropertyIdentifier::CAR_DOOR_STATUS => {
412                let items: Vec<PropertyValue> = self
413                    .car_door_status
414                    .iter()
415                    .map(|v| PropertyValue::Unsigned(*v))
416                    .collect();
417                Ok(PropertyValue::List(items))
418            }
419            p if p == PropertyIdentifier::CAR_LOAD => Ok(PropertyValue::Unsigned(self.car_load)),
420            p if p == PropertyIdentifier::LANDING_DOOR_STATUS => {
421                Ok(PropertyValue::Unsigned(self.landing_doors))
422            }
423            p if p == PropertyIdentifier::FLOOR_TEXT => {
424                let items: Vec<PropertyValue> = self
425                    .floor_text
426                    .iter()
427                    .map(|s| PropertyValue::CharacterString(s.clone()))
428                    .collect();
429                Ok(PropertyValue::List(items))
430            }
431            p if p == PropertyIdentifier::ENERGY_METER => {
432                Ok(PropertyValue::Real(self.energy_meter))
433            }
434            p if p == PropertyIdentifier::FLOOR_NUMBER => {
435                Ok(PropertyValue::Unsigned(self.tracking_value))
436            }
437            _ => Err(common::unknown_property_error()),
438        }
439    }
440
441    fn write_property(
442        &mut self,
443        property: PropertyIdentifier,
444        _array_index: Option<u32>,
445        value: PropertyValue,
446        _priority: Option<u8>,
447    ) -> Result<(), Error> {
448        if let Some(result) =
449            common::write_out_of_service(&mut self.out_of_service, property, &value)
450        {
451            return result;
452        }
453        if let Some(result) = common::write_description(&mut self.description, property, &value) {
454            return result;
455        }
456        match property {
457            p if p == PropertyIdentifier::TRACKING_VALUE => {
458                if let PropertyValue::Unsigned(v) = value {
459                    self.tracking_value = v;
460                    Ok(())
461                } else {
462                    Err(common::invalid_data_type_error())
463                }
464            }
465            p if p == PropertyIdentifier::CAR_POSITION => {
466                if let PropertyValue::Unsigned(v) = value {
467                    self.car_position = v;
468                    Ok(())
469                } else {
470                    Err(common::invalid_data_type_error())
471                }
472            }
473            p if p == PropertyIdentifier::CAR_MOVING_DIRECTION => {
474                if let PropertyValue::Enumerated(v) = value {
475                    if v > 3 {
476                        return Err(common::value_out_of_range_error());
477                    }
478                    self.car_moving_direction = v;
479                    Ok(())
480                } else {
481                    Err(common::invalid_data_type_error())
482                }
483            }
484            p if p == PropertyIdentifier::CAR_LOAD => {
485                if let PropertyValue::Unsigned(v) = value {
486                    if v > 100 {
487                        return Err(common::value_out_of_range_error());
488                    }
489                    self.car_load = v;
490                    Ok(())
491                } else {
492                    Err(common::invalid_data_type_error())
493                }
494            }
495            _ => Err(common::write_access_denied_error()),
496        }
497    }
498
499    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
500        static PROPS: &[PropertyIdentifier] = &[
501            PropertyIdentifier::OBJECT_IDENTIFIER,
502            PropertyIdentifier::OBJECT_NAME,
503            PropertyIdentifier::DESCRIPTION,
504            PropertyIdentifier::OBJECT_TYPE,
505            PropertyIdentifier::TRACKING_VALUE,
506            PropertyIdentifier::CAR_POSITION,
507            PropertyIdentifier::CAR_MOVING_DIRECTION,
508            PropertyIdentifier::CAR_DOOR_STATUS,
509            PropertyIdentifier::CAR_LOAD,
510            PropertyIdentifier::LANDING_DOOR_STATUS,
511            PropertyIdentifier::FLOOR_TEXT,
512            PropertyIdentifier::ENERGY_METER,
513            PropertyIdentifier::STATUS_FLAGS,
514            PropertyIdentifier::OUT_OF_SERVICE,
515            PropertyIdentifier::RELIABILITY,
516        ];
517        Cow::Borrowed(PROPS)
518    }
519}
520
521// ---------------------------------------------------------------------------
522// Tests
523// ---------------------------------------------------------------------------
524
525#[cfg(test)]
526mod tests {
527    use super::*;
528
529    // --- ElevatorGroupObject ---
530
531    #[test]
532    fn elevator_group_create_and_read_defaults() {
533        let eg = ElevatorGroupObject::new(1, "EG-1").unwrap();
534        assert_eq!(eg.object_name(), "EG-1");
535        assert_eq!(
536            eg.read_property(PropertyIdentifier::GROUP_ID, None)
537                .unwrap(),
538            PropertyValue::Unsigned(0)
539        );
540        assert_eq!(
541            eg.read_property(PropertyIdentifier::GROUP_MEMBERS, None)
542                .unwrap(),
543            PropertyValue::List(vec![])
544        );
545        assert_eq!(
546            eg.read_property(PropertyIdentifier::GROUP_MODE, None)
547                .unwrap(),
548            PropertyValue::Enumerated(0) // Unknown
549        );
550    }
551
552    #[test]
553    fn elevator_group_object_type() {
554        let eg = ElevatorGroupObject::new(1, "EG-1").unwrap();
555        assert_eq!(
556            eg.read_property(PropertyIdentifier::OBJECT_TYPE, None)
557                .unwrap(),
558            PropertyValue::Enumerated(ObjectType::ELEVATOR_GROUP.to_raw())
559        );
560    }
561
562    #[test]
563    fn elevator_group_add_members() {
564        let mut eg = ElevatorGroupObject::new(1, "EG-1").unwrap();
565        let lift1 = ObjectIdentifier::new(ObjectType::LIFT, 1).unwrap();
566        let lift2 = ObjectIdentifier::new(ObjectType::LIFT, 2).unwrap();
567        eg.add_member(lift1);
568        eg.add_member(lift2);
569        assert_eq!(
570            eg.read_property(PropertyIdentifier::GROUP_MEMBERS, None)
571                .unwrap(),
572            PropertyValue::List(vec![
573                PropertyValue::ObjectIdentifier(lift1),
574                PropertyValue::ObjectIdentifier(lift2),
575            ])
576        );
577    }
578
579    #[test]
580    fn elevator_group_read_landing_calls() {
581        let eg = ElevatorGroupObject::new(1, "EG-1").unwrap();
582        assert_eq!(
583            eg.read_property(PropertyIdentifier::LANDING_CALLS, None)
584                .unwrap(),
585            PropertyValue::Unsigned(0)
586        );
587    }
588
589    #[test]
590    fn elevator_group_property_list() {
591        let eg = ElevatorGroupObject::new(1, "EG-1").unwrap();
592        let list = eg.property_list();
593        assert!(list.contains(&PropertyIdentifier::GROUP_ID));
594        assert!(list.contains(&PropertyIdentifier::GROUP_MEMBERS));
595        assert!(list.contains(&PropertyIdentifier::GROUP_MODE));
596        assert!(list.contains(&PropertyIdentifier::LANDING_CALLS));
597        assert!(list.contains(&PropertyIdentifier::LANDING_CALL_CONTROL));
598        assert!(list.contains(&PropertyIdentifier::STATUS_FLAGS));
599    }
600
601    // --- EscalatorObject ---
602
603    #[test]
604    fn escalator_create_and_read_defaults() {
605        let esc = EscalatorObject::new(1, "ESC-1").unwrap();
606        assert_eq!(esc.object_name(), "ESC-1");
607        assert_eq!(
608            esc.read_property(PropertyIdentifier::ESCALATOR_MODE, None)
609                .unwrap(),
610            PropertyValue::Enumerated(0) // unknown
611        );
612        assert_eq!(
613            esc.read_property(PropertyIdentifier::ENERGY_METER, None)
614                .unwrap(),
615            PropertyValue::Real(0.0)
616        );
617        assert_eq!(
618            esc.read_property(PropertyIdentifier::POWER_MODE, None)
619                .unwrap(),
620            PropertyValue::Boolean(false)
621        );
622    }
623
624    #[test]
625    fn escalator_object_type() {
626        let esc = EscalatorObject::new(1, "ESC-1").unwrap();
627        assert_eq!(
628            esc.read_property(PropertyIdentifier::OBJECT_TYPE, None)
629                .unwrap(),
630            PropertyValue::Enumerated(ObjectType::ESCALATOR.to_raw())
631        );
632    }
633
634    #[test]
635    fn escalator_read_operation_direction() {
636        let esc = EscalatorObject::new(1, "ESC-1").unwrap();
637        assert_eq!(
638            esc.read_property(PropertyIdentifier::OPERATION_DIRECTION, None)
639                .unwrap(),
640            PropertyValue::Enumerated(0) // unknown
641        );
642    }
643
644    #[test]
645    fn escalator_read_fault_signals() {
646        let esc = EscalatorObject::new(1, "ESC-1").unwrap();
647        assert_eq!(
648            esc.read_property(PropertyIdentifier::FAULT_SIGNALS, None)
649                .unwrap(),
650            PropertyValue::List(vec![])
651        );
652    }
653
654    #[test]
655    fn escalator_property_list() {
656        let esc = EscalatorObject::new(1, "ESC-1").unwrap();
657        let list = esc.property_list();
658        assert!(list.contains(&PropertyIdentifier::ESCALATOR_MODE));
659        assert!(list.contains(&PropertyIdentifier::FAULT_SIGNALS));
660        assert!(list.contains(&PropertyIdentifier::ENERGY_METER));
661        assert!(list.contains(&PropertyIdentifier::ENERGY_METER_REF));
662        assert!(list.contains(&PropertyIdentifier::POWER_MODE));
663        assert!(list.contains(&PropertyIdentifier::OPERATION_DIRECTION));
664        assert!(list.contains(&PropertyIdentifier::STATUS_FLAGS));
665    }
666
667    // --- LiftObject ---
668
669    #[test]
670    fn lift_create_and_read_defaults() {
671        let lift = LiftObject::new(1, "LIFT-1", 10).unwrap();
672        assert_eq!(lift.object_name(), "LIFT-1");
673        assert_eq!(
674            lift.read_property(PropertyIdentifier::TRACKING_VALUE, None)
675                .unwrap(),
676            PropertyValue::Unsigned(1)
677        );
678        assert_eq!(
679            lift.read_property(PropertyIdentifier::CAR_POSITION, None)
680                .unwrap(),
681            PropertyValue::Unsigned(1)
682        );
683        assert_eq!(
684            lift.read_property(PropertyIdentifier::CAR_MOVING_DIRECTION, None)
685                .unwrap(),
686            PropertyValue::Enumerated(1) // stopped
687        );
688    }
689
690    #[test]
691    fn lift_object_type() {
692        let lift = LiftObject::new(1, "LIFT-1", 5).unwrap();
693        assert_eq!(
694            lift.read_property(PropertyIdentifier::OBJECT_TYPE, None)
695                .unwrap(),
696            PropertyValue::Enumerated(ObjectType::LIFT.to_raw())
697        );
698    }
699
700    #[test]
701    fn lift_floor_text() {
702        let lift = LiftObject::new(1, "LIFT-1", 3).unwrap();
703        assert_eq!(
704            lift.read_property(PropertyIdentifier::FLOOR_TEXT, None)
705                .unwrap(),
706            PropertyValue::List(vec![
707                PropertyValue::CharacterString("Floor 1".into()),
708                PropertyValue::CharacterString("Floor 2".into()),
709                PropertyValue::CharacterString("Floor 3".into()),
710            ])
711        );
712    }
713
714    #[test]
715    fn lift_read_car_load() {
716        let lift = LiftObject::new(1, "LIFT-1", 5).unwrap();
717        assert_eq!(
718            lift.read_property(PropertyIdentifier::CAR_LOAD, None)
719                .unwrap(),
720            PropertyValue::Unsigned(0)
721        );
722    }
723
724    #[test]
725    fn lift_write_tracking_value() {
726        let mut lift = LiftObject::new(1, "LIFT-1", 10).unwrap();
727        lift.write_property(
728            PropertyIdentifier::TRACKING_VALUE,
729            None,
730            PropertyValue::Unsigned(5),
731            None,
732        )
733        .unwrap();
734        assert_eq!(
735            lift.read_property(PropertyIdentifier::TRACKING_VALUE, None)
736                .unwrap(),
737            PropertyValue::Unsigned(5)
738        );
739    }
740
741    #[test]
742    fn lift_write_car_load_out_of_range() {
743        let mut lift = LiftObject::new(1, "LIFT-1", 5).unwrap();
744        let result = lift.write_property(
745            PropertyIdentifier::CAR_LOAD,
746            None,
747            PropertyValue::Unsigned(101),
748            None,
749        );
750        assert!(result.is_err());
751    }
752
753    #[test]
754    fn lift_read_landing_doors() {
755        let lift = LiftObject::new(1, "LIFT-1", 8).unwrap();
756        assert_eq!(
757            lift.read_property(PropertyIdentifier::LANDING_DOOR_STATUS, None)
758                .unwrap(),
759            PropertyValue::Unsigned(8)
760        );
761    }
762
763    #[test]
764    fn lift_read_energy_meter() {
765        let lift = LiftObject::new(1, "LIFT-1", 5).unwrap();
766        assert_eq!(
767            lift.read_property(PropertyIdentifier::ENERGY_METER, None)
768                .unwrap(),
769            PropertyValue::Real(0.0)
770        );
771    }
772
773    #[test]
774    fn lift_property_list() {
775        let lift = LiftObject::new(1, "LIFT-1", 5).unwrap();
776        let list = lift.property_list();
777        assert!(list.contains(&PropertyIdentifier::TRACKING_VALUE));
778        assert!(list.contains(&PropertyIdentifier::CAR_POSITION));
779        assert!(list.contains(&PropertyIdentifier::CAR_MOVING_DIRECTION));
780        assert!(list.contains(&PropertyIdentifier::CAR_DOOR_STATUS));
781        assert!(list.contains(&PropertyIdentifier::CAR_LOAD));
782        assert!(list.contains(&PropertyIdentifier::LANDING_DOOR_STATUS));
783        assert!(list.contains(&PropertyIdentifier::FLOOR_TEXT));
784        assert!(list.contains(&PropertyIdentifier::ENERGY_METER));
785        assert!(list.contains(&PropertyIdentifier::STATUS_FLAGS));
786    }
787}