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