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