use bacnet_types::error::Error;
use super::color_command::{self, decode_xy_color};
use super::lighting_command;
use super::tagged::expect_closing;
use crate::primitives;
use crate::tags::{self, TagClass};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstructedChannelValue {
LightingCommand,
XyColor,
ColorCommand,
}
impl ConstructedChannelValue {
fn of_tag(tag: u8) -> Option<Self> {
match tag {
0 => Some(Self::LightingCommand),
1 => Some(Self::XyColor),
2 => Some(Self::ColorCommand),
_ => None,
}
}
}
pub fn channel_value_end(data: &[u8], offset: usize) -> Result<usize, Error> {
if offset >= data.len() {
return Err(Error::decoding(offset, "missing channel value"));
}
let (tag, pos) = tags::decode_tag(data, offset)?;
if tag.class == TagClass::Application {
return primitives::validate_application_value(data, offset);
}
let kind = tag
.is_opening
.then(|| ConstructedChannelValue::of_tag(tag.number))
.flatten()
.ok_or_else(|| {
Error::decoding(
offset,
"a channel value is an application-tagged primitive, or a lighting command, xy \
color or color command in context tag 0, 1 or 2",
)
})?;
match kind {
ConstructedChannelValue::LightingCommand => {
let (command, end, oversized) = lighting_command::decode_fields(data, pos)?;
let after = expect_closing(data, end, 0, "lighting command")?;
if let Some(field) = oversized {
return Err(Error::decoding(offset, format!("lighting command {field}")));
}
if let Some(priority) = command.priority.filter(|p| !(1..=16).contains(p)) {
return Err(Error::out_of_range(
offset,
format!("lighting command priority {priority} out of range 1-16"),
));
}
Ok(after)
}
ConstructedChannelValue::XyColor => {
let (_, end) = decode_xy_color(data, pos)?;
expect_closing(data, end, 1, "xy color")
}
ConstructedChannelValue::ColorCommand => {
let (_, end, oversized) = color_command::decode_fields(data, pos)?;
let after = expect_closing(data, end, 2, "color command")?;
if let Some(field) = oversized {
return Err(Error::decoding(offset, format!("color command {field}")));
}
Ok(after)
}
}
}
pub fn constructed_channel_value(data: &[u8]) -> Option<ConstructedChannelValue> {
let (tag, _) = tags::decode_tag(data, 0).ok()?;
if tag.class != TagClass::Context || !tag.is_opening {
return None;
}
let kind = ConstructedChannelValue::of_tag(tag.number)?;
(channel_value_end(data, 0).ok()? == data.len()).then_some(kind)
}