use std::borrow::Cow;
use std::sync::Arc;
use std::time::Duration;
use bacnet_types::constructed::{BACnetColorCommand, BACnetXyColor};
use bacnet_types::enums::{ColorOperation, ColorTransition, ObjectType, PropertyIdentifier};
use bacnet_types::error::Error;
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
use super::engine::Engine;
use super::{
command, metadata, milliseconds, noncommandable_audit_policy, written_transition,
written_unsigned, written_xy, xy_value,
};
use crate::audit::{AuditPolicyAuthority, ObjectAuditPolicy};
use crate::command_source::{CommandOrigin, SingleValueSource};
use crate::common::{self, read_identity_properties};
use crate::object_profile::{ObjectProfile, ProfileState, TagsPersistence};
use crate::traits::{BACnetObject, DeadlineWaker, MonotonicClock};
use crate::transition::Transition;
const D65: BACnetXyColor = BACnetXyColor::new(0.3127, 0.3290);
const XY_SAMPLE_STEP: f64 = 0.001;
#[derive(Clone)]
pub struct ColorObject {
oid: ObjectIdentifier,
name: String,
description: String,
present_value: BACnetXyColor,
color_command: BACnetColorCommand,
default_color: BACnetXyColor,
default_fade_time: u32,
transition: ColorTransition,
audit_policy: ObjectAuditPolicy,
value_source: SingleValueSource,
profile: ProfileState,
engine: Engine<BACnetXyColor>,
}
impl ColorObject {
pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
let oid = ObjectIdentifier::new(ObjectType::COLOR, instance)?;
Ok(Self {
oid,
name: name.into(),
description: String::new(),
present_value: D65,
color_command: BACnetColorCommand::new(ColorOperation::NONE),
default_color: D65,
default_fade_time: *command::FADE_TIME_MS.start(),
transition: ColorTransition::NONE,
audit_policy: ObjectAuditPolicy::default(),
value_source: SingleValueSource::default(),
profile: ProfileState::default(),
engine: Engine::new(XY_SAMPLE_STEP),
})
}
pub fn set_value_source_tracking(&mut self, enabled: bool) {
self.value_source.set_enabled(enabled);
}
pub(super) fn value_source(&self) -> &SingleValueSource {
&self.value_source
}
pub fn with_tags_persistence(
instance: u32,
name: impl Into<String>,
persistence: Arc<dyn TagsPersistence>,
) -> Result<Self, Error> {
let mut object = Self::new(instance, name)?;
object.profile = ProfileState::persistent(object.oid, persistence)?;
Ok(object)
}
pub fn wait_for_tag_saves(&self) {
self.profile.wait_for_saves();
}
pub fn set_profile(&mut self, profile: ObjectProfile) -> Result<(), Error> {
self.profile.provision(profile)
}
pub(super) fn profile(&self) -> &ObjectProfile {
self.profile.profile()
}
pub fn set_audit_policy(&mut self, policy: ObjectAuditPolicy) {
self.audit_policy = noncommandable_audit_policy(policy);
}
pub(super) fn audit_policy(&self) -> &ObjectAuditPolicy {
&self.audit_policy
}
pub fn set_present_value(&mut self, color: BACnetXyColor) -> Result<(), Error> {
if !command::xy_in_range(color) {
return Err(common::value_out_of_range_error());
}
self.write_present_value(color);
self.value_source.forget();
Ok(())
}
pub fn color_command(&self) -> BACnetColorCommand {
self.color_command
}
pub fn set_color_command(&mut self, command: BACnetColorCommand) -> Result<(), Error> {
command::check_color(&command)?;
if self.take_color_command(command) {
self.value_source.forget();
}
Ok(())
}
fn take_color_command(&mut self, command: BACnetColorCommand) -> bool {
self.color_command = command;
let now = self.engine.now();
match command.operation {
ColorOperation::FADE_TO_COLOR => {
let Some(target) = command.target_color else {
return false;
};
let tracking = self.engine.tracking(self.present_value, now);
let fade_time = command.fade_time.unwrap_or(self.default_fade_time);
self.present_value = target;
self.engine.start(Transition::fade(
tracking,
target,
now,
milliseconds(fade_time),
));
true
}
ColorOperation::STOP => match self.engine.halt(now) {
Some(reached) => {
self.present_value = reached;
true
}
None => false,
},
_ => false,
}
}
fn write_output(
&mut self,
property: PropertyIdentifier,
value: PropertyValue,
) -> Result<bool, Error> {
if property == PropertyIdentifier::PRESENT_VALUE {
self.write_present_value(written_xy(value)?);
return Ok(true);
}
let command = command::decode_write(value, command::check_color)?;
Ok(self.take_color_command(command))
}
fn write_present_value(&mut self, color: BACnetXyColor) {
let now = self.engine.now();
let tracking = self.engine.tracking(self.present_value, now);
self.present_value = color;
let fade = (self.transition == ColorTransition::FADE)
.then(|| Transition::fade(tracking, color, now, milliseconds(self.default_fade_time)));
self.engine.start(fade.flatten());
}
}
impl BACnetObject for ColorObject {
fn object_identifier(&self) -> ObjectIdentifier {
self.oid
}
fn object_name(&self) -> &str {
&self.name
}
fn read_property(
&self,
property: PropertyIdentifier,
array_index: Option<u32>,
) -> Result<PropertyValue, Error> {
if let Some(result) = read_identity_properties!(self, property, array_index) {
return result;
}
if let Some(result) = self.audit_policy.read(property, array_index) {
return result;
}
if let Some(result) = self.value_source.read(property, array_index) {
return result;
}
if let Some(result) = self.profile.read(property, array_index) {
return result;
}
match property {
p if p == PropertyIdentifier::OBJECT_TYPE => {
Ok(PropertyValue::Enumerated(ObjectType::COLOR.to_raw()))
}
p if p == PropertyIdentifier::PRESENT_VALUE => Ok(xy_value(self.present_value)),
p if p == PropertyIdentifier::TRACKING_VALUE => Ok(xy_value(
self.engine.tracking(self.present_value, self.engine.now()),
)),
p if p == PropertyIdentifier::COLOR_COMMAND => Ok(command::encode(&self.color_command)),
p if p == PropertyIdentifier::IN_PROGRESS => Ok(PropertyValue::Enumerated(
self.engine.in_progress(self.engine.now()).to_raw(),
)),
p if p == PropertyIdentifier::DEFAULT_COLOR => Ok(xy_value(self.default_color)),
p if p == PropertyIdentifier::DEFAULT_FADE_TIME => {
Ok(PropertyValue::Unsigned(u64::from(self.default_fade_time)))
}
p if p == PropertyIdentifier::TRANSITION => {
Ok(PropertyValue::Enumerated(self.transition.to_raw()))
}
_ => Err(common::unknown_property_error()),
}
}
fn write_property(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: PropertyValue,
priority: Option<u8>,
) -> Result<(), Error> {
if let Some(result) = common::write_description(&mut self.description, property, &value) {
return result;
}
if let Some(result) = self
.audit_policy
.write(property, array_index, &value, priority)
{
return result;
}
if let Some(result) = self.profile.write(property, array_index, &value) {
return result;
}
match property {
p if p == PropertyIdentifier::PRESENT_VALUE
|| p == PropertyIdentifier::COLOR_COMMAND =>
{
self.value_source.unsourced()?;
self.write_output(property, value)?;
}
p if p == PropertyIdentifier::DEFAULT_COLOR => {
self.default_color = written_xy(value)?;
}
p if p == PropertyIdentifier::DEFAULT_FADE_TIME => {
self.default_fade_time = written_unsigned(value, command::FADE_TIME_MS)?;
}
p if p == PropertyIdentifier::TRANSITION => {
self.transition = written_transition(value, ColorTransition::FADE)?;
}
_ => {
return Err(common::unhandled_write_error(
self.property_metadata().as_ref(),
property,
array_index,
))
}
}
Ok(())
}
fn write_property_from(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: PropertyValue,
priority: Option<u8>,
origin: &CommandOrigin,
) -> Result<(), Error> {
match property {
PropertyIdentifier::VALUE_SOURCE => {
self.value_source.correct(array_index, value, origin)
}
PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::COLOR_COMMAND => {
self.value_source.admit(origin)?;
if self.write_output(property, value)? {
self.value_source.record(origin);
}
Ok(())
}
_ => self.write_property(property, array_index, value, priority),
}
}
fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
metadata::for_color_object(self)
}
fn durable_writes_internal(&mut self) -> Option<&mut dyn crate::durable::DurableWrites> {
self.profile.capability()
}
fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
}
fn supports_cov(&self) -> bool {
true
}
fn audit_object_policy_internal(&self) -> ObjectAuditPolicy {
self.audit_policy
}
fn audit_policy_authority_internal(&mut self) -> Option<AuditPolicyAuthority<'_>> {
Some(AuditPolicyAuthority::new(&mut self.audit_policy))
}
fn advance_time_internal(&mut self, elapsed: Duration) -> bool {
self.engine.advance_by(elapsed)
}
fn bind_monotonic_clock_internal(&mut self, clock: Option<Arc<MonotonicClock>>) {
self.engine.bind_clock(clock);
}
fn bind_deadline_waker_internal(&mut self, waker: Option<Arc<DeadlineWaker>>) {
self.engine.bind_waker(waker);
}
fn advance_monotonic_time_internal(&mut self, now: Duration) -> bool {
self.profile.expire(now);
self.engine.advance_to(now)
}
fn next_monotonic_deadline_internal(&self) -> Option<Duration> {
self.engine.deadline()
}
fn cov_snapshot_internal(&self) -> Option<Box<dyn BACnetObject>> {
Some(Box::new(Self {
engine: self.engine.frozen(),
..self.clone()
}))
}
}