Skip to main content

bacnet_objects/color/
mod.rs

1//! Color (type 63) and Color Temperature (type 64) objects, added by
2//! Addendum 135-2020ca (Clauses 12.X and 12.Y, Tables 12-X and 12-Y).
3//!
4//! A Color object holds a CIE 1931 xy colour, a Color Temperature object a
5//! correlated colour temperature in kelvin. Both take a typed
6//! `BACnetColorCommand` as Color_Command, checked against the operations
7//! each allows (see `command`), and carry it out (#1474): fades and ramps
8//! move Tracking_Value to the new Present_Value over time, with In_Progress
9//! showing which, steps change the value at once, and STOP ends a fade or
10//! ramp where it stands (see `engine`). A Present_Value write moves the
11//! output as the object's Transition says.
12//!
13//! At the device's start the server drives each output that nothing has
14//! driven yet to its default, Default_Color or Default_Color_Temperature,
15//! at once; a zero default keeps the value the object came back with from
16//! its storage (see `persistence`), and with nothing stored the output stays
17//! undriven. Until it is driven, In_Progress reads NOT_CONTROLLED (#1526).
18//!
19//! Neither object has a priority array, and their property tables have no
20//! Status_Flags, Event_State, Reliability or Out_Of_Service, so the objects
21//! serve none of them. Value_Source, once tracked, names the last writer of
22//! Present_Value (#1552).
23
24use core::ops::RangeInclusive;
25use core::time::Duration;
26
27use bacnet_types::constructed::BACnetXyColor;
28use bacnet_types::enums::ColorTransition;
29use bacnet_types::error::Error;
30use bacnet_types::primitives::PropertyValue;
31
32use crate::audit::ObjectAuditPolicy;
33use crate::common;
34
35mod command;
36mod engine;
37mod metadata;
38mod persistence;
39mod saving;
40mod temperature;
41mod xy;
42
43#[cfg(feature = "std")]
44pub use persistence::FileColorOutputPersistence;
45pub use persistence::{ColorOutputPersistence, ColorOutputSnapshot, ColorOutputValue};
46pub use saving::ColorOutputSaveCounters;
47pub use temperature::ColorTemperatureObject;
48pub use xy::ColorObject;
49
50#[cfg(test)]
51mod command_tests;
52#[cfg(test)]
53mod engine_tests;
54#[cfg(test)]
55mod metadata_tests;
56#[cfg(all(test, feature = "std"))]
57mod persistence_tests;
58#[cfg(test)]
59mod rows_tests;
60#[cfg(test)]
61mod start_tests;
62#[cfg(test)]
63mod value_source_tests;
64
65/// The value an xy colour reads as: its two REALs, which encode as the
66/// BACnetxyColor SEQUENCE.
67fn xy_value(color: BACnetXyColor) -> PropertyValue {
68    PropertyValue::List(vec![
69        PropertyValue::Real(color.x),
70        PropertyValue::Real(color.y),
71    ])
72}
73
74/// An xy colour written to the object: the two REALs a WriteProperty of a
75/// BACnetxyColor decodes to. A coordinate outside 0.0 to 1.0, NaN included,
76/// is VALUE_OUT_OF_RANGE (Clause 12.X); any other value INVALID_DATA_TYPE.
77fn written_xy(value: PropertyValue) -> Result<BACnetXyColor, Error> {
78    let PropertyValue::List(items) = value else {
79        return Err(common::invalid_data_type_error());
80    };
81    let [PropertyValue::Real(x), PropertyValue::Real(y)] = items.as_slice() else {
82        return Err(common::invalid_data_type_error());
83    };
84    let color = BACnetXyColor::new(*x, *y);
85    if command::xy_in_range(color) {
86        Ok(color)
87    } else {
88        Err(common::value_out_of_range_error())
89    }
90}
91
92/// An Unsigned written to the object that must lie within `range`: any other
93/// value is VALUE_OUT_OF_RANGE, and any other datatype INVALID_DATA_TYPE.
94fn written_unsigned(value: PropertyValue, range: RangeInclusive<u32>) -> Result<u32, Error> {
95    let PropertyValue::Unsigned(value) = value else {
96        return Err(common::invalid_data_type_error());
97    };
98    u32::try_from(value)
99        .ok()
100        .filter(|value| range.contains(value))
101        .ok_or_else(common::value_out_of_range_error)
102}
103
104/// A Transition written to the object: NONE up to `last`, the last kind the
105/// object has. Any other ENUMERATED is VALUE_OUT_OF_RANGE, and any other
106/// datatype INVALID_DATA_TYPE.
107fn written_transition(
108    value: PropertyValue,
109    last: ColorTransition,
110) -> Result<ColorTransition, Error> {
111    let PropertyValue::Enumerated(raw) = value else {
112        return Err(common::invalid_data_type_error());
113    };
114    if raw > last.to_raw() {
115        return Err(common::value_out_of_range_error());
116    }
117    Ok(ColorTransition::from_raw(raw))
118}
119
120/// An audit policy for an object with no commandable property: its
121/// Audit_Priority_Filter left out (#1525).
122fn noncommandable_audit_policy(policy: ObjectAuditPolicy) -> ObjectAuditPolicy {
123    ObjectAuditPolicy {
124        priority_filter: None,
125        ..policy
126    }
127}
128
129/// A fade time in milliseconds as a `Duration`.
130fn milliseconds(fade_time: u32) -> Duration {
131    Duration::from_millis(u64::from(fade_time))
132}