use bacnet_types::constructed::{BACnetColorCommand, BACnetXyColor};
use bacnet_types::enums::ColorOperation;
use bacnet_types::error::Error;
use bytes::BytesMut;
use super::members::unsigned_field;
use super::tagged::{
decode_app_fixed, decode_optional_ctx, expect_closing, expect_end, expect_opening,
next_is_opening,
};
use crate::primitives;
use crate::tags::{self, app_tag};
const WHAT: &str = "color command";
const XY: &str = "xy color";
pub fn encode_xy_color(buf: &mut BytesMut, value: &BACnetXyColor) {
primitives::encode_app_real(buf, value.x);
primitives::encode_app_real(buf, value.y);
}
fn app_real(data: &[u8], offset: usize, what: &str) -> Result<(f32, usize), Error> {
let (octets, end) = decode_app_fixed(data, offset, app_tag::REAL, 4, what)?;
Ok((primitives::decode_real(octets)?, end))
}
pub fn decode_xy_color(data: &[u8], offset: usize) -> Result<(BACnetXyColor, usize), Error> {
let (x, offset) = app_real(data, offset, XY)?;
let (y, offset) = app_real(data, offset, XY)?;
Ok((BACnetXyColor { x, y }, offset))
}
pub fn encode_color_command(buf: &mut BytesMut, value: &BACnetColorCommand) {
primitives::encode_ctx_enumerated(buf, 0, value.operation.to_raw());
if let Some(color) = &value.target_color {
tags::encode_opening_tag(buf, 1);
encode_xy_color(buf, color);
tags::encode_closing_tag(buf, 1);
}
let unsigneds = [
value.target_color_temperature,
value.fade_time,
value.ramp_rate,
value.step_increment,
];
for (tag, field) in (2..).zip(unsigneds) {
if let Some(field) = field {
primitives::encode_ctx_unsigned(buf, tag, field.into());
}
}
}
pub(super) type Fields = (BACnetColorCommand, usize, Option<&'static str>);
fn target_color(data: &[u8], offset: usize) -> Result<(Option<BACnetXyColor>, usize), Error> {
if !next_is_opening(data, offset, 1)? {
return Ok((None, offset));
}
let content = expect_opening(data, offset, 1, WHAT)?;
let (color, end) = decode_xy_color(data, content)?;
let end = expect_closing(data, end, 1, WHAT)?;
Ok((Some(color), end))
}
pub(super) fn decode_fields(data: &[u8], offset: usize) -> Result<Fields, Error> {
let mut oversized = None;
let (operation, offset) = unsigned_field::<u32>(
data,
offset,
0,
WHAT,
"operation [0] exceeds 32 bits",
&mut oversized,
)?;
let (target_color, offset) = target_color(data, offset)?;
let mut unsigned = |offset: usize, tag: u8, too_wide: &'static str| {
decode_optional_ctx(data, offset, tag, WHAT, |data, at, tag, _| {
unsigned_field::<u32>(data, at, tag, WHAT, too_wide, &mut oversized)
})
};
let (target_color_temperature, offset) =
unsigned(offset, 2, "target-color-temperature [2] exceeds 32 bits")?;
let (fade_time, offset) = unsigned(offset, 3, "fade-time [3] exceeds 32 bits")?;
let (ramp_rate, offset) = unsigned(offset, 4, "ramp-rate [4] exceeds 32 bits")?;
let (step_increment, offset) = unsigned(offset, 5, "step-increment [5] exceeds 32 bits")?;
let command = BACnetColorCommand {
operation: ColorOperation::from_raw(operation),
target_color,
target_color_temperature,
fade_time,
ramp_rate,
step_increment,
};
Ok((command, offset, oversized))
}
fn too_wide(field: &'static str) -> Error {
Error::OutOfRange(format!("{WHAT} {field}"))
}
pub fn decode_color_command(
data: &[u8],
offset: usize,
) -> Result<(BACnetColorCommand, usize), Error> {
let (command, end, oversized) = decode_fields(data, offset)?;
match oversized {
Some(field) => Err(too_wide(field)),
None => Ok((command, end)),
}
}
pub fn decode_color_command_value(data: &[u8]) -> Result<BACnetColorCommand, Error> {
let (command, end, oversized) = decode_fields(data, 0)?;
expect_end(data, end, end, WHAT)?;
match oversized {
Some(field) => Err(too_wide(field)),
None => Ok(command),
}
}