use bacnet_encoding::constructed::{
decode_assigned_landing_calls, decode_landing_door_status, decode_lift_car_call_list,
encode_assigned_landing_calls, encode_landing_door_status, encode_lift_car_call_list,
};
use bacnet_types::constructed::{
BACnetAssignedLandingCalls, BACnetLandingDoorStatus, BACnetLiftCarCallList,
};
use bacnet_types::enums::{DoorStatus, LiftCarDoorCommand};
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use bytes::BytesMut;
use super::{checked, direction_in_range, named_or_proprietary};
use crate::common;
pub(super) fn door_status_in_range(status: &DoorStatus) -> bool {
named_or_proprietary(DoorStatus::ALL_NAMED, *status, status.to_raw())
}
pub(super) fn landing_doors_in_range(car_door: &BACnetLandingDoorStatus) -> bool {
car_door
.landing_doors
.iter()
.all(|landing| door_status_in_range(&landing.door_status))
}
pub(super) fn landing_calls_in_range(car_door: &BACnetAssignedLandingCalls) -> bool {
car_door
.landing_calls
.iter()
.all(|call| direction_in_range(&call.direction))
}
pub(super) fn door_command_in_range(command: &LiftCarDoorCommand) -> bool {
LiftCarDoorCommand::ALL_NAMED
.iter()
.any(|&(_, named)| named == *command)
}
pub(super) fn always_in_range<T>(_: &T) -> bool {
true
}
pub(super) fn decode_car_door(value: PropertyValue) -> Result<DoorStatus, Error> {
let PropertyValue::Enumerated(raw) = value else {
return Err(common::invalid_data_type_error());
};
checked(DoorStatus::from_raw(raw), door_status_in_range)
}
pub(super) fn decode_door_command(value: PropertyValue) -> Result<LiftCarDoorCommand, Error> {
let PropertyValue::Enumerated(raw) = value else {
return Err(common::invalid_data_type_error());
};
checked(LiftCarDoorCommand::from_raw(raw), door_command_in_range)
}
pub(super) fn decode_car_call(value: PropertyValue) -> Result<u8, Error> {
let PropertyValue::Unsigned(floor) = value else {
return Err(common::invalid_data_type_error());
};
u8::try_from(floor).map_err(|_| common::value_out_of_range_error())
}
pub(super) fn decode_landing_doors(value: PropertyValue) -> Result<BACnetLandingDoorStatus, Error> {
checked(
decode_frame(value, decode_landing_door_status)?,
landing_doors_in_range,
)
}
pub(super) fn decode_landing_calls(
value: PropertyValue,
) -> Result<BACnetAssignedLandingCalls, Error> {
checked(
decode_frame(value, decode_assigned_landing_calls)?,
landing_calls_in_range,
)
}
pub(super) fn decode_car_calls(value: PropertyValue) -> Result<BACnetLiftCarCallList, Error> {
decode_frame(value, decode_lift_car_call_list)
}
pub(super) fn encode_frame<T>(element: &T, encode: fn(&mut BytesMut, &T)) -> PropertyValue {
let mut encoded = BytesMut::new();
encode(&mut encoded, element);
PropertyValue::ApplicationData(encoded.to_vec())
}
pub(super) fn encode_landing_doors(element: &BACnetLandingDoorStatus) -> PropertyValue {
encode_frame(element, encode_landing_door_status)
}
pub(super) fn encode_landing_calls(element: &BACnetAssignedLandingCalls) -> PropertyValue {
encode_frame(element, encode_assigned_landing_calls)
}
pub(super) fn encode_car_calls(element: &BACnetLiftCarCallList) -> PropertyValue {
encode_frame(element, encode_lift_car_call_list)
}
type FrameDecoder<T> = fn(&[u8], usize) -> Result<(T, usize), Error>;
fn decode_frame<T>(value: PropertyValue, decode: FrameDecoder<T>) -> Result<T, Error> {
let PropertyValue::ApplicationData(bytes) = value else {
return Err(common::invalid_data_type_error());
};
let (element, consumed) = decode(&bytes, 0).map_err(|error| match error {
Error::OutOfRange(_) => common::value_out_of_range_error(),
_ => common::invalid_data_encoding_error(),
})?;
if consumed != bytes.len() {
return Err(common::invalid_data_encoding_error());
}
Ok(element)
}