bacnet_objects/lighting/
color_link.rs1use bacnet_types::constructed::BACnetXyColor;
25use bacnet_types::enums::{ObjectType, PropertyIdentifier as P};
26use bacnet_types::error::Error;
27use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
28
29use crate::common;
30use crate::database::ObjectDatabase;
31use crate::property_metadata::{
32 PropertyConformance::Optional, PropertyMetadata, PropertyPresenceCondition,
33 PropertyWriteCapability::Always,
34};
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct ColorLink {
43 pub reference: ObjectIdentifier,
45 pub color_override: Option<ColorOverride>,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct ColorOverride {
53 pub active: bool,
55 pub reference: ObjectIdentifier,
58}
59
60impl ColorLink {
61 pub const fn new(reference: ObjectIdentifier) -> Self {
63 Self {
64 reference,
65 color_override: None,
66 }
67 }
68
69 pub fn active_reference(&self) -> Option<ObjectIdentifier> {
73 let reference = match self.color_override {
74 Some(ColorOverride {
75 active: true,
76 reference,
77 }) => reference,
78 _ => self.reference,
79 };
80 (reference.instance_number() != ObjectIdentifier::WILDCARD_INSTANCE).then_some(reference)
81 }
82
83 pub(super) fn checked(self) -> Result<Self, Error> {
86 checked_reference(self.reference)?;
87 if let Some(color_override) = self.color_override {
88 checked_reference(color_override.reference)?;
89 }
90 Ok(self)
91 }
92}
93
94fn checked_reference(reference: ObjectIdentifier) -> Result<ObjectIdentifier, Error> {
95 if reference.instance_number() == ObjectIdentifier::WILDCARD_INSTANCE
96 || matches!(
97 reference.object_type(),
98 ObjectType::COLOR | ObjectType::COLOR_TEMPERATURE
99 )
100 {
101 Ok(reference)
102 } else {
103 Err(common::value_out_of_range_error())
104 }
105}
106
107pub(super) fn metadata(link: Option<&ColorLink>) -> impl Iterator<Item = PropertyMetadata> {
112 let overridable = link.is_some_and(|link| link.color_override.is_some());
113 [
114 (P::COLOR_REFERENCE, link.is_some()),
115 (P::COLOR_OVERRIDE, overridable),
116 (P::OVERRIDE_COLOR_REFERENCE, overridable),
117 ]
118 .into_iter()
119 .filter(|&(_, present)| present)
120 .map(|(property, _)| {
121 PropertyMetadata::new(
122 property,
123 Optional,
124 Some(PropertyPresenceCondition::LightingColor),
125 Always,
126 )
127 })
128}
129
130pub(super) fn read(link: Option<&ColorLink>, property: P) -> Option<Result<PropertyValue, Error>> {
133 let color_override = link.and_then(|link| link.color_override);
134 let value = match property {
135 P::COLOR_REFERENCE => link.map(|link| PropertyValue::ObjectIdentifier(link.reference)),
136 P::COLOR_OVERRIDE => color_override.map(|o| PropertyValue::Boolean(o.active)),
137 P::OVERRIDE_COLOR_REFERENCE => {
138 color_override.map(|o| PropertyValue::ObjectIdentifier(o.reference))
139 }
140 _ => return None,
141 };
142 Some(value.ok_or_else(common::unknown_property_error))
143}
144
145pub(super) fn write(
147 link: &mut Option<ColorLink>,
148 property: P,
149 value: &PropertyValue,
150) -> Option<Result<(), Error>> {
151 if let Err(error) = read(link.as_ref(), property)? {
152 return Some(Err(error));
153 }
154 let link = link.as_mut()?;
155 Some(match (property, value) {
156 (P::COLOR_REFERENCE, PropertyValue::ObjectIdentifier(reference)) => {
157 checked_reference(*reference).map(|reference| link.reference = reference)
158 }
159 (P::COLOR_OVERRIDE, PropertyValue::Boolean(active)) => {
160 if let Some(color_override) = &mut link.color_override {
161 color_override.active = *active;
162 }
163 Ok(())
164 }
165 (P::OVERRIDE_COLOR_REFERENCE, PropertyValue::ObjectIdentifier(reference)) => {
166 checked_reference(*reference).map(|reference| {
167 if let Some(color_override) = &mut link.color_override {
168 color_override.reference = reference;
169 }
170 })
171 }
172 _ => Err(common::invalid_data_type_error()),
173 })
174}
175
176fn read_link(output: &dyn crate::traits::BACnetObject) -> Option<ColorLink> {
178 let read = |property| output.read_property(property, None).ok();
179 let Some(PropertyValue::ObjectIdentifier(reference)) = read(P::COLOR_REFERENCE) else {
180 return None;
181 };
182 let color_override = match (read(P::COLOR_OVERRIDE), read(P::OVERRIDE_COLOR_REFERENCE)) {
183 (
184 Some(PropertyValue::Boolean(active)),
185 Some(PropertyValue::ObjectIdentifier(reference)),
186 ) => Some(ColorOverride { active, reference }),
187 _ => None,
188 };
189 Some(ColorLink {
190 reference,
191 color_override,
192 })
193}
194
195#[derive(Debug, Clone, Copy, PartialEq)]
197pub struct LightingColor {
198 pub source: ObjectIdentifier,
200 pub color: OutputColor,
202}
203
204#[derive(Debug, Clone, Copy, PartialEq)]
206pub enum OutputColor {
207 Xy(BACnetXyColor),
209 Kelvin(u32),
212}
213
214impl ObjectDatabase {
215 pub fn lighting_color(&self, lighting_output: &ObjectIdentifier) -> Option<LightingColor> {
225 let source = read_link(self.get(lighting_output)?)?.active_reference()?;
226 let tracking = self
227 .get(&source)?
228 .read_property(P::TRACKING_VALUE, None)
229 .ok()?;
230 let color = match (source.object_type(), tracking) {
231 (ObjectType::COLOR, PropertyValue::List(xy)) => match xy.as_slice() {
232 [PropertyValue::Real(x), PropertyValue::Real(y)] => {
233 OutputColor::Xy(BACnetXyColor::new(*x, *y))
234 }
235 _ => return None,
236 },
237 (ObjectType::COLOR_TEMPERATURE, PropertyValue::Unsigned(kelvin)) => {
238 OutputColor::Kelvin(u32::try_from(kelvin).ok()?)
239 }
240 _ => return None,
241 };
242 Some(LightingColor { source, color })
243 }
244}