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