use std::ops::RangeInclusive;
use bacnet_encoding::constructed::{decode_lighting_command_value, encode_lighting_command};
use bacnet_types::constructed::BACnetLightingCommand;
use bacnet_types::enums::LightingOperation;
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use bytes::BytesMut;
use super::{lighting_percent, DEFAULT_FADE_TIME_MS};
use crate::common;
const PROPRIETARY_OPERATIONS: RangeInclusive<u32> = 256..=65_535;
enum Uses {
Fade,
Ramp,
Step,
Priority,
}
fn uses(operation: LightingOperation) -> Result<Uses, Error> {
Ok(match operation {
LightingOperation::FADE_TO => Uses::Fade,
LightingOperation::RAMP_TO => Uses::Ramp,
LightingOperation::STEP_UP
| LightingOperation::STEP_DOWN
| LightingOperation::STEP_ON
| LightingOperation::STEP_OFF => Uses::Step,
LightingOperation::WARN
| LightingOperation::WARN_OFF
| LightingOperation::WARN_RELINQUISH
| LightingOperation::STOP => Uses::Priority,
other if PROPRIETARY_OPERATIONS.contains(&other.to_raw()) => Uses::Priority,
_ => return Err(common::value_out_of_range_error()),
})
}
fn within<T: PartialOrd>(value: Option<T>, range: RangeInclusive<T>) -> Result<(), Error> {
match value {
Some(value) if !range.contains(&value) => Err(common::value_out_of_range_error()),
_ => Ok(()),
}
}
fn target_level(command: &BACnetLightingCommand) -> Result<(), Error> {
let level = command
.target_level
.ok_or_else(common::value_out_of_range_error)?;
within(Some(level), 0.0..=100.0)
}
pub(super) fn check(command: &BACnetLightingCommand) -> Result<(), Error> {
match uses(command.operation)? {
Uses::Fade => {
target_level(command)?;
within(command.fade_time, DEFAULT_FADE_TIME_MS)?;
}
Uses::Ramp => {
target_level(command)?;
if let Some(rate) = command.ramp_rate {
lighting_percent(rate)?;
}
}
Uses::Step => {
if let Some(increment) = command.step_increment {
lighting_percent(increment)?;
}
}
Uses::Priority => {}
}
within(command.priority, 1..=16)
}
pub(super) fn decode_write(value: PropertyValue) -> Result<BACnetLightingCommand, Error> {
let command = common::decode_command_write(value, decode_lighting_command_value)?;
check(&command)?;
Ok(command)
}
pub(super) fn encode(command: &BACnetLightingCommand) -> PropertyValue {
let mut encoded = BytesMut::new();
encode_lighting_command(&mut encoded, command);
PropertyValue::ApplicationData(encoded.to_vec())
}