1use crate::prelude::*;
5use alloc::borrow::Cow;
6use alloc::sync::Arc;
7use core::time::Duration;
8
9use bacnet_types::constructed::{BACnetColorCommand, BACnetXyColor};
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, written_xy, xy_value,
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 D65: BACnetXyColor = BACnetXyColor::new(0.3127, 0.3290);
30
31const XY_SAMPLE_STEP: f64 = 0.001;
37
38#[derive(Clone)]
61pub struct ColorObject {
62 oid: ObjectIdentifier,
63 name: String,
64 description: String,
65 present_value: BACnetXyColor,
67 color_command: BACnetColorCommand,
69 default_color: BACnetXyColor,
71 default_fade_time: u32,
73 transition: ColorTransition,
75 audit_policy: ObjectAuditPolicy,
77 value_source: SingleValueSource,
79 profile: ProfileState,
81 output: OutputStorage,
83 restored: bool,
85 to_restore: Option<BACnetXyColor>,
87 engine: Engine<BACnetXyColor>,
88}
89
90impl ColorObject {
91 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
97 let oid = ObjectIdentifier::new(ObjectType::COLOR, instance)?;
98 Ok(Self {
99 oid,
100 name: name.into(),
101 description: String::new(),
102 present_value: D65,
103 color_command: BACnetColorCommand::new(ColorOperation::NONE),
104 default_color: D65,
105 default_fade_time: *command::FADE_TIME_MS.start(),
106 transition: ColorTransition::NONE,
107 audit_policy: ObjectAuditPolicy::default(),
108 value_source: SingleValueSource::default(),
109 profile: ProfileState::default(),
110 output: OutputStorage::default(),
111 restored: false,
112 to_restore: None,
113 engine: Engine::new(XY_SAMPLE_STEP),
114 })
115 }
116
117 pub fn with_output_persistence(
131 mut self,
132 persistence: Arc<dyn ColorOutputPersistence>,
133 ) -> Result<Self, Error> {
134 let current = ColorOutputValue::Xy(self.present_value);
135 match self.output.open(self.oid, current, persistence)? {
136 Some(ColorOutputValue::Xy(color)) if command::xy_in_range(color) => {
137 self.present_value = color;
138 self.restored = true;
139 self.to_restore = Some(color);
140 }
141 Some(_) => {
142 return Err(Error::Encoding(
143 "saved Color output is not a colour the object can hold".into(),
144 ));
145 }
146 None => {}
147 }
148 Ok(self)
149 }
150
151 pub fn output_restored(&self) -> bool {
154 self.restored
155 }
156
157 pub fn output_save_counters(&self) -> Option<ColorOutputSaveCounters> {
160 self.output.counters()
161 }
162
163 pub fn wait_for_output_saves(&self) {
166 self.output.wait_for_saves();
167 }
168
169 pub fn default_color(&self) -> BACnetXyColor {
172 self.default_color
173 }
174
175 pub fn set_default_color(&mut self, color: BACnetXyColor) -> Result<(), Error> {
180 if !command::xy_in_range(color) {
181 return Err(common::value_out_of_range_error());
182 }
183 self.default_color = color;
184 Ok(())
185 }
186
187 pub fn start_output(&mut self) {
205 let restored = self.to_restore.take();
206 let target = if self.default_color != BACnetXyColor::new(0.0, 0.0) {
207 Some(self.default_color)
208 } else {
209 restored
210 };
211 if let Some(color) = target {
214 if color != self.present_value {
215 self.value_source.forget();
216 }
217 self.present_value = color;
218 self.engine.start(None);
219 self.output_changed();
220 }
221 }
222
223 fn output_changed(&mut self) {
225 self.output.note(ColorOutputValue::Xy(self.present_value));
226 }
227
228 pub fn set_value_source_tracking(&mut self, enabled: bool) {
238 self.value_source.set_enabled(enabled);
239 }
240
241 pub(super) fn value_source(&self) -> &SingleValueSource {
242 &self.value_source
243 }
244
245 pub fn with_tags_persistence(
249 instance: u32,
250 name: impl Into<String>,
251 persistence: Arc<dyn TagsPersistence>,
252 ) -> Result<Self, Error> {
253 let mut object = Self::new(instance, name)?;
254 object.profile = ProfileState::persistent(object.oid, persistence)?;
255 Ok(object)
256 }
257
258 pub fn wait_for_tag_saves(&self) {
261 self.profile.wait_for_saves();
262 }
263
264 pub fn set_profile(&mut self, profile: ObjectProfile) -> Result<(), Error> {
269 self.profile.provision(profile)
270 }
271
272 pub(super) fn profile(&self) -> &ObjectProfile {
273 self.profile.profile()
274 }
275
276 pub fn set_audit_policy(&mut self, policy: ObjectAuditPolicy) {
286 self.audit_policy = noncommandable_audit_policy(policy);
287 }
288
289 pub(super) fn audit_policy(&self) -> &ObjectAuditPolicy {
290 &self.audit_policy
291 }
292
293 pub fn set_present_value(&mut self, color: BACnetXyColor) -> Result<(), Error> {
300 if !command::xy_in_range(color) {
301 return Err(common::value_out_of_range_error());
302 }
303 self.write_present_value(color);
304 self.value_source.forget();
305 Ok(())
306 }
307
308 pub fn color_command(&self) -> BACnetColorCommand {
311 self.color_command
312 }
313
314 pub fn set_color_command(&mut self, command: BACnetColorCommand) -> Result<(), Error> {
324 command::check_color(&command)?;
325 if self.take_color_command(command) {
326 self.value_source.forget();
327 }
328 Ok(())
329 }
330
331 fn take_color_command(&mut self, command: BACnetColorCommand) -> bool {
335 let set = self.carry_out(command);
336 if set {
337 self.output_changed();
338 }
339 set
340 }
341
342 fn carry_out(&mut self, command: BACnetColorCommand) -> bool {
344 self.color_command = command;
345 let now = self.engine.now();
346 match command.operation {
347 ColorOperation::FADE_TO_COLOR => {
348 let Some(target) = command.target_color else {
349 return false;
350 };
351 let tracking = self.engine.tracking(self.present_value, now);
352 let fade_time = command.fade_time.unwrap_or(self.default_fade_time);
353 self.present_value = target;
354 self.engine.start(Transition::fade(
355 tracking,
356 target,
357 now,
358 milliseconds(fade_time),
359 ));
360 true
361 }
362 ColorOperation::STOP => match self.engine.halt(now) {
363 Some(reached) => {
364 self.present_value = reached;
365 true
366 }
367 None => false,
368 },
369 _ => false,
370 }
371 }
372
373 fn write_output(
376 &mut self,
377 property: PropertyIdentifier,
378 value: PropertyValue,
379 ) -> Result<bool, Error> {
380 if property == PropertyIdentifier::PRESENT_VALUE {
381 self.write_present_value(written_xy(value)?);
382 return Ok(true);
383 }
384 let command = command::decode_write(value, command::check_color)?;
385 Ok(self.take_color_command(command))
386 }
387
388 fn write_present_value(&mut self, color: BACnetXyColor) {
391 let now = self.engine.now();
392 let tracking = self.engine.tracking(self.present_value, now);
393 self.present_value = color;
394 let fade = (self.transition == ColorTransition::FADE)
395 .then(|| Transition::fade(tracking, color, now, milliseconds(self.default_fade_time)));
396 self.engine.start(fade.flatten());
397 self.output_changed();
398 }
399}
400
401impl BACnetObject for ColorObject {
402 fn object_identifier(&self) -> ObjectIdentifier {
403 self.oid
404 }
405
406 fn object_name(&self) -> &str {
407 &self.name
408 }
409
410 fn read_property(
411 &self,
412 property: PropertyIdentifier,
413 array_index: Option<u32>,
414 ) -> Result<PropertyValue, Error> {
415 if let Some(result) = read_identity_properties!(self, property, array_index) {
416 return result;
417 }
418 if let Some(result) = self.audit_policy.read(property, array_index) {
419 return result;
420 }
421 if let Some(result) = self.value_source.read(property, array_index) {
422 return result;
423 }
424 if let Some(result) = self.profile.read(property, array_index) {
425 return result;
426 }
427 match property {
431 p if p == PropertyIdentifier::OBJECT_TYPE => {
432 Ok(PropertyValue::Enumerated(ObjectType::COLOR.to_raw()))
433 }
434 p if p == PropertyIdentifier::PRESENT_VALUE => Ok(xy_value(self.present_value)),
435 p if p == PropertyIdentifier::TRACKING_VALUE => Ok(xy_value(
436 self.engine.tracking(self.present_value, self.engine.now()),
437 )),
438 p if p == PropertyIdentifier::COLOR_COMMAND => Ok(command::encode(&self.color_command)),
439 p if p == PropertyIdentifier::IN_PROGRESS => Ok(PropertyValue::Enumerated(
440 self.engine.in_progress(self.engine.now()).to_raw(),
441 )),
442 p if p == PropertyIdentifier::DEFAULT_COLOR => Ok(xy_value(self.default_color)),
443 p if p == PropertyIdentifier::DEFAULT_FADE_TIME => {
444 Ok(PropertyValue::Unsigned(u64::from(self.default_fade_time)))
445 }
446 p if p == PropertyIdentifier::TRANSITION => {
447 Ok(PropertyValue::Enumerated(self.transition.to_raw()))
448 }
449 _ => Err(common::unknown_property_error()),
450 }
451 }
452
453 fn write_property(
454 &mut self,
455 property: PropertyIdentifier,
456 array_index: Option<u32>,
457 value: PropertyValue,
458 priority: Option<u8>,
459 ) -> Result<(), Error> {
460 if let Some(result) = common::write_description(&mut self.description, property, &value) {
461 return result;
462 }
463 if let Some(result) = self
464 .audit_policy
465 .write(property, array_index, &value, priority)
466 {
467 return result;
468 }
469 if let Some(result) = self.profile.write(property, array_index, &value) {
470 return result;
471 }
472 match property {
473 p if p == PropertyIdentifier::PRESENT_VALUE
474 || p == PropertyIdentifier::COLOR_COMMAND =>
475 {
476 self.value_source.unsourced()?;
478 self.write_output(property, value)?;
479 }
480 p if p == PropertyIdentifier::DEFAULT_COLOR => {
483 self.default_color = written_xy(value)?;
484 }
485 p if p == PropertyIdentifier::DEFAULT_FADE_TIME => {
486 self.default_fade_time = written_unsigned(value, command::FADE_TIME_MS)?;
487 }
488 p if p == PropertyIdentifier::TRANSITION => {
489 self.transition = written_transition(value, ColorTransition::FADE)?;
490 }
491 _ => {
492 return Err(common::unhandled_write_error(
493 self.property_metadata().as_ref(),
494 property,
495 array_index,
496 ));
497 }
498 }
499 Ok(())
500 }
501
502 fn write_property_from(
503 &mut self,
504 property: PropertyIdentifier,
505 array_index: Option<u32>,
506 value: PropertyValue,
507 priority: Option<u8>,
508 origin: &CommandOrigin,
509 ) -> Result<(), Error> {
510 match property {
511 PropertyIdentifier::VALUE_SOURCE => {
512 self.value_source.correct(array_index, value, origin)
513 }
514 PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::COLOR_COMMAND => {
515 self.value_source.admit(origin)?;
516 if self.write_output(property, value)? {
517 self.value_source.record(origin);
518 }
519 Ok(())
520 }
521 _ => self.write_property(property, array_index, value, priority),
522 }
523 }
524
525 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
526 metadata::for_color_object(self)
527 }
528
529 fn durable_writes_internal(&mut self) -> Option<&mut dyn crate::durable::DurableWrites> {
530 if self.output.is_some() {
531 return Some(self);
532 }
533 self.profile.capability()
534 }
535
536 fn start_output_internal(&mut self) {
537 self.start_output();
538 }
539
540 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
541 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
542 }
543
544 fn supports_cov(&self) -> bool {
545 true
546 }
547
548 fn audit_object_policy_internal(&self) -> ObjectAuditPolicy {
549 self.audit_policy
550 }
551
552 fn audit_policy_authority_internal(&mut self) -> Option<AuditPolicyAuthority<'_>> {
553 Some(AuditPolicyAuthority::new(&mut self.audit_policy))
554 }
555
556 fn advance_time_internal(&mut self, elapsed: Duration) -> bool {
557 self.engine.advance_by(elapsed)
558 }
559
560 fn bind_monotonic_clock_internal(&mut self, clock: Option<Arc<MonotonicClock>>) {
561 self.engine.bind_clock(clock);
562 }
563
564 fn bind_deadline_waker_internal(&mut self, waker: Option<Arc<DeadlineWaker>>) {
565 self.engine.bind_waker(waker);
566 }
567
568 fn advance_monotonic_time_internal(&mut self, now: Duration) -> bool {
569 self.profile.expire(now);
570 self.engine.advance_to(now)
571 }
572
573 fn next_monotonic_deadline_internal(&self) -> Option<Duration> {
574 self.engine.deadline()
575 }
576
577 fn cov_snapshot_internal(&self) -> Option<Box<dyn BACnetObject>> {
581 Some(Box::new(Self {
582 engine: self.engine.frozen(),
583 ..self.clone()
584 }))
585 }
586}
587
588impl_color_durable!(ColorObject);