use std::time::Duration;
use bacnet_types::constructed::BACnetLightingCommand;
use bacnet_types::enums::{LightingInProgress, LightingOperation as Op};
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use super::{normalized_level, LightingOutputObject};
use crate::common;
use crate::transition::{Progress, Run, Transition, TransitionKind};
const DEFAULT_SAMPLE_STEP: f64 = 1.0;
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) enum Operation {
Moving { priority: u8, run: Run<f32> },
Egress {
priority: u8,
then: AfterEgress,
deadline: Duration,
},
}
impl Operation {
fn priority(&self) -> u8 {
match *self {
Self::Moving { priority, .. } | Self::Egress { priority, .. } => priority,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum AfterEgress {
Relinquish,
Off,
}
impl AfterEgress {
fn slot(self) -> Option<f32> {
match self {
Self::Relinquish => None,
Self::Off => Some(0.0),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) enum PresentValueWrite {
Level(Option<f32>),
Warn(Op),
}
impl PresentValueWrite {
pub(super) fn decode(value: PropertyValue) -> Result<Self, Error> {
match value {
PropertyValue::Null => Ok(Self::Level(None)),
PropertyValue::Real(level) => Ok(match warn_operation(level) {
Some(operation) => Self::Warn(operation),
None => Self::Level(Some(normalized_level(level)?)),
}),
_ => Err(common::invalid_data_type_error()),
}
}
}
fn warn_operation(value: f32) -> Option<Op> {
if value == -1.0 {
Some(Op::WARN)
} else if value == -2.0 {
Some(Op::WARN_RELINQUISH)
} else if value == -3.0 {
Some(Op::WARN_OFF)
} else {
None
}
}
fn engine_level(value: f32) -> f32 {
normalized_level(value.clamp(0.0, 100.0)).unwrap_or(0.0)
}
fn step_level(operation: Op, tracking: f32, increment: f32, floor: f32) -> Option<f32> {
let up = (tracking + increment).min(100.0);
let down = (tracking - increment).max(1.0);
match operation {
Op::STEP_UP => (tracking != 0.0).then_some(up),
Op::STEP_DOWN => (tracking != 0.0).then_some(down),
Op::STEP_ON => Some(if tracking == 0.0 { 1.0 } else { up }),
Op::STEP_OFF if tracking == floor => Some(0.0),
Op::STEP_OFF => (tracking != 0.0).then_some(down),
_ => None,
}
}
impl LightingOutputObject {
pub(super) fn now(&self) -> Duration {
self.monotonic_clock
.as_ref()
.map_or(self.logical_now, |clock| clock())
}
pub(super) fn tracking_value_at(&self, now: Duration) -> f32 {
self.trimmed(self.untrimmed_at(now), now)
}
fn untrimmed_at(&self, now: Duration) -> f32 {
match self.operation {
Some(Operation::Moving { run, .. }) if !run.transition().is_finished(now) => {
run.transition().value_at(now)
}
_ => self.present_value,
}
}
pub(super) fn in_progress_at(&self, now: Duration) -> LightingInProgress {
if self.trim_active(self.untrimmed_at(now), now) {
return LightingInProgress::TRIM_ACTIVE;
}
match self.operation {
Some(Operation::Moving { run, .. }) if !run.transition().is_finished(now) => {
match run.transition().kind() {
TransitionKind::Fade => LightingInProgress::FADE_ACTIVE,
TransitionKind::Ramp => LightingInProgress::RAMP_ACTIVE,
}
}
_ => LightingInProgress::IDLE,
}
}
pub(super) fn egress_active(&self) -> bool {
matches!(self.operation, Some(Operation::Egress { .. }))
}
pub(super) fn execute(&mut self, command: &BACnetLightingCommand, now: Duration) {
let priority = command
.priority
.unwrap_or(self.lighting_command_default_priority as u8);
self.advance_to(now);
self.operate(command, priority, now);
}
pub(super) fn write_present_value(
&mut self,
priority: u8,
write: PresentValueWrite,
now: Duration,
) {
self.advance_to(now);
match write {
PresentValueWrite::Level(level) => {
self.halt_for(priority);
self.set_slot(priority, level);
}
PresentValueWrite::Warn(operation) => {
self.operate(&BACnetLightingCommand::new(operation), priority, now);
}
}
}
pub(super) fn advance_to(&mut self, now: Duration) -> bool {
let step = self.sample_step();
let trims = self.trims.advance(now, step);
let operation = match self.operation {
Some(Operation::Egress {
priority,
then,
deadline,
}) if now >= deadline => {
self.operation = None;
self.set_slot(priority, then.slot());
true
}
Some(Operation::Moving { priority, mut run }) => {
let progress = run.advance(now, step);
self.operation =
(progress != Progress::Finished).then_some(Operation::Moving { priority, run });
progress != Progress::Pending
}
_ => false,
};
trims || operation
}
pub(super) fn next_deadline(&self) -> Option<Duration> {
let operation = self.operation.map(|operation| match operation {
Operation::Moving { run, .. } => run.deadline(),
Operation::Egress { deadline, .. } => deadline,
});
match (operation, self.trims.deadline()) {
(Some(a), Some(b)) => Some(a.min(b)),
(a, b) => a.or(b),
}
}
fn operate(&mut self, command: &BACnetLightingCommand, priority: u8, now: Duration) {
let tracking = self.tracking_value_at(now);
let operation = command.operation;
match operation {
Op::FADE_TO | Op::RAMP_TO => {
let Some(target) = command.target_level else {
return;
};
self.halt_for(priority);
let level = engine_level(target);
self.set_slot(priority, Some(level));
if self.highest_priority() != Some(priority) {
return;
}
let (from, to) = self.trimmed_line(tracking, level, now);
let transition = if operation == Op::FADE_TO {
let fade_time = command.fade_time.unwrap_or(self.default_fade_time);
let fade_time = Duration::from_millis(u64::from(fade_time));
Transition::fade(from, to, now, fade_time)
} else {
let rate = command.ramp_rate.unwrap_or(self.default_ramp_rate);
Transition::ramp(from, to, now, f64::from(rate))
};
if let Some(transition) = transition {
let run = Run::start(transition, self.sample_step());
self.operation = Some(Operation::Moving { priority, run });
self.wake();
}
}
Op::STEP_UP | Op::STEP_DOWN | Op::STEP_ON | Op::STEP_OFF => {
let increment = command
.step_increment
.unwrap_or(self.default_step_increment);
let floor = self.step_floor(now);
if let Some(level) = step_level(operation, tracking, increment, floor) {
self.halt_for(priority);
self.set_slot(priority, Some(engine_level(level)));
}
}
Op::WARN => {
self.halt_for(priority);
if self.blink_warn_enable
&& self.highest_priority() == Some(priority)
&& self.slot(priority) != Some(0.0)
{
self.request_blink();
}
}
Op::WARN_RELINQUISH => {
self.halt_for(priority);
let eligible = self.blink_warn_enable
&& self.highest_priority() == Some(priority)
&& self.slot(priority) != Some(0.0)
&& self.next_level_below(priority) <= 0.0;
self.warn_then(priority, AfterEgress::Relinquish, eligible, now);
}
Op::WARN_OFF => {
self.halt_for(priority);
let eligible = self.blink_warn_enable
&& self.highest_priority() == Some(priority)
&& self.present_value != 0.0;
self.warn_then(priority, AfterEgress::Off, eligible, now);
}
Op::STOP => self.stop(priority, tracking),
_ => {}
}
}
fn warn_then(&mut self, priority: u8, then: AfterEgress, eligible: bool, now: Duration) {
if !eligible {
self.set_slot(priority, then.slot());
return;
}
self.request_blink();
let egress = Duration::from_secs(u64::from(self.egress_time));
if egress.is_zero() {
self.set_slot(priority, then.slot());
} else {
self.operation = Some(Operation::Egress {
priority,
then,
deadline: now.saturating_add(egress),
});
self.wake();
}
}
fn stop(&mut self, priority: u8, tracking: f32) {
match self.operation {
Some(Operation::Moving { priority: at, .. }) if at == priority => {
self.operation = None;
self.set_slot(priority, Some(engine_level(tracking)));
}
Some(Operation::Egress { priority: at, .. }) if at == priority => {
self.operation = None;
}
_ => {}
}
}
fn halt_for(&mut self, priority: u8) {
let Some(operation) = self.operation else {
return;
};
if priority > operation.priority() {
return;
}
self.operation = None;
if let Operation::Egress { priority, then, .. } = operation {
self.set_slot(priority, then.slot());
}
}
fn slot(&self, priority: u8) -> Option<f32> {
self.priority_array[usize::from(priority - 1)]
}
fn set_slot(&mut self, priority: u8, level: Option<f32>) {
self.priority_array[usize::from(priority - 1)] = level;
self.recalculate_present_value();
}
pub(super) fn highest_priority(&self) -> Option<u8> {
self.priority_array
.iter()
.position(Option::is_some)
.map(|index| index as u8 + 1)
}
fn next_level_below(&self, priority: u8) -> f32 {
self.priority_array[usize::from(priority)..]
.iter()
.flatten()
.next()
.copied()
.unwrap_or(self.relinquish_default)
}
fn request_blink(&mut self) {
self.blink_request_count = self.blink_request_count.saturating_add(1);
}
pub(super) fn sample_step(&self) -> f64 {
let own = if self.cov_increment > 0.0 {
f64::from(self.cov_increment)
} else {
DEFAULT_SAMPLE_STEP
};
self.finest_tracking_increment
.map_or(own, |finest| own.min(finest))
}
pub(super) fn wake(&self) {
if let Some(waker) = &self.deadline_waker {
waker();
}
}
}