1use std::borrow::Cow;
5use std::sync::Arc;
6use std::time::Duration;
7
8use bacnet_types::constructed::BACnetColorCommand;
9use bacnet_types::enums::{ColorOperation, ColorTransition, ObjectType, PropertyIdentifier};
10use bacnet_types::error::Error;
11use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
12
13use super::engine::Engine;
14use super::persistence::{ColorOutputPersistence, ColorOutputValue};
15use super::saving::{impl_color_durable, ColorOutputSaveCounters, OutputStorage};
16use super::{
17 command, metadata, milliseconds, noncommandable_audit_policy, written_transition,
18 written_unsigned,
19};
20use crate::audit::{AuditPolicyAuthority, ObjectAuditPolicy};
21use crate::command_source::{CommandOrigin, SingleValueSource};
22use crate::common::{self, read_identity_properties};
23use crate::object_profile::{ObjectProfile, ProfileState, TagsPersistence};
24use crate::traits::{BACnetObject, DeadlineWaker, MonotonicClock};
25use crate::transition::Transition;
26
27const KELVIN_SAMPLE_STEP: f64 = 10.0;
32
33#[derive(Clone)]
62pub struct ColorTemperatureObject {
63 oid: ObjectIdentifier,
64 name: String,
65 description: String,
66 present_value: u32,
69 color_command: BACnetColorCommand,
71 default_color_temperature: u32,
73 default_fade_time: u32,
75 default_ramp_rate: u32,
77 default_step_increment: u32,
79 transition: ColorTransition,
81 min_pres_value: u32,
83 max_pres_value: u32,
84 audit_policy: ObjectAuditPolicy,
86 value_source: SingleValueSource,
88 profile: ProfileState,
90 output: OutputStorage,
92 restored: bool,
94 to_restore: Option<u32>,
96 engine: Engine<u32>,
97}
98
99impl ColorTemperatureObject {
100 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
108 let oid = ObjectIdentifier::new(ObjectType::COLOR_TEMPERATURE, instance)?;
109 Ok(Self {
110 oid,
111 name: name.into(),
112 description: String::new(),
113 present_value: 4000,
114 color_command: BACnetColorCommand::new(ColorOperation::NONE),
115 default_color_temperature: 4000,
116 default_fade_time: *command::FADE_TIME_MS.start(),
117 default_ramp_rate: 100,
118 default_step_increment: 50,
119 transition: ColorTransition::NONE,
120 min_pres_value: *command::KELVIN.start(),
121 max_pres_value: *command::KELVIN.end(),
122 audit_policy: ObjectAuditPolicy::default(),
123 value_source: SingleValueSource::default(),
124 profile: ProfileState::default(),
125 output: OutputStorage::default(),
126 restored: false,
127 to_restore: None,
128 engine: Engine::new(KELVIN_SAMPLE_STEP),
129 })
130 }
131
132 pub fn with_output_persistence(
145 mut self,
146 persistence: Arc<dyn ColorOutputPersistence>,
147 ) -> Result<Self, Error> {
148 let current = ColorOutputValue::Kelvin(self.present_value);
149 match self.output.open(self.oid, current, persistence)? {
150 Some(ColorOutputValue::Kelvin(kelvin)) if command::KELVIN.contains(&kelvin) => {
151 self.present_value = self.clamped(kelvin);
152 self.restored = true;
153 self.to_restore = Some(kelvin);
154 }
155 Some(_) => {
156 return Err(Error::Encoding(
157 "saved Color Temperature output is not a temperature the object can hold"
158 .into(),
159 ))
160 }
161 None => {}
162 }
163 Ok(self)
164 }
165
166 pub fn output_restored(&self) -> bool {
169 self.restored
170 }
171
172 pub fn output_save_counters(&self) -> Option<ColorOutputSaveCounters> {
175 self.output.counters()
176 }
177
178 pub fn wait_for_output_saves(&self) {
181 self.output.wait_for_saves();
182 }
183
184 pub fn default_color_temperature(&self) -> u32 {
188 self.default_color_temperature
189 }
190
191 pub fn set_default_color_temperature(&mut self, kelvin: u32) -> Result<(), Error> {
195 self.default_color_temperature = match kelvin {
196 0 => 0,
197 kelvin => self.written_kelvin(u64::from(kelvin))?,
198 };
199 Ok(())
200 }
201
202 pub fn start_output(&mut self) {
213 let restored = self.to_restore.take();
214 let target = match self.default_color_temperature {
215 0 => restored,
216 default => Some(default),
217 };
218 if let Some(kelvin) = target.map(|kelvin| self.clamped(kelvin)) {
221 if kelvin != self.present_value {
222 self.value_source.forget();
223 }
224 self.present_value = kelvin;
225 self.engine.start(None);
226 self.output_changed();
227 }
228 }
229
230 fn output_changed(&mut self) {
232 self.output
233 .note(ColorOutputValue::Kelvin(self.present_value));
234 }
235
236 pub fn set_value_source_tracking(&mut self, enabled: bool) {
241 self.value_source.set_enabled(enabled);
242 }
243
244 pub(super) fn value_source(&self) -> &SingleValueSource {
245 &self.value_source
246 }
247
248 pub fn with_tags_persistence(
252 instance: u32,
253 name: impl Into<String>,
254 persistence: Arc<dyn TagsPersistence>,
255 ) -> Result<Self, Error> {
256 let mut object = Self::new(instance, name)?;
257 object.profile = ProfileState::persistent(object.oid, persistence)?;
258 Ok(object)
259 }
260
261 pub fn wait_for_tag_saves(&self) {
264 self.profile.wait_for_saves();
265 }
266
267 pub fn set_profile(&mut self, profile: ObjectProfile) -> Result<(), Error> {
272 self.profile.provision(profile)
273 }
274
275 pub(super) fn profile(&self) -> &ObjectProfile {
276 self.profile.profile()
277 }
278
279 pub fn set_audit_policy(&mut self, policy: ObjectAuditPolicy) {
284 self.audit_policy = noncommandable_audit_policy(policy);
285 }
286
287 pub(super) fn audit_policy(&self) -> &ObjectAuditPolicy {
288 &self.audit_policy
289 }
290
291 pub fn set_present_value(&mut self, kelvin: u32) -> Result<(), Error> {
298 let kelvin = self.written_kelvin(u64::from(kelvin))?;
299 self.write_present_value(kelvin);
300 self.value_source.forget();
301 Ok(())
302 }
303
304 pub fn set_min_max(&mut self, min: u32, max: u32) -> Result<(), Error> {
313 if !command::KELVIN.contains(&min) || !command::KELVIN.contains(&max) || min > max {
314 return Err(common::value_out_of_range_error());
315 }
316 self.min_pres_value = min;
317 self.max_pres_value = max;
318 let clamped = self.present_value.clamp(min, max);
319 if clamped != self.present_value {
320 self.engine.halt(self.engine.now());
321 self.present_value = clamped;
322 self.value_source.forget();
323 self.output_changed();
324 }
325 if self.default_color_temperature != 0 {
326 self.default_color_temperature = self.default_color_temperature.clamp(min, max);
327 }
328 Ok(())
329 }
330
331 pub fn color_command(&self) -> BACnetColorCommand {
334 self.color_command
335 }
336
337 pub fn set_color_command(&mut self, command: BACnetColorCommand) -> Result<(), Error> {
350 command::check_color_temperature(&command)?;
351 if self.take_color_command(command) {
352 self.value_source.forget();
353 }
354 Ok(())
355 }
356
357 fn written_kelvin(&self, kelvin: u64) -> Result<u32, Error> {
360 u32::try_from(kelvin)
361 .ok()
362 .filter(|kelvin| command::KELVIN.contains(kelvin))
363 .map(|kelvin| self.clamped(kelvin))
364 .ok_or_else(common::value_out_of_range_error)
365 }
366
367 fn clamped(&self, kelvin: u32) -> u32 {
368 kelvin.clamp(self.min_pres_value, self.max_pres_value)
369 }
370
371 fn take_color_command(&mut self, command: BACnetColorCommand) -> bool {
376 let set = self.carry_out(command);
377 if set {
378 self.output_changed();
379 }
380 set
381 }
382
383 fn carry_out(&mut self, command: BACnetColorCommand) -> bool {
385 self.color_command = command;
386 let now = self.engine.now();
387 let tracking = self.engine.tracking(self.present_value, now);
388 let target = command
389 .target_color_temperature
390 .map(|kelvin| self.clamped(kelvin));
391 match (command.operation, target) {
392 (ColorOperation::FADE_TO_CCT, Some(target)) => {
393 let fade_time = command.fade_time.unwrap_or(self.default_fade_time);
394 self.present_value = target;
395 self.engine.start(Transition::fade(
396 tracking,
397 target,
398 now,
399 milliseconds(fade_time),
400 ));
401 true
402 }
403 (ColorOperation::RAMP_TO_CCT, Some(target)) => {
404 let rate = command.ramp_rate.unwrap_or(self.default_ramp_rate);
405 self.present_value = target;
406 self.engine
407 .start(Transition::ramp(tracking, target, now, f64::from(rate)));
408 true
409 }
410 (operation @ (ColorOperation::STEP_UP_CCT | ColorOperation::STEP_DOWN_CCT), _) => {
411 let increment = command
412 .step_increment
413 .unwrap_or(self.default_step_increment);
414 let stepped = if operation == ColorOperation::STEP_UP_CCT {
415 tracking.saturating_add(increment)
416 } else {
417 tracking.saturating_sub(increment)
418 };
419 self.present_value = self.clamped(stepped);
420 self.engine.start(None);
421 true
422 }
423 (ColorOperation::STOP, _) => match self.engine.halt(now) {
424 Some(reached) => {
425 self.present_value = reached;
426 true
427 }
428 None => false,
429 },
430 _ => false,
431 }
432 }
433
434 fn write_output(
437 &mut self,
438 property: PropertyIdentifier,
439 value: PropertyValue,
440 ) -> Result<bool, Error> {
441 if property == PropertyIdentifier::PRESENT_VALUE {
442 let PropertyValue::Unsigned(kelvin) = value else {
443 return Err(common::invalid_data_type_error());
444 };
445 let kelvin = self.written_kelvin(kelvin)?;
446 self.write_present_value(kelvin);
447 return Ok(true);
448 }
449 let command = command::decode_write(value, command::check_color_temperature)?;
450 Ok(self.take_color_command(command))
451 }
452
453 fn write_present_value(&mut self, kelvin: u32) {
457 let now = self.engine.now();
458 let tracking = self.engine.tracking(self.present_value, now);
459 self.present_value = kelvin;
460 let transition = match self.transition {
461 ColorTransition::FADE => {
462 Transition::fade(tracking, kelvin, now, milliseconds(self.default_fade_time))
463 }
464 ColorTransition::RAMP => {
465 Transition::ramp(tracking, kelvin, now, f64::from(self.default_ramp_rate))
466 }
467 _ => None,
468 };
469 self.engine.start(transition);
470 self.output_changed();
471 }
472}
473
474impl BACnetObject for ColorTemperatureObject {
475 fn object_identifier(&self) -> ObjectIdentifier {
476 self.oid
477 }
478
479 fn object_name(&self) -> &str {
480 &self.name
481 }
482
483 fn read_property(
484 &self,
485 property: PropertyIdentifier,
486 array_index: Option<u32>,
487 ) -> Result<PropertyValue, Error> {
488 if let Some(result) = read_identity_properties!(self, property, array_index) {
489 return result;
490 }
491 if let Some(result) = self.audit_policy.read(property, array_index) {
492 return result;
493 }
494 if let Some(result) = self.value_source.read(property, array_index) {
495 return result;
496 }
497 if let Some(result) = self.profile.read(property, array_index) {
498 return result;
499 }
500 let unsigned = |value: u32| Ok(PropertyValue::Unsigned(u64::from(value)));
501 match property {
502 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
503 ObjectType::COLOR_TEMPERATURE.to_raw(),
504 )),
505 p if p == PropertyIdentifier::PRESENT_VALUE => unsigned(self.present_value),
506 p if p == PropertyIdentifier::TRACKING_VALUE => {
507 unsigned(self.engine.tracking(self.present_value, self.engine.now()))
508 }
509 p if p == PropertyIdentifier::COLOR_COMMAND => Ok(command::encode(&self.color_command)),
510 p if p == PropertyIdentifier::IN_PROGRESS => Ok(PropertyValue::Enumerated(
511 self.engine.in_progress(self.engine.now()).to_raw(),
512 )),
513 p if p == PropertyIdentifier::DEFAULT_COLOR_TEMPERATURE => {
514 unsigned(self.default_color_temperature)
515 }
516 p if p == PropertyIdentifier::DEFAULT_FADE_TIME => unsigned(self.default_fade_time),
517 p if p == PropertyIdentifier::DEFAULT_RAMP_RATE => unsigned(self.default_ramp_rate),
518 p if p == PropertyIdentifier::DEFAULT_STEP_INCREMENT => {
519 unsigned(self.default_step_increment)
520 }
521 p if p == PropertyIdentifier::MIN_PRES_VALUE => unsigned(self.min_pres_value),
522 p if p == PropertyIdentifier::MAX_PRES_VALUE => unsigned(self.max_pres_value),
523 p if p == PropertyIdentifier::TRANSITION => {
524 Ok(PropertyValue::Enumerated(self.transition.to_raw()))
525 }
526 _ => Err(common::unknown_property_error()),
527 }
528 }
529
530 fn write_property(
531 &mut self,
532 property: PropertyIdentifier,
533 array_index: Option<u32>,
534 value: PropertyValue,
535 priority: Option<u8>,
536 ) -> Result<(), Error> {
537 if let Some(result) = common::write_description(&mut self.description, property, &value) {
538 return result;
539 }
540 if let Some(result) = self
541 .audit_policy
542 .write(property, array_index, &value, priority)
543 {
544 return result;
545 }
546 if let Some(result) = self.profile.write(property, array_index, &value) {
547 return result;
548 }
549 match property {
550 p if p == PropertyIdentifier::PRESENT_VALUE
551 || p == PropertyIdentifier::COLOR_COMMAND =>
552 {
553 self.value_source.unsourced()?;
555 self.write_output(property, value)?;
556 }
557 p if p == PropertyIdentifier::DEFAULT_COLOR_TEMPERATURE => {
561 self.default_color_temperature = match value {
562 PropertyValue::Unsigned(0) => 0,
563 PropertyValue::Unsigned(kelvin) => self.written_kelvin(kelvin)?,
564 _ => return Err(common::invalid_data_type_error()),
565 };
566 }
567 p if p == PropertyIdentifier::DEFAULT_FADE_TIME => {
568 self.default_fade_time = written_unsigned(value, command::FADE_TIME_MS)?;
569 }
570 p if p == PropertyIdentifier::DEFAULT_RAMP_RATE => {
571 self.default_ramp_rate = written_unsigned(value, command::KELVIN_STEP)?;
572 }
573 p if p == PropertyIdentifier::DEFAULT_STEP_INCREMENT => {
574 self.default_step_increment = written_unsigned(value, command::KELVIN_STEP)?;
575 }
576 p if p == PropertyIdentifier::TRANSITION => {
577 self.transition = written_transition(value, ColorTransition::RAMP)?;
578 }
579 _ => {
580 return Err(common::unhandled_write_error(
581 self.property_metadata().as_ref(),
582 property,
583 array_index,
584 ))
585 }
586 }
587 Ok(())
588 }
589
590 fn write_property_from(
591 &mut self,
592 property: PropertyIdentifier,
593 array_index: Option<u32>,
594 value: PropertyValue,
595 priority: Option<u8>,
596 origin: &CommandOrigin,
597 ) -> Result<(), Error> {
598 match property {
599 PropertyIdentifier::VALUE_SOURCE => {
600 self.value_source.correct(array_index, value, origin)
601 }
602 PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::COLOR_COMMAND => {
603 self.value_source.admit(origin)?;
604 if self.write_output(property, value)? {
605 self.value_source.record(origin);
606 }
607 Ok(())
608 }
609 _ => self.write_property(property, array_index, value, priority),
610 }
611 }
612
613 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
614 metadata::for_color_temperature_object(self)
615 }
616
617 fn durable_writes_internal(&mut self) -> Option<&mut dyn crate::durable::DurableWrites> {
618 if self.output.is_some() {
619 return Some(self);
620 }
621 self.profile.capability()
622 }
623
624 fn start_output_internal(&mut self) {
625 self.start_output();
626 }
627
628 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
629 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
630 }
631
632 fn supports_cov(&self) -> bool {
633 true
634 }
635
636 fn audit_object_policy_internal(&self) -> ObjectAuditPolicy {
637 self.audit_policy
638 }
639
640 fn audit_policy_authority_internal(&mut self) -> Option<AuditPolicyAuthority<'_>> {
641 Some(AuditPolicyAuthority::new(&mut self.audit_policy))
642 }
643
644 fn advance_time_internal(&mut self, elapsed: Duration) -> bool {
645 self.engine.advance_by(elapsed)
646 }
647
648 fn bind_monotonic_clock_internal(&mut self, clock: Option<Arc<MonotonicClock>>) {
649 self.engine.bind_clock(clock);
650 }
651
652 fn bind_deadline_waker_internal(&mut self, waker: Option<Arc<DeadlineWaker>>) {
653 self.engine.bind_waker(waker);
654 }
655
656 fn advance_monotonic_time_internal(&mut self, now: Duration) -> bool {
657 self.profile.expire(now);
658 self.engine.advance_to(now)
659 }
660
661 fn next_monotonic_deadline_internal(&self) -> Option<Duration> {
662 self.engine.deadline()
663 }
664
665 fn set_tracking_cov_increment_internal(&mut self, finest: Option<f64>) {
666 self.engine.set_finest_increment(finest);
667 }
668
669 fn cov_snapshot_internal(&self) -> Option<Box<dyn BACnetObject>> {
671 Some(Box::new(Self {
672 engine: self.engine.frozen(),
673 ..self.clone()
674 }))
675 }
676}
677
678impl_color_durable!(ColorTemperatureObject);