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            _ => Err(common::unknown_property_error()),
435        }
436    }
437
438    fn write_property(
439        &mut self,
440        property: PropertyIdentifier,
441        _array_index: Option<u32>,
442        value: PropertyValue,
443        _priority: Option<u8>,
444    ) -> Result<(), Error> {
445        if let Some(result) =
446            common::write_out_of_service(&mut self.out_of_service, property, &value)
447        {
448            return result;
449        }
450        if let Some(result) = common::write_description(&mut self.description, property, &value) {
451            return result;
452        }
453        match property {
454            p if p == PropertyIdentifier::TRACKING_VALUE => {
455                if let PropertyValue::Unsigned(v) = value {
456                    self.tracking_value = v;
457                    Ok(())
458                } else {
459                    Err(common::invalid_data_type_error())
460                }
461            }
462            p if p == PropertyIdentifier::CAR_POSITION => {
463                if let PropertyValue::Unsigned(v) = value {
464                    self.car_position = v;
465                    Ok(())
466                } else {
467                    Err(common::invalid_data_type_error())
468                }
469            }
470            p if p == PropertyIdentifier::CAR_MOVING_DIRECTION => {
471                if let PropertyValue::Enumerated(v) = value {
472                    if v > 3 {
473                        return Err(common::value_out_of_range_error());
474                    }
475                    self.car_moving_direction = v;
476                    Ok(())
477                } else {
478                    Err(common::invalid_data_type_error())
479                }
480            }
481            p if p == PropertyIdentifier::CAR_LOAD => {
482                if let PropertyValue::Unsigned(v) = value {
483                    if v > 100 {
484                        return Err(common::value_out_of_range_error());
485                    }
486                    self.car_load = v;
487                    Ok(())
488                } else {
489                    Err(common::invalid_data_type_error())
490                }
491            }
492            _ => Err(common::write_access_denied_error()),
493        }
494    }
495
496    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
497        static PROPS: &[PropertyIdentifier] = &[
498            PropertyIdentifier::OBJECT_IDENTIFIER,
499            PropertyIdentifier::OBJECT_NAME,
500            PropertyIdentifier::DESCRIPTION,
501            PropertyIdentifier::OBJECT_TYPE,
502            PropertyIdentifier::TRACKING_VALUE,
503            PropertyIdentifier::CAR_POSITION,
504            PropertyIdentifier::CAR_MOVING_DIRECTION,
505            PropertyIdentifier::CAR_DOOR_STATUS,
506            PropertyIdentifier::CAR_LOAD,
507            PropertyIdentifier::LANDING_DOOR_STATUS,
508            PropertyIdentifier::FLOOR_TEXT,
509            PropertyIdentifier::ENERGY_METER,
510            PropertyIdentifier::STATUS_FLAGS,
511            PropertyIdentifier::OUT_OF_SERVICE,
512            PropertyIdentifier::RELIABILITY,
513        ];
514        Cow::Borrowed(PROPS)
515    }
516}
517
518// ---------------------------------------------------------------------------
519// Tests
520// ---------------------------------------------------------------------------
521
522#[cfg(test)]
523mod tests {
524    use super::*;
525
526    // --- ElevatorGroupObject ---
527
528    #[test]
529    fn elevator_group_create_and_read_defaults() {
530        let eg = ElevatorGroupObject::new(1, "EG-1").unwrap();
531        assert_eq!(eg.object_name(), "EG-1");
532        assert_eq!(
533            eg.read_property(PropertyIdentifier::GROUP_ID, None)
534                .unwrap(),
535            PropertyValue::Unsigned(0)
536        );
537        assert_eq!(
538            eg.read_property(PropertyIdentifier::GROUP_MEMBERS, None)
539                .unwrap(),
540            PropertyValue::List(vec![])
541        );
542        assert_eq!(
543            eg.read_property(PropertyIdentifier::GROUP_MODE, None)
544                .unwrap(),
545            PropertyValue::Enumerated(0) // Unknown
546        );
547    }
548
549    #[test]
550    fn elevator_group_object_type() {
551        let eg = ElevatorGroupObject::new(1, "EG-1").unwrap();
552        assert_eq!(
553            eg.read_property(PropertyIdentifier::OBJECT_TYPE, None)
554                .unwrap(),
555            PropertyValue::Enumerated(ObjectType::ELEVATOR_GROUP.to_raw())
556        );
557    }
558
559    #[test]
560    fn elevator_group_add_members() {
561        let mut eg = ElevatorGroupObject::new(1, "EG-1").unwrap();
562        let lift1 = ObjectIdentifier::new(ObjectType::LIFT, 1).unwrap();
563        let lift2 = ObjectIdentifier::new(ObjectType::LIFT, 2).unwrap();
564        eg.add_member(lift1);
565        eg.add_member(lift2);
566        assert_eq!(
567            eg.read_property(PropertyIdentifier::GROUP_MEMBERS, None)
568                .unwrap(),
569            PropertyValue::List(vec![
570                PropertyValue::ObjectIdentifier(lift1),
571                PropertyValue::ObjectIdentifier(lift2),
572            ])
573        );
574    }
575
576    #[test]
577    fn elevator_group_read_landing_calls() {
578        let eg = ElevatorGroupObject::new(1, "EG-1").unwrap();
579        assert_eq!(
580            eg.read_property(PropertyIdentifier::LANDING_CALLS, None)
581                .unwrap(),
582            PropertyValue::Unsigned(0)
583        );
584    }
585
586    #[test]
587    fn elevator_group_property_list() {
588        let eg = ElevatorGroupObject::new(1, "EG-1").unwrap();
589        let list = eg.property_list();
590        assert!(list.contains(&PropertyIdentifier::GROUP_ID));
591        assert!(list.contains(&PropertyIdentifier::GROUP_MEMBERS));
592        assert!(list.contains(&PropertyIdentifier::GROUP_MODE));
593        assert!(list.contains(&PropertyIdentifier::LANDING_CALLS));
594        assert!(list.contains(&PropertyIdentifier::LANDING_CALL_CONTROL));
595        assert!(list.contains(&PropertyIdentifier::STATUS_FLAGS));
596    }
597
598    // --- EscalatorObject ---
599
600    #[test]
601    fn escalator_create_and_read_defaults() {
602        let esc = EscalatorObject::new(1, "ESC-1").unwrap();
603        assert_eq!(esc.object_name(), "ESC-1");
604        assert_eq!(
605            esc.read_property(PropertyIdentifier::ESCALATOR_MODE, None)
606                .unwrap(),
607            PropertyValue::Enumerated(0) // unknown
608        );
609        assert_eq!(
610            esc.read_property(PropertyIdentifier::ENERGY_METER, None)
611                .unwrap(),
612            PropertyValue::Real(0.0)
613        );
614        assert_eq!(
615            esc.read_property(PropertyIdentifier::POWER_MODE, None)
616                .unwrap(),
617            PropertyValue::Boolean(false)
618        );
619    }
620
621    #[test]
622    fn escalator_object_type() {
623        let esc = EscalatorObject::new(1, "ESC-1").unwrap();
624        assert_eq!(
625            esc.read_property(PropertyIdentifier::OBJECT_TYPE, None)
626                .unwrap(),
627            PropertyValue::Enumerated(ObjectType::ESCALATOR.to_raw())
628        );
629    }
630
631    #[test]
632    fn escalator_read_operation_direction() {
633        let esc = EscalatorObject::new(1, "ESC-1").unwrap();
634        assert_eq!(
635            esc.read_property(PropertyIdentifier::OPERATION_DIRECTION, None)
636                .unwrap(),
637            PropertyValue::Enumerated(0) // unknown
638        );
639    }
640
641    #[test]
642    fn escalator_read_fault_signals() {
643        let esc = EscalatorObject::new(1, "ESC-1").unwrap();
644        assert_eq!(
645            esc.read_property(PropertyIdentifier::FAULT_SIGNALS, None)
646                .unwrap(),
647            PropertyValue::List(vec![])
648        );
649    }
650
651    #[test]
652    fn escalator_property_list() {
653        let esc = EscalatorObject::new(1, "ESC-1").unwrap();
654        let list = esc.property_list();
655        assert!(list.contains(&PropertyIdentifier::ESCALATOR_MODE));
656        assert!(list.contains(&PropertyIdentifier::FAULT_SIGNALS));
657        assert!(list.contains(&PropertyIdentifier::ENERGY_METER));
658        assert!(list.contains(&PropertyIdentifier::ENERGY_METER_REF));
659        assert!(list.contains(&PropertyIdentifier::POWER_MODE));
660        assert!(list.contains(&PropertyIdentifier::OPERATION_DIRECTION));
661        assert!(list.contains(&PropertyIdentifier::STATUS_FLAGS));
662    }
663
664    // --- LiftObject ---
665
666    #[test]
667    fn lift_create_and_read_defaults() {
668        let lift = LiftObject::new(1, "LIFT-1", 10).unwrap();
669        assert_eq!(lift.object_name(), "LIFT-1");
670        assert_eq!(
671            lift.read_property(PropertyIdentifier::TRACKING_VALUE, None)
672                .unwrap(),
673            PropertyValue::Unsigned(1)
674        );
675        assert_eq!(
676            lift.read_property(PropertyIdentifier::CAR_POSITION, None)
677                .unwrap(),
678            PropertyValue::Unsigned(1)
679        );
680        assert_eq!(
681            lift.read_property(PropertyIdentifier::CAR_MOVING_DIRECTION, None)
682                .unwrap(),
683            PropertyValue::Enumerated(1) // stopped
684        );
685    }
686
687    #[test]
688    fn lift_object_type() {
689        let lift = LiftObject::new(1, "LIFT-1", 5).unwrap();
690        assert_eq!(
691            lift.read_property(PropertyIdentifier::OBJECT_TYPE, None)
692                .unwrap(),
693            PropertyValue::Enumerated(ObjectType::LIFT.to_raw())
694        );
695    }
696
697    #[test]
698    fn lift_floor_text() {
699        let lift = LiftObject::new(1, "LIFT-1", 3).unwrap();
700        assert_eq!(
701            lift.read_property(PropertyIdentifier::FLOOR_TEXT, None)
702                .unwrap(),
703            PropertyValue::List(vec![
704                PropertyValue::CharacterString("Floor 1".into()),
705                PropertyValue::CharacterString("Floor 2".into()),
706                PropertyValue::CharacterString("Floor 3".into()),
707            ])
708        );
709    }
710
711    #[test]
712    fn lift_read_car_load() {
713        let lift = LiftObject::new(1, "LIFT-1", 5).unwrap();
714        assert_eq!(
715            lift.read_property(PropertyIdentifier::CAR_LOAD, None)
716                .unwrap(),
717            PropertyValue::Unsigned(0)
718        );
719    }
720
721    #[test]
722    fn lift_write_tracking_value() {
723        let mut lift = LiftObject::new(1, "LIFT-1", 10).unwrap();
724        lift.write_property(
725            PropertyIdentifier::TRACKING_VALUE,
726            None,
727            PropertyValue::Unsigned(5),
728            None,
729        )
730        .unwrap();
731        assert_eq!(
732            lift.read_property(PropertyIdentifier::TRACKING_VALUE, None)
733                .unwrap(),
734            PropertyValue::Unsigned(5)
735        );
736    }
737
738    #[test]
739    fn lift_write_car_load_out_of_range() {
740        let mut lift = LiftObject::new(1, "LIFT-1", 5).unwrap();
741        let result = lift.write_property(
742            PropertyIdentifier::CAR_LOAD,
743            None,
744            PropertyValue::Unsigned(101),
745            None,
746        );
747        assert!(result.is_err());
748    }
749
750    #[test]
751    fn lift_read_landing_doors() {
752        let lift = LiftObject::new(1, "LIFT-1", 8).unwrap();
753        assert_eq!(
754            lift.read_property(PropertyIdentifier::LANDING_DOOR_STATUS, None)
755                .unwrap(),
756            PropertyValue::Unsigned(8)
757        );
758    }
759
760    #[test]
761    fn lift_read_energy_meter() {
762        let lift = LiftObject::new(1, "LIFT-1", 5).unwrap();
763        assert_eq!(
764            lift.read_property(PropertyIdentifier::ENERGY_METER, None)
765                .unwrap(),
766            PropertyValue::Real(0.0)
767        );
768    }
769
770    #[test]
771    fn lift_property_list() {
772        let lift = LiftObject::new(1, "LIFT-1", 5).unwrap();
773        let list = lift.property_list();
774        assert!(list.contains(&PropertyIdentifier::TRACKING_VALUE));
775        assert!(list.contains(&PropertyIdentifier::CAR_POSITION));
776        assert!(list.contains(&PropertyIdentifier::CAR_MOVING_DIRECTION));
777        assert!(list.contains(&PropertyIdentifier::CAR_DOOR_STATUS));
778        assert!(list.contains(&PropertyIdentifier::CAR_LOAD));
779        assert!(list.contains(&PropertyIdentifier::LANDING_DOOR_STATUS));
780        assert!(list.contains(&PropertyIdentifier::FLOOR_TEXT));
781        assert!(list.contains(&PropertyIdentifier::ENERGY_METER));
782        assert!(list.contains(&PropertyIdentifier::STATUS_FLAGS));
783    }
784}