#[cfg(feature = "std")]
use std::path::Path;
use bacnet_types::constructed::BACnetXyColor;
use bacnet_types::error::Error;
use bacnet_types::primitives::ObjectIdentifier;
use super::command;
#[cfg(feature = "std")]
use crate::durable::file::ObjectFile;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ColorOutputValue {
Xy(BACnetXyColor),
Kelvin(u32),
}
impl ColorOutputValue {
pub fn is_valid(&self) -> bool {
match *self {
Self::Xy(color) => command::xy_in_range(color),
Self::Kelvin(kelvin) => command::KELVIN.contains(&kelvin),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ColorOutputSnapshot {
pub present_value: ColorOutputValue,
}
pub trait ColorOutputPersistence: Send + Sync {
fn load(&self, object: ObjectIdentifier) -> Result<Option<ColorOutputSnapshot>, Error>;
fn save(&self, object: ObjectIdentifier, snapshot: &ColorOutputSnapshot) -> Result<(), Error>;
}
#[cfg(feature = "std")]
const MAGIC: &[u8; 8] = b"RBNCLR01";
#[cfg(feature = "std")]
const MAX_FILE_BYTES: u64 = 64;
#[cfg(feature = "std")]
const KIND_XY: u8 = 1;
#[cfg(feature = "std")]
const KIND_KELVIN: u8 = 2;
#[derive(Clone, Debug)]
#[cfg(feature = "std")]
pub struct FileColorOutputPersistence {
file: ObjectFile,
}
#[cfg(feature = "std")]
impl FileColorOutputPersistence {
pub fn new(path: impl AsRef<Path>) -> Result<Self, Error> {
Ok(Self {
file: ObjectFile::new(path.as_ref(), MAGIC, MAX_FILE_BYTES, "Color output")?,
})
}
pub fn path(&self) -> &Path {
self.file.path()
}
}
#[cfg(feature = "std")]
impl ColorOutputPersistence for FileColorOutputPersistence {
fn load(&self, object: ObjectIdentifier) -> Result<Option<ColorOutputSnapshot>, Error> {
let Some(body) = self.file.load(object)? else {
return Ok(None);
};
let present_value = match body.as_slice() {
[KIND_XY, x0, x1, x2, x3, y0, y1, y2, y3] => ColorOutputValue::Xy(BACnetXyColor::new(
f32::from_be_bytes([*x0, *x1, *x2, *x3]),
f32::from_be_bytes([*y0, *y1, *y2, *y3]),
)),
[KIND_KELVIN, k0, k1, k2, k3] => {
ColorOutputValue::Kelvin(u32::from_be_bytes([*k0, *k1, *k2, *k3]))
}
_ => return Err(self.file.corrupt("holds no valid value")),
};
if !present_value.is_valid() {
return Err(self.file.corrupt("holds a value out of range"));
}
Ok(Some(ColorOutputSnapshot { present_value }))
}
fn save(&self, object: ObjectIdentifier, snapshot: &ColorOutputSnapshot) -> Result<(), Error> {
let mut body = Vec::with_capacity(9);
match snapshot.present_value {
ColorOutputValue::Xy(color) => {
body.push(KIND_XY);
body.extend_from_slice(&color.x.to_be_bytes());
body.extend_from_slice(&color.y.to_be_bytes());
}
ColorOutputValue::Kelvin(kelvin) => {
body.push(KIND_KELVIN);
body.extend_from_slice(&kelvin.to_be_bytes());
}
}
self.file.save(object, &body)
}
}