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//! Neither object has a priority array, and their property tables have no
14//! Status_Flags, Event_State, Reliability or Out_Of_Service, so the objects
15//! serve none of them. Value_Source, once tracked, names the last writer of
16//! Present_Value (#1552).
17
18use std::ops::RangeInclusive;
19use std::time::Duration;
20
21use bacnet_types::constructed::BACnetXyColor;
22use bacnet_types::enums::ColorTransition;
23use bacnet_types::error::Error;
24use bacnet_types::primitives::PropertyValue;
25
26use crate::audit::ObjectAuditPolicy;
27use crate::common;
28
29mod command;
30mod engine;
31mod metadata;
32mod temperature;
33mod xy;
34
35pub use temperature::ColorTemperatureObject;
36pub use xy::ColorObject;
37
38#[cfg(test)]
39mod command_tests;
40#[cfg(test)]
41mod engine_tests;
42#[cfg(test)]
43mod metadata_tests;
44#[cfg(test)]
45mod rows_tests;
46#[cfg(test)]
47mod value_source_tests;
48
49/// The value an xy colour reads as: its two REALs, which encode as the
50/// BACnetxyColor SEQUENCE.
51fn xy_value(color: BACnetXyColor) -> PropertyValue {
52    PropertyValue::List(vec![
53        PropertyValue::Real(color.x),
54        PropertyValue::Real(color.y),
55    ])
56}
57
58/// An xy colour written to the object: the two REALs a WriteProperty of a
59/// BACnetxyColor decodes to. A coordinate outside 0.0 to 1.0, NaN included,
60/// is VALUE_OUT_OF_RANGE (Clause 12.X); any other value INVALID_DATA_TYPE.
61fn written_xy(value: PropertyValue) -> Result<BACnetXyColor, Error> {
62    let PropertyValue::List(items) = value else {
63        return Err(common::invalid_data_type_error());
64    };
65    let [PropertyValue::Real(x), PropertyValue::Real(y)] = items.as_slice() else {
66        return Err(common::invalid_data_type_error());
67    };
68    let color = BACnetXyColor::new(*x, *y);
69    if command::xy_in_range(color) {
70        Ok(color)
71    } else {
72        Err(common::value_out_of_range_error())
73    }
74}
75
76/// An Unsigned written to the object that must lie within `range`: any other
77/// value is VALUE_OUT_OF_RANGE, and any other datatype INVALID_DATA_TYPE.
78fn written_unsigned(value: PropertyValue, range: RangeInclusive<u32>) -> Result<u32, Error> {
79    let PropertyValue::Unsigned(value) = value else {
80        return Err(common::invalid_data_type_error());
81    };
82    u32::try_from(value)
83        .ok()
84        .filter(|value| range.contains(value))
85        .ok_or_else(common::value_out_of_range_error)
86}
87
88/// A Transition written to the object: NONE up to `last`, the last kind the
89/// object has. Any other ENUMERATED is VALUE_OUT_OF_RANGE, and any other
90/// datatype INVALID_DATA_TYPE.
91fn written_transition(
92    value: PropertyValue,
93    last: ColorTransition,
94) -> Result<ColorTransition, Error> {
95    let PropertyValue::Enumerated(raw) = value else {
96        return Err(common::invalid_data_type_error());
97    };
98    if raw > last.to_raw() {
99        return Err(common::value_out_of_range_error());
100    }
101    Ok(ColorTransition::from_raw(raw))
102}
103
104/// An audit policy for an object with no commandable property: its
105/// Audit_Priority_Filter left out (#1525).
106fn noncommandable_audit_policy(policy: ObjectAuditPolicy) -> ObjectAuditPolicy {
107    ObjectAuditPolicy {
108        priority_filter: None,
109        ..policy
110    }
111}
112
113/// A fade time in milliseconds as a `Duration`.
114fn milliseconds(fade_time: u32) -> Duration {
115    Duration::from_millis(u64::from(fade_time))
116}