1use bacnet_types::enums::{
8 EscalatorFault, EscalatorMode, EscalatorOperationDirection, ObjectType, PropertyIdentifier,
9};
10use bacnet_types::error::Error;
11use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
12use std::{borrow::Cow, collections::HashSet};
13
14use crate::common::{self, read_common_properties};
15use crate::traits::BACnetObject;
16
17pub struct ElevatorGroupObject {
23 oid: ObjectIdentifier,
24 name: String,
25 description: String,
26 group_id: u64,
28 group_members: Vec<ObjectIdentifier>,
30 group_mode: u32,
32 landing_calls: u64,
34 landing_call_control: u32,
36 status_flags: StatusFlags,
37 out_of_service: bool,
38 reliability: u32,
39}
40
41impl ElevatorGroupObject {
42 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
44 let oid = ObjectIdentifier::new(ObjectType::ELEVATOR_GROUP, instance)?;
45 Ok(Self {
46 oid,
47 name: name.into(),
48 description: String::new(),
49 group_id: 0,
50 group_members: Vec::new(),
51 group_mode: 0, landing_calls: 0,
53 landing_call_control: 0,
54 status_flags: StatusFlags::empty(),
55 out_of_service: false,
56 reliability: 0,
57 })
58 }
59
60 pub fn add_member(&mut self, oid: ObjectIdentifier) {
62 self.group_members.push(oid);
63 }
64}
65
66impl BACnetObject for ElevatorGroupObject {
67 fn object_identifier(&self) -> ObjectIdentifier {
68 self.oid
69 }
70
71 fn object_name(&self) -> &str {
72 &self.name
73 }
74
75 fn read_property(
76 &self,
77 property: PropertyIdentifier,
78 array_index: Option<u32>,
79 ) -> Result<PropertyValue, Error> {
80 if let Some(result) = read_common_properties!(self, property, array_index) {
81 return result;
82 }
83 match property {
84 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
85 ObjectType::ELEVATOR_GROUP.to_raw(),
86 )),
87 p if p == PropertyIdentifier::GROUP_ID => Ok(PropertyValue::Unsigned(self.group_id)),
88 p if p == PropertyIdentifier::GROUP_MEMBERS => {
89 let items: Vec<PropertyValue> = self
90 .group_members
91 .iter()
92 .map(|oid| PropertyValue::ObjectIdentifier(*oid))
93 .collect();
94 Ok(PropertyValue::List(items))
95 }
96 p if p == PropertyIdentifier::GROUP_MODE => {
97 Ok(PropertyValue::Enumerated(self.group_mode))
98 }
99 p if p == PropertyIdentifier::LANDING_CALLS => {
100 Ok(PropertyValue::Unsigned(self.landing_calls))
101 }
102 p if p == PropertyIdentifier::LANDING_CALL_CONTROL => {
103 Ok(PropertyValue::Enumerated(self.landing_call_control))
104 }
105 _ => Err(common::unknown_property_error()),
106 }
107 }
108
109 fn write_property(
110 &mut self,
111 property: PropertyIdentifier,
112 _array_index: Option<u32>,
113 value: PropertyValue,
114 _priority: Option<u8>,
115 ) -> Result<(), Error> {
116 if let Some(result) =
117 common::write_out_of_service(&mut self.out_of_service, property, &value)
118 {
119 return result;
120 }
121 if let Some(result) = common::write_description(&mut self.description, property, &value) {
122 return result;
123 }
124 match property {
125 p if p == PropertyIdentifier::GROUP_ID => {
126 if let PropertyValue::Unsigned(v) = value {
127 self.group_id = v;
128 Ok(())
129 } else {
130 Err(common::invalid_data_type_error())
131 }
132 }
133 p if p == PropertyIdentifier::GROUP_MODE => {
134 if let PropertyValue::Enumerated(v) = value {
135 self.group_mode = v;
136 Ok(())
137 } else {
138 Err(common::invalid_data_type_error())
139 }
140 }
141 p if p == PropertyIdentifier::LANDING_CALL_CONTROL => {
142 if let PropertyValue::Enumerated(v) = value {
143 self.landing_call_control = v;
144 Ok(())
145 } else {
146 Err(common::invalid_data_type_error())
147 }
148 }
149 _ => Err(common::write_access_denied_error()),
150 }
151 }
152
153 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
154 static PROPS: &[PropertyIdentifier] = &[
155 PropertyIdentifier::OBJECT_IDENTIFIER,
156 PropertyIdentifier::OBJECT_NAME,
157 PropertyIdentifier::DESCRIPTION,
158 PropertyIdentifier::OBJECT_TYPE,
159 PropertyIdentifier::GROUP_ID,
160 PropertyIdentifier::GROUP_MEMBERS,
161 PropertyIdentifier::GROUP_MODE,
162 PropertyIdentifier::LANDING_CALLS,
163 PropertyIdentifier::LANDING_CALL_CONTROL,
164 PropertyIdentifier::STATUS_FLAGS,
165 PropertyIdentifier::OUT_OF_SERVICE,
166 PropertyIdentifier::RELIABILITY,
167 ];
168 Cow::Borrowed(PROPS)
169 }
170}
171
172pub struct EscalatorObject {
178 oid: ObjectIdentifier,
179 name: String,
180 description: String,
181 escalator_mode: EscalatorMode,
184 fault_signals: Vec<EscalatorFault>,
186 energy_meter: f32,
188 energy_meter_ref: Vec<u8>,
190 power_mode: bool,
192 operation_direction: EscalatorOperationDirection,
195 passenger_alarm: bool,
197 status_flags: StatusFlags,
198 out_of_service: bool,
199 reliability: u32,
200}
201
202impl EscalatorObject {
203 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
205 let oid = ObjectIdentifier::new(ObjectType::ESCALATOR, instance)?;
206 Ok(Self {
207 oid,
208 name: name.into(),
209 description: String::new(),
210 escalator_mode: EscalatorMode::UNKNOWN,
211 fault_signals: Vec::new(),
212 energy_meter: 0.0,
213 energy_meter_ref: Vec::new(),
214 power_mode: false,
215 operation_direction: EscalatorOperationDirection::UNKNOWN,
216 passenger_alarm: false,
217 status_flags: StatusFlags::empty(),
218 out_of_service: false,
219 reliability: 0,
220 })
221 }
222}
223
224impl BACnetObject for EscalatorObject {
225 fn object_identifier(&self) -> ObjectIdentifier {
226 self.oid
227 }
228
229 fn object_name(&self) -> &str {
230 &self.name
231 }
232
233 fn read_property(
234 &self,
235 property: PropertyIdentifier,
236 array_index: Option<u32>,
237 ) -> Result<PropertyValue, Error> {
238 if let Some(result) = read_common_properties!(self, property, array_index) {
239 return result;
240 }
241 match property {
242 p if p == PropertyIdentifier::OBJECT_TYPE => {
243 Ok(PropertyValue::Enumerated(ObjectType::ESCALATOR.to_raw()))
244 }
245 p if p == PropertyIdentifier::ESCALATOR_MODE => {
246 Ok(PropertyValue::Enumerated(self.escalator_mode.to_raw()))
247 }
248 p if p == PropertyIdentifier::FAULT_SIGNALS => {
249 let items: Vec<PropertyValue> = self
250 .fault_signals
251 .iter()
252 .map(|v| PropertyValue::Enumerated(v.to_raw()))
253 .collect();
254 Ok(PropertyValue::List(items))
255 }
256 p if p == PropertyIdentifier::ENERGY_METER => {
257 Ok(PropertyValue::Real(self.energy_meter))
258 }
259 p if p == PropertyIdentifier::ENERGY_METER_REF => {
260 Ok(PropertyValue::OctetString(self.energy_meter_ref.clone()))
261 }
262 p if p == PropertyIdentifier::POWER_MODE => Ok(PropertyValue::Boolean(self.power_mode)),
263 p if p == PropertyIdentifier::OPERATION_DIRECTION => {
264 Ok(PropertyValue::Enumerated(self.operation_direction.to_raw()))
265 }
266 p if p == PropertyIdentifier::PASSENGER_ALARM => {
267 Ok(PropertyValue::Boolean(self.passenger_alarm))
268 }
269 _ => Err(common::unknown_property_error()),
270 }
271 }
272
273 fn write_property(
274 &mut self,
275 property: PropertyIdentifier,
276 _array_index: Option<u32>,
277 value: PropertyValue,
278 _priority: Option<u8>,
279 ) -> Result<(), Error> {
280 if let Some(result) =
281 common::write_out_of_service(&mut self.out_of_service, property, &value)
282 {
283 return result;
284 }
285 if let Some(result) = common::write_description(&mut self.description, property, &value) {
286 return result;
287 }
288 match property {
289 p if p == PropertyIdentifier::POWER_MODE => {
290 if let PropertyValue::Boolean(v) = value {
291 self.power_mode = v;
292 Ok(())
293 } else {
294 Err(common::invalid_data_type_error())
295 }
296 }
297 p if p == PropertyIdentifier::ESCALATOR_MODE => {
298 if let PropertyValue::Enumerated(v) = value {
299 let named = EscalatorMode::ALL_NAMED
300 .iter()
301 .any(|&(_, value)| value.to_raw() == v);
302 if !(named || (1024..=65535).contains(&v)) {
303 return Err(common::value_out_of_range_error());
304 }
305 self.escalator_mode = EscalatorMode::from_raw(v);
306 Ok(())
307 } else {
308 Err(common::invalid_data_type_error())
309 }
310 }
311 p if p == PropertyIdentifier::OPERATION_DIRECTION => {
312 if let PropertyValue::Enumerated(v) = value {
313 let named = EscalatorOperationDirection::ALL_NAMED
320 .iter()
321 .any(|&(_, value)| value.to_raw() == v);
322 if !(named || (1024..=65535).contains(&v)) {
323 return Err(common::value_out_of_range_error());
324 }
325 self.operation_direction = EscalatorOperationDirection::from_raw(v);
326 Ok(())
327 } else {
328 Err(common::invalid_data_type_error())
329 }
330 }
331 p if p == PropertyIdentifier::ENERGY_METER => {
332 if let PropertyValue::Real(v) = value {
333 if !v.is_finite() {
334 return Err(common::value_out_of_range_error());
335 }
336 self.energy_meter = v;
337 Ok(())
338 } else {
339 Err(common::invalid_data_type_error())
340 }
341 }
342 p if p == PropertyIdentifier::FAULT_SIGNALS => {
343 let values = match value {
344 PropertyValue::Enumerated(v) => vec![PropertyValue::Enumerated(v)],
345 PropertyValue::List(values) => values,
346 _ => return Err(common::invalid_data_type_error()),
347 };
348 let mut faults = Vec::with_capacity(values.len());
349 let mut seen = HashSet::with_capacity(values.len());
350 for value in values {
351 let PropertyValue::Enumerated(raw) = value else {
352 return Err(common::invalid_data_type_error());
353 };
354 let named = EscalatorFault::ALL_NAMED
355 .iter()
356 .any(|&(_, value)| value.to_raw() == raw);
357 if !(named || (1024..=65535).contains(&raw)) {
358 return Err(common::value_out_of_range_error());
359 }
360 let fault = EscalatorFault::from_raw(raw);
361 if !seen.insert(fault) {
362 return Err(common::value_out_of_range_error());
363 }
364 faults.push(fault);
365 }
366 self.fault_signals = faults;
367 Ok(())
368 }
369 p if p == PropertyIdentifier::PASSENGER_ALARM => {
370 if let PropertyValue::Boolean(v) = value {
371 self.passenger_alarm = v;
372 Ok(())
373 } else {
374 Err(common::invalid_data_type_error())
375 }
376 }
377 _ => Err(common::write_access_denied_error()),
378 }
379 }
380
381 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
382 static PROPS: &[PropertyIdentifier] = &[
383 PropertyIdentifier::OBJECT_IDENTIFIER,
384 PropertyIdentifier::OBJECT_NAME,
385 PropertyIdentifier::DESCRIPTION,
386 PropertyIdentifier::OBJECT_TYPE,
387 PropertyIdentifier::ESCALATOR_MODE,
388 PropertyIdentifier::FAULT_SIGNALS,
389 PropertyIdentifier::ENERGY_METER,
390 PropertyIdentifier::ENERGY_METER_REF,
391 PropertyIdentifier::POWER_MODE,
392 PropertyIdentifier::OPERATION_DIRECTION,
393 PropertyIdentifier::PASSENGER_ALARM,
394 PropertyIdentifier::STATUS_FLAGS,
395 PropertyIdentifier::OUT_OF_SERVICE,
396 PropertyIdentifier::RELIABILITY,
397 ];
398 Cow::Borrowed(PROPS)
399 }
400
401 fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
402 matches!(
403 property,
404 PropertyIdentifier::DESCRIPTION
405 | PropertyIdentifier::OUT_OF_SERVICE
406 | PropertyIdentifier::POWER_MODE
407 | PropertyIdentifier::OPERATION_DIRECTION
408 | PropertyIdentifier::ESCALATOR_MODE
409 | PropertyIdentifier::ENERGY_METER
410 | PropertyIdentifier::FAULT_SIGNALS
411 | PropertyIdentifier::PASSENGER_ALARM
412 )
413 }
414}
415
416pub struct LiftObject {
422 oid: ObjectIdentifier,
423 name: String,
424 description: String,
425 tracking_value: u64,
427 car_position: u64,
429 car_moving_direction: u32,
431 car_door_status: Vec<u64>,
433 car_load: u64,
435 landing_doors: u64,
437 floor_text: Vec<String>,
439 energy_meter: f32,
441 status_flags: StatusFlags,
442 out_of_service: bool,
443 reliability: u32,
444}
445
446impl LiftObject {
447 pub fn new(instance: u32, name: impl Into<String>, num_floors: usize) -> Result<Self, Error> {
451 let oid = ObjectIdentifier::new(ObjectType::LIFT, instance)?;
452 let floor_text = (1..=num_floors).map(|i| format!("Floor {i}")).collect();
453 Ok(Self {
454 oid,
455 name: name.into(),
456 description: String::new(),
457 tracking_value: 1,
458 car_position: 1,
459 car_moving_direction: 1, car_door_status: Vec::new(),
461 car_load: 0,
462 landing_doors: num_floors as u64,
463 floor_text,
464 energy_meter: 0.0,
465 status_flags: StatusFlags::empty(),
466 out_of_service: false,
467 reliability: 0,
468 })
469 }
470}
471
472impl BACnetObject for LiftObject {
473 fn object_identifier(&self) -> ObjectIdentifier {
474 self.oid
475 }
476
477 fn object_name(&self) -> &str {
478 &self.name
479 }
480
481 fn read_property(
482 &self,
483 property: PropertyIdentifier,
484 array_index: Option<u32>,
485 ) -> Result<PropertyValue, Error> {
486 if let Some(result) = read_common_properties!(self, property, array_index) {
487 return result;
488 }
489 match property {
490 p if p == PropertyIdentifier::OBJECT_TYPE => {
491 Ok(PropertyValue::Enumerated(ObjectType::LIFT.to_raw()))
492 }
493 p if p == PropertyIdentifier::TRACKING_VALUE => {
494 Ok(PropertyValue::Unsigned(self.tracking_value))
495 }
496 p if p == PropertyIdentifier::CAR_POSITION => {
497 Ok(PropertyValue::Unsigned(self.car_position))
498 }
499 p if p == PropertyIdentifier::CAR_MOVING_DIRECTION => {
500 Ok(PropertyValue::Enumerated(self.car_moving_direction))
501 }
502 p if p == PropertyIdentifier::CAR_DOOR_STATUS => {
503 let items: Vec<PropertyValue> = self
504 .car_door_status
505 .iter()
506 .map(|v| PropertyValue::Unsigned(*v))
507 .collect();
508 Ok(PropertyValue::List(items))
509 }
510 p if p == PropertyIdentifier::CAR_LOAD => Ok(PropertyValue::Unsigned(self.car_load)),
511 p if p == PropertyIdentifier::LANDING_DOOR_STATUS => {
512 Ok(PropertyValue::Unsigned(self.landing_doors))
513 }
514 p if p == PropertyIdentifier::FLOOR_TEXT => {
515 let items: Vec<PropertyValue> = self
516 .floor_text
517 .iter()
518 .map(|s| PropertyValue::CharacterString(s.clone()))
519 .collect();
520 Ok(PropertyValue::List(items))
521 }
522 p if p == PropertyIdentifier::ENERGY_METER => {
523 Ok(PropertyValue::Real(self.energy_meter))
524 }
525 p if p == PropertyIdentifier::FLOOR_NUMBER => {
526 Ok(PropertyValue::Unsigned(self.tracking_value))
527 }
528 _ => Err(common::unknown_property_error()),
529 }
530 }
531
532 fn write_property(
533 &mut self,
534 property: PropertyIdentifier,
535 _array_index: Option<u32>,
536 value: PropertyValue,
537 _priority: Option<u8>,
538 ) -> Result<(), Error> {
539 if let Some(result) =
540 common::write_out_of_service(&mut self.out_of_service, property, &value)
541 {
542 return result;
543 }
544 if let Some(result) = common::write_description(&mut self.description, property, &value) {
545 return result;
546 }
547 match property {
548 p if p == PropertyIdentifier::TRACKING_VALUE => {
549 if let PropertyValue::Unsigned(v) = value {
550 self.tracking_value = v;
551 Ok(())
552 } else {
553 Err(common::invalid_data_type_error())
554 }
555 }
556 p if p == PropertyIdentifier::CAR_POSITION => {
557 if let PropertyValue::Unsigned(v) = value {
558 self.car_position = v;
559 Ok(())
560 } else {
561 Err(common::invalid_data_type_error())
562 }
563 }
564 p if p == PropertyIdentifier::CAR_MOVING_DIRECTION => {
565 if let PropertyValue::Enumerated(v) = value {
566 if v > 3 {
567 return Err(common::value_out_of_range_error());
568 }
569 self.car_moving_direction = v;
570 Ok(())
571 } else {
572 Err(common::invalid_data_type_error())
573 }
574 }
575 p if p == PropertyIdentifier::CAR_LOAD => {
576 if let PropertyValue::Unsigned(v) = value {
577 if v > 100 {
578 return Err(common::value_out_of_range_error());
579 }
580 self.car_load = v;
581 Ok(())
582 } else {
583 Err(common::invalid_data_type_error())
584 }
585 }
586 _ => Err(common::write_access_denied_error()),
587 }
588 }
589
590 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
591 static PROPS: &[PropertyIdentifier] = &[
592 PropertyIdentifier::OBJECT_IDENTIFIER,
593 PropertyIdentifier::OBJECT_NAME,
594 PropertyIdentifier::DESCRIPTION,
595 PropertyIdentifier::OBJECT_TYPE,
596 PropertyIdentifier::TRACKING_VALUE,
597 PropertyIdentifier::CAR_POSITION,
598 PropertyIdentifier::CAR_MOVING_DIRECTION,
599 PropertyIdentifier::CAR_DOOR_STATUS,
600 PropertyIdentifier::CAR_LOAD,
601 PropertyIdentifier::LANDING_DOOR_STATUS,
602 PropertyIdentifier::FLOOR_TEXT,
603 PropertyIdentifier::ENERGY_METER,
604 PropertyIdentifier::STATUS_FLAGS,
605 PropertyIdentifier::OUT_OF_SERVICE,
606 PropertyIdentifier::RELIABILITY,
607 ];
608 Cow::Borrowed(PROPS)
609 }
610}
611
612#[cfg(test)]
617mod tests;