1use crate::prelude::*;
4use alloc::borrow::Cow;
5use bacnet_types::constructed::{
6 BACnetAssignedLandingCalls, BACnetDeviceObjectReference, BACnetLandingDoorStatus,
7 BACnetLiftCarCallList,
8};
9use bacnet_types::enums::{
10 DoorStatus, EngineeringUnits, LiftCarDirection, LiftCarDoorCommand, LiftCarDriveStatus,
11 LiftCarMode, LiftFault, ObjectType, PropertyIdentifier, Reliability,
12};
13use bacnet_types::error::Error;
14use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
15
16use super::car_state::{self, CarState};
17use super::doors::CarDoors;
18use super::energy_meter::{EnergyMeter, energy_meter_accessors};
19use super::membership::{GroupMembership, group_membership_accessors};
20use super::{checked, decode_fault_signals, direction_in_range, metadata};
21use crate::common::{self, read_common_properties};
22use crate::traits::BACnetObject;
23
24pub struct LiftObject {
35 oid: ObjectIdentifier,
36 name: String,
37 description: String,
38 membership: GroupMembership,
40 floor_text: Vec<String>,
42 car_position: u8,
44 pub(super) car_moving_direction: LiftCarDirection,
47 doors: CarDoors,
49 car: CarState,
52 car_load: f32,
54 car_load_units: EngineeringUnits,
56 passenger_alarm: bool,
58 energy_meter: EnergyMeter,
60 status_flags: StatusFlags,
61 out_of_service: bool,
62 reliability: Reliability,
63 fault_signals: Vec<LiftFault>,
65}
66
67impl LiftObject {
68 pub fn new(instance: u32, name: impl Into<String>, num_floors: usize) -> Result<Self, Error> {
76 let oid = ObjectIdentifier::new(ObjectType::LIFT, instance)?;
77 let floor_text = (1..=num_floors).map(|i| format!("Floor {i}")).collect();
78 Ok(Self {
79 oid,
80 name: name.into(),
81 description: String::new(),
82 membership: GroupMembership::new()?,
83 floor_text,
84 car_position: 1,
85 car_moving_direction: LiftCarDirection::STOPPED,
86 doors: CarDoors::new(),
87 car: CarState::new(),
88 car_load: 0.0,
89 car_load_units: EngineeringUnits::PERCENT,
90 passenger_alarm: false,
91 energy_meter: EnergyMeter::new()?,
92 status_flags: StatusFlags::empty(),
93 out_of_service: false,
94 reliability: Reliability::NO_FAULT_DETECTED,
95 fault_signals: Vec::new(),
96 })
97 }
98
99 group_membership_accessors!("lift");
100 energy_meter_accessors!("lift");
101
102 pub fn car_door_status(&self) -> &[DoorStatus] {
104 &self.doors.status
105 }
106
107 pub fn set_car_door_status(&mut self, status: Vec<DoorStatus>) -> Result<(), Error> {
118 self.doors.set_status(status)
119 }
120
121 pub fn landing_door_status(&self) -> &[BACnetLandingDoorStatus] {
123 &self.doors.landing
124 }
125
126 pub fn set_landing_door_status(
133 &mut self,
134 status: Vec<BACnetLandingDoorStatus>,
135 ) -> Result<(), Error> {
136 self.doors.set_landing(status)
137 }
138
139 pub fn assigned_landing_calls(&self) -> &[BACnetAssignedLandingCalls] {
142 &self.doors.landing_calls
143 }
144
145 pub fn set_assigned_landing_calls(
154 &mut self,
155 calls: Vec<BACnetAssignedLandingCalls>,
156 ) -> Result<(), Error> {
157 self.doors.set_landing_calls(calls)
158 }
159
160 pub fn making_car_call(&self) -> &[u8] {
163 &self.doors.making_car_call
164 }
165
166 pub fn set_making_car_call(&mut self, floors: Vec<u8>) -> Result<(), Error> {
174 self.doors.set_making_car_call(floors)
175 }
176
177 pub fn registered_car_call(&self) -> &[BACnetLiftCarCallList] {
180 &self.doors.registered_car_call
181 }
182
183 pub fn set_registered_car_call(
191 &mut self,
192 calls: Vec<BACnetLiftCarCallList>,
193 ) -> Result<(), Error> {
194 self.doors.set_registered_car_call(calls)
195 }
196
197 pub fn car_door_command(&self) -> &[LiftCarDoorCommand] {
199 &self.doors.command
200 }
201
202 pub fn set_car_door_command(&mut self, commands: Vec<LiftCarDoorCommand>) -> Result<(), Error> {
210 self.doors.set_command(commands)
211 }
212
213 pub fn car_assigned_direction(&self) -> LiftCarDirection {
216 self.car.assigned_direction
217 }
218
219 pub fn set_car_assigned_direction(&mut self, direction: LiftCarDirection) -> Result<(), Error> {
224 self.car.assigned_direction = checked(direction, direction_in_range)?;
225 Ok(())
226 }
227
228 pub fn car_door_zone(&self) -> bool {
230 self.car.door_zone
231 }
232
233 pub fn set_car_door_zone(&mut self, in_zone: bool) {
236 self.car.door_zone = in_zone;
237 }
238
239 pub fn car_mode(&self) -> LiftCarMode {
241 self.car.mode
242 }
243
244 pub fn set_car_mode(&mut self, mode: LiftCarMode) -> Result<(), Error> {
249 self.car.mode = checked(mode, car_state::mode_in_range)?;
250 Ok(())
251 }
252
253 pub fn next_stopping_floor(&self) -> u8 {
256 self.car.next_stopping_floor
257 }
258
259 pub fn set_next_stopping_floor(&mut self, floor: u8) {
262 self.car.next_stopping_floor = floor;
263 }
264
265 pub fn car_drive_status(&self) -> LiftCarDriveStatus {
267 self.car.drive_status
268 }
269
270 pub fn set_car_drive_status(&mut self, status: LiftCarDriveStatus) -> Result<(), Error> {
275 self.car.drive_status = checked(status, car_state::drive_status_in_range)?;
276 Ok(())
277 }
278
279 pub fn car_load_units(&self) -> EngineeringUnits {
281 self.car_load_units
282 }
283
284 pub fn set_car_load_units(&mut self, units: EngineeringUnits) -> Result<(), Error> {
290 if units.to_raw() > 65_535 {
291 return Err(common::value_out_of_range_error());
292 }
293 self.car_load_units = units;
294 Ok(())
295 }
296}
297
298impl BACnetObject for LiftObject {
299 fn object_identifier(&self) -> ObjectIdentifier {
300 self.oid
301 }
302
303 fn object_name(&self) -> &str {
304 &self.name
305 }
306
307 fn read_property(
308 &self,
309 property: PropertyIdentifier,
310 array_index: Option<u32>,
311 ) -> Result<PropertyValue, Error> {
312 if let Some(result) = read_common_properties!(self, property, array_index) {
313 return result;
314 }
315 if let Some(value) = self.membership.read(property) {
316 return Ok(value);
317 }
318 if let Some(value) = self.energy_meter.read(property) {
319 return Ok(value);
320 }
321 if let Some(value) = self.car.read(property) {
322 return Ok(value);
323 }
324 if let Some(result) = self.doors.read(property, array_index) {
325 return result;
326 }
327 match property {
328 p if p == PropertyIdentifier::OBJECT_TYPE => {
329 Ok(PropertyValue::Enumerated(ObjectType::LIFT.to_raw()))
330 }
331 p if p == PropertyIdentifier::FLOOR_TEXT => common::read_array(
332 self.floor_text
333 .iter()
334 .cloned()
335 .map(PropertyValue::CharacterString)
336 .collect(),
337 array_index,
338 ),
339 p if p == PropertyIdentifier::CAR_POSITION => {
340 Ok(PropertyValue::Unsigned(self.car_position.into()))
341 }
342 p if p == PropertyIdentifier::CAR_MOVING_DIRECTION => Ok(PropertyValue::Enumerated(
343 self.car_moving_direction.to_raw(),
344 )),
345 p if p == PropertyIdentifier::CAR_LOAD => Ok(PropertyValue::Real(self.car_load)),
346 p if p == PropertyIdentifier::CAR_LOAD_UNITS => {
347 Ok(PropertyValue::Enumerated(self.car_load_units.to_raw()))
348 }
349 p if p == PropertyIdentifier::PASSENGER_ALARM => {
350 Ok(PropertyValue::Boolean(self.passenger_alarm))
351 }
352 p if p == PropertyIdentifier::FAULT_SIGNALS => Ok(PropertyValue::List(
353 self.fault_signals
354 .iter()
355 .map(|fault| PropertyValue::Enumerated(fault.to_raw()))
356 .collect(),
357 )),
358 _ => Err(common::unknown_property_error()),
359 }
360 }
361
362 fn write_property(
363 &mut self,
364 property: PropertyIdentifier,
365 array_index: Option<u32>,
366 value: PropertyValue,
367 _priority: Option<u8>,
368 ) -> Result<(), Error> {
369 if let Some(result) =
370 common::write_out_of_service(&mut self.out_of_service, property, &value)
371 {
372 return result;
373 }
374 if let Some(result) = common::write_description(&mut self.description, property, &value) {
375 return result;
376 }
377 if let Some(result) = self.energy_meter.write(property, &value) {
378 return result;
379 }
380 if let Some(result) = self.car.write(property, &value, self.out_of_service) {
384 return result;
385 }
386 if let Some(result) = self
387 .doors
388 .write(property, array_index, &value, self.out_of_service)
389 {
390 return result;
391 }
392 match property {
393 p if p == PropertyIdentifier::CAR_POSITION => {
394 if let PropertyValue::Unsigned(v) = value {
395 self.car_position =
397 u8::try_from(v).map_err(|_| common::value_out_of_range_error())?;
398 Ok(())
399 } else {
400 Err(common::invalid_data_type_error())
401 }
402 }
403 p if p == PropertyIdentifier::CAR_MOVING_DIRECTION => {
404 if let PropertyValue::Enumerated(v) = value {
405 self.car_moving_direction =
406 checked(LiftCarDirection::from_raw(v), direction_in_range)?;
407 Ok(())
408 } else {
409 Err(common::invalid_data_type_error())
410 }
411 }
412 p if p == PropertyIdentifier::CAR_LOAD => {
413 if let PropertyValue::Real(v) = value {
414 common::reject_non_finite(v)?;
415 self.car_load = v;
416 Ok(())
417 } else {
418 Err(common::invalid_data_type_error())
419 }
420 }
421 p if p == PropertyIdentifier::PASSENGER_ALARM => {
422 if let PropertyValue::Boolean(v) = value {
423 self.passenger_alarm = v;
424 Ok(())
425 } else {
426 Err(common::invalid_data_type_error())
427 }
428 }
429 p if p == PropertyIdentifier::FAULT_SIGNALS => {
430 self.fault_signals =
431 decode_fault_signals(value, LiftFault::ALL_NAMED, LiftFault::from_raw)?;
432 Ok(())
433 }
434 _ => Err(crate::common::unhandled_write_error(
435 self.property_metadata().as_ref(),
436 property,
437 array_index,
438 )),
439 }
440 }
441
442 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
443 metadata::for_lift_object(self)
444 }
445
446 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
447 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
448 }
449}