use super::*;
use PropertyIdentifier as P;
use bacnet_objects::{
elevator::{ElevatorGroupObject, EscalatorObject, LiftObject},
traits::BACnetObject,
};
use bacnet_types::error::RemoteError;
use bacnet_types::primitives::PropertyValue;
fn elevator_objects(configured: bool) -> [Box<dyn BACnetObject>; 3] {
let mut group = ElevatorGroupObject::new(7, "EG-7").unwrap();
let mut escalator = EscalatorObject::new(7, "ESC-7").unwrap();
let mut lift = LiftObject::new(7, "LIFT-7", 2).unwrap();
if configured {
let lift1 = ObjectIdentifier::new(ObjectType::LIFT, 1).unwrap();
let lift2 = ObjectIdentifier::new(ObjectType::LIFT, 2).unwrap();
group.add_member(lift1);
group.add_member(lift2);
group
.set_machine_room_id(
ObjectIdentifier::new(ObjectType::POSITIVE_INTEGER_VALUE, 9).unwrap(),
)
.unwrap();
escalator
.write_property(P::POWER_MODE, None, PropertyValue::Boolean(true), None)
.unwrap();
escalator
.write_property(
P::OPERATION_DIRECTION,
None,
PropertyValue::Enumerated(2),
None,
)
.unwrap();
escalator
.write_property(P::ENERGY_METER, None, PropertyValue::Real(18.75), None)
.unwrap();
lift.write_property(P::CAR_POSITION, None, PropertyValue::Unsigned(2), None)
.unwrap();
for object in [&mut escalator as &mut dyn BACnetObject, &mut lift] {
object
.write_property(P::PASSENGER_ALARM, None, PropertyValue::Boolean(true), None)
.unwrap();
}
escalator.set_group_id(47);
lift.set_installation_id(2);
lift.set_energy_meter_ref(bacnet_types::constructed::BACnetDeviceObjectReference {
device_identifier: Some(ObjectIdentifier::new(ObjectType::DEVICE, 9).unwrap()),
object_identifier: ObjectIdentifier::new(ObjectType::ACCUMULATOR, 3).unwrap(),
})
.unwrap();
}
let mut objects: [Box<dyn BACnetObject>; 3] =
[Box::new(group), Box::new(escalator), Box::new(lift)];
for object in &mut objects {
object
.write_property(
P::DESCRIPTION,
None,
PropertyValue::CharacterString("long elevator label".repeat(100)),
None,
)
.unwrap();
if object.object_identifier().object_type() != ObjectType::ELEVATOR_GROUP {
object
.write_property(
P::OUT_OF_SERVICE,
None,
PropertyValue::Boolean(configured),
None,
)
.unwrap();
}
}
objects
}
fn expected_lists(kind: ObjectType) -> (Vec<P>, Vec<P>, Vec<P>) {
let all = match kind {
ObjectType::ELEVATOR_GROUP => vec![
P::OBJECT_IDENTIFIER,
P::OBJECT_NAME,
P::DESCRIPTION,
P::OBJECT_TYPE,
P::MACHINE_ROOM_ID,
P::GROUP_ID,
P::GROUP_MEMBERS,
P::GROUP_MODE,
P::LANDING_CALLS,
P::LANDING_CALL_CONTROL,
],
ObjectType::ESCALATOR => vec![
P::OBJECT_IDENTIFIER,
P::OBJECT_NAME,
P::OBJECT_TYPE,
P::DESCRIPTION,
P::STATUS_FLAGS,
P::ELEVATOR_GROUP,
P::GROUP_ID,
P::INSTALLATION_ID,
P::POWER_MODE,
P::OPERATION_DIRECTION,
P::ESCALATOR_MODE,
P::ENERGY_METER,
P::ENERGY_METER_REF,
P::RELIABILITY,
P::OUT_OF_SERVICE,
P::FAULT_SIGNALS,
P::PASSENGER_ALARM,
],
_ => vec![
P::OBJECT_IDENTIFIER,
P::OBJECT_NAME,
P::OBJECT_TYPE,
P::DESCRIPTION,
P::STATUS_FLAGS,
P::ELEVATOR_GROUP,
P::GROUP_ID,
P::INSTALLATION_ID,
P::FLOOR_TEXT,
P::ASSIGNED_LANDING_CALLS,
P::MAKING_CAR_CALL,
P::REGISTERED_CAR_CALL,
P::CAR_POSITION,
P::CAR_MOVING_DIRECTION,
P::CAR_ASSIGNED_DIRECTION,
P::CAR_DOOR_STATUS,
P::CAR_DOOR_COMMAND,
P::CAR_DOOR_ZONE,
P::CAR_MODE,
P::CAR_LOAD,
P::CAR_LOAD_UNITS,
P::NEXT_STOPPING_FLOOR,
P::PASSENGER_ALARM,
P::ENERGY_METER,
P::ENERGY_METER_REF,
P::RELIABILITY,
P::OUT_OF_SERVICE,
P::CAR_DRIVE_STATUS,
P::FAULT_SIGNALS,
P::LANDING_DOOR_STATUS,
],
};
let optional = match kind {
ObjectType::ELEVATOR_GROUP => vec![
P::DESCRIPTION,
P::GROUP_MODE,
P::LANDING_CALLS,
P::LANDING_CALL_CONTROL,
],
ObjectType::ESCALATOR => vec![
P::DESCRIPTION,
P::POWER_MODE,
P::ESCALATOR_MODE,
P::ENERGY_METER,
P::ENERGY_METER_REF,
P::RELIABILITY,
P::FAULT_SIGNALS,
],
_ => vec![
P::DESCRIPTION,
P::FLOOR_TEXT,
P::ASSIGNED_LANDING_CALLS,
P::MAKING_CAR_CALL,
P::REGISTERED_CAR_CALL,
P::CAR_ASSIGNED_DIRECTION,
P::CAR_DOOR_COMMAND,
P::CAR_DOOR_ZONE,
P::CAR_MODE,
P::CAR_LOAD,
P::CAR_LOAD_UNITS,
P::NEXT_STOPPING_FLOOR,
P::ENERGY_METER,
P::ENERGY_METER_REF,
P::RELIABILITY,
P::CAR_DRIVE_STATUS,
P::LANDING_DOOR_STATUS,
],
};
let required: Vec<_> = all
.iter()
.copied()
.filter(|p| !optional.contains(p))
.collect();
(all, required, optional)
}
#[test]
fn rpm_elevator_metadata_selectors_preserve_bytes_and_budgets() {
for configured in [false, true] {
for object in elevator_objects(configured) {
let oid = object.object_identifier();
let (all, required, optional) = expected_lists(oid.object_type());
let mut db = ObjectDatabase::new();
db.add(object).unwrap();
for (selector, expected) in [
(P::ALL, all.as_slice()),
(P::REQUIRED, required.as_slice()),
(P::OPTIONAL, optional.as_slice()),
(P::PROPERTY_LIST, &[P::PROPERTY_LIST]),
] {
assert_rpm_selector_bytes(&db, oid, selector, expected);
}
}
}
}
#[test]
fn rpm_elevator_metadata_does_not_enable_create_object() {
use bacnet_services::object_mgmt::{CreateObjectRequest, ObjectSpecifier};
let cases = [
(
ObjectType::ELEVATOR_GROUP,
ObjectIdentifier::new(ObjectType::ELEVATOR_GROUP, 7).unwrap(),
),
(
ObjectType::ESCALATOR,
ObjectIdentifier::new(ObjectType::ESCALATOR, 7).unwrap(),
),
(
ObjectType::LIFT,
ObjectIdentifier::new(ObjectType::LIFT, 7).unwrap(),
),
];
for (kind, oid) in cases {
for object_specifier in [
ObjectSpecifier::Type(kind),
ObjectSpecifier::Identifier(oid),
] {
let mut db = ObjectDatabase::new();
let mut request = BytesMut::new();
CreateObjectRequest {
object_specifier,
list_of_initial_values: vec![],
}
.encode(&mut request);
let mut response = BytesMut::new();
let result = handle_create_object(&mut db, &request, &mut response);
assert!(
matches!(result, Err(Error::Remote(RemoteError::Error { class, code }))
if class == ErrorClass::OBJECT
&& code == ErrorCode::UNSUPPORTED_OBJECT_TYPE)
);
assert!(response.is_empty());
assert!(db.is_empty());
}
}
}
#[test]
fn elevator_delete_object_removes_each_trio_member() {
use bacnet_services::object_mgmt::DeleteObjectRequest;
for object in elevator_objects(false) {
let oid = object.object_identifier();
let mut db = ObjectDatabase::new();
db.add(object).unwrap();
let mut request = BytesMut::new();
DeleteObjectRequest {
object_identifier: oid,
}
.encode(&mut request);
handle_delete_object(&mut db, &request).unwrap();
assert!(db.get(&oid).is_none());
}
}