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::{
15 command, metadata, milliseconds, noncommandable_audit_policy, written_transition,
16 written_unsigned, written_xy, xy_value,
17};
18use crate::audit::{AuditPolicyAuthority, ObjectAuditPolicy};
19use crate::command_source::{CommandOrigin, SingleValueSource};
20use crate::common::{self, read_identity_properties};
21use crate::object_profile::{ObjectProfile, ProfileState, TagsPersistence};
22use crate::traits::{BACnetObject, DeadlineWaker, MonotonicClock};
23use crate::transition::Transition;
24
25const D65: BACnetXyColor = BACnetXyColor::new(0.3127, 0.3290);
27
28const XY_SAMPLE_STEP: f64 = 0.001;
34
35#[derive(Clone)]
58pub struct ColorObject {
59 oid: ObjectIdentifier,
60 name: String,
61 description: String,
62 present_value: BACnetXyColor,
64 color_command: BACnetColorCommand,
66 default_color: BACnetXyColor,
68 default_fade_time: u32,
70 transition: ColorTransition,
72 audit_policy: ObjectAuditPolicy,
74 value_source: SingleValueSource,
76 profile: ProfileState,
78 engine: Engine<BACnetXyColor>,
79}
80
81impl ColorObject {
82 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
88 let oid = ObjectIdentifier::new(ObjectType::COLOR, instance)?;
89 Ok(Self {
90 oid,
91 name: name.into(),
92 description: String::new(),
93 present_value: D65,
94 color_command: BACnetColorCommand::new(ColorOperation::NONE),
95 default_color: D65,
96 default_fade_time: *command::FADE_TIME_MS.start(),
97 transition: ColorTransition::NONE,
98 audit_policy: ObjectAuditPolicy::default(),
99 value_source: SingleValueSource::default(),
100 profile: ProfileState::default(),
101 engine: Engine::new(XY_SAMPLE_STEP),
102 })
103 }
104
105 pub fn set_value_source_tracking(&mut self, enabled: bool) {
115 self.value_source.set_enabled(enabled);
116 }
117
118 pub(super) fn value_source(&self) -> &SingleValueSource {
119 &self.value_source
120 }
121
122 pub fn with_tags_persistence(
126 instance: u32,
127 name: impl Into<String>,
128 persistence: Arc<dyn TagsPersistence>,
129 ) -> Result<Self, Error> {
130 let mut object = Self::new(instance, name)?;
131 object.profile = ProfileState::persistent(object.oid, persistence)?;
132 Ok(object)
133 }
134
135 pub fn wait_for_tag_saves(&self) {
138 self.profile.wait_for_saves();
139 }
140
141 pub fn set_profile(&mut self, profile: ObjectProfile) -> Result<(), Error> {
146 self.profile.provision(profile)
147 }
148
149 pub(super) fn profile(&self) -> &ObjectProfile {
150 self.profile.profile()
151 }
152
153 pub fn set_audit_policy(&mut self, policy: ObjectAuditPolicy) {
163 self.audit_policy = noncommandable_audit_policy(policy);
164 }
165
166 pub(super) fn audit_policy(&self) -> &ObjectAuditPolicy {
167 &self.audit_policy
168 }
169
170 pub fn set_present_value(&mut self, color: BACnetXyColor) -> Result<(), Error> {
177 if !command::xy_in_range(color) {
178 return Err(common::value_out_of_range_error());
179 }
180 self.write_present_value(color);
181 self.value_source.forget();
182 Ok(())
183 }
184
185 pub fn color_command(&self) -> BACnetColorCommand {
188 self.color_command
189 }
190
191 pub fn set_color_command(&mut self, command: BACnetColorCommand) -> Result<(), Error> {
201 command::check_color(&command)?;
202 if self.take_color_command(command) {
203 self.value_source.forget();
204 }
205 Ok(())
206 }
207
208 fn take_color_command(&mut self, command: BACnetColorCommand) -> bool {
212 self.color_command = command;
213 let now = self.engine.now();
214 match command.operation {
215 ColorOperation::FADE_TO_COLOR => {
216 let Some(target) = command.target_color else {
217 return false;
218 };
219 let tracking = self.engine.tracking(self.present_value, now);
220 let fade_time = command.fade_time.unwrap_or(self.default_fade_time);
221 self.present_value = target;
222 self.engine.start(Transition::fade(
223 tracking,
224 target,
225 now,
226 milliseconds(fade_time),
227 ));
228 true
229 }
230 ColorOperation::STOP => match self.engine.halt(now) {
231 Some(reached) => {
232 self.present_value = reached;
233 true
234 }
235 None => false,
236 },
237 _ => false,
238 }
239 }
240
241 fn write_output(
244 &mut self,
245 property: PropertyIdentifier,
246 value: PropertyValue,
247 ) -> Result<bool, Error> {
248 if property == PropertyIdentifier::PRESENT_VALUE {
249 self.write_present_value(written_xy(value)?);
250 return Ok(true);
251 }
252 let command = command::decode_write(value, command::check_color)?;
253 Ok(self.take_color_command(command))
254 }
255
256 fn write_present_value(&mut self, color: BACnetXyColor) {
259 let now = self.engine.now();
260 let tracking = self.engine.tracking(self.present_value, now);
261 self.present_value = color;
262 let fade = (self.transition == ColorTransition::FADE)
263 .then(|| Transition::fade(tracking, color, now, milliseconds(self.default_fade_time)));
264 self.engine.start(fade.flatten());
265 }
266}
267
268impl BACnetObject for ColorObject {
269 fn object_identifier(&self) -> ObjectIdentifier {
270 self.oid
271 }
272
273 fn object_name(&self) -> &str {
274 &self.name
275 }
276
277 fn read_property(
278 &self,
279 property: PropertyIdentifier,
280 array_index: Option<u32>,
281 ) -> Result<PropertyValue, Error> {
282 if let Some(result) = read_identity_properties!(self, property, array_index) {
283 return result;
284 }
285 if let Some(result) = self.audit_policy.read(property, array_index) {
286 return result;
287 }
288 if let Some(result) = self.value_source.read(property, array_index) {
289 return result;
290 }
291 if let Some(result) = self.profile.read(property, array_index) {
292 return result;
293 }
294 match property {
298 p if p == PropertyIdentifier::OBJECT_TYPE => {
299 Ok(PropertyValue::Enumerated(ObjectType::COLOR.to_raw()))
300 }
301 p if p == PropertyIdentifier::PRESENT_VALUE => Ok(xy_value(self.present_value)),
302 p if p == PropertyIdentifier::TRACKING_VALUE => Ok(xy_value(
303 self.engine.tracking(self.present_value, self.engine.now()),
304 )),
305 p if p == PropertyIdentifier::COLOR_COMMAND => Ok(command::encode(&self.color_command)),
306 p if p == PropertyIdentifier::IN_PROGRESS => Ok(PropertyValue::Enumerated(
307 self.engine.in_progress(self.engine.now()).to_raw(),
308 )),
309 p if p == PropertyIdentifier::DEFAULT_COLOR => Ok(xy_value(self.default_color)),
310 p if p == PropertyIdentifier::DEFAULT_FADE_TIME => {
311 Ok(PropertyValue::Unsigned(u64::from(self.default_fade_time)))
312 }
313 p if p == PropertyIdentifier::TRANSITION => {
314 Ok(PropertyValue::Enumerated(self.transition.to_raw()))
315 }
316 _ => Err(common::unknown_property_error()),
317 }
318 }
319
320 fn write_property(
321 &mut self,
322 property: PropertyIdentifier,
323 array_index: Option<u32>,
324 value: PropertyValue,
325 priority: Option<u8>,
326 ) -> Result<(), Error> {
327 if let Some(result) = common::write_description(&mut self.description, property, &value) {
328 return result;
329 }
330 if let Some(result) = self
331 .audit_policy
332 .write(property, array_index, &value, priority)
333 {
334 return result;
335 }
336 if let Some(result) = self.profile.write(property, array_index, &value) {
337 return result;
338 }
339 match property {
340 p if p == PropertyIdentifier::PRESENT_VALUE
341 || p == PropertyIdentifier::COLOR_COMMAND =>
342 {
343 self.value_source.unsourced()?;
345 self.write_output(property, value)?;
346 }
347 p if p == PropertyIdentifier::DEFAULT_COLOR => {
350 self.default_color = written_xy(value)?;
351 }
352 p if p == PropertyIdentifier::DEFAULT_FADE_TIME => {
353 self.default_fade_time = written_unsigned(value, command::FADE_TIME_MS)?;
354 }
355 p if p == PropertyIdentifier::TRANSITION => {
356 self.transition = written_transition(value, ColorTransition::FADE)?;
357 }
358 _ => {
359 return Err(common::unhandled_write_error(
360 self.property_metadata().as_ref(),
361 property,
362 array_index,
363 ))
364 }
365 }
366 Ok(())
367 }
368
369 fn write_property_from(
370 &mut self,
371 property: PropertyIdentifier,
372 array_index: Option<u32>,
373 value: PropertyValue,
374 priority: Option<u8>,
375 origin: &CommandOrigin,
376 ) -> Result<(), Error> {
377 match property {
378 PropertyIdentifier::VALUE_SOURCE => {
379 self.value_source.correct(array_index, value, origin)
380 }
381 PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::COLOR_COMMAND => {
382 self.value_source.admit(origin)?;
383 if self.write_output(property, value)? {
384 self.value_source.record(origin);
385 }
386 Ok(())
387 }
388 _ => self.write_property(property, array_index, value, priority),
389 }
390 }
391
392 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
393 metadata::for_color_object(self)
394 }
395
396 fn durable_writes_internal(&mut self) -> Option<&mut dyn crate::durable::DurableWrites> {
397 self.profile.capability()
398 }
399
400 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
401 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
402 }
403
404 fn supports_cov(&self) -> bool {
405 true
406 }
407
408 fn audit_object_policy_internal(&self) -> ObjectAuditPolicy {
409 self.audit_policy
410 }
411
412 fn audit_policy_authority_internal(&mut self) -> Option<AuditPolicyAuthority<'_>> {
413 Some(AuditPolicyAuthority::new(&mut self.audit_policy))
414 }
415
416 fn advance_time_internal(&mut self, elapsed: Duration) -> bool {
417 self.engine.advance_by(elapsed)
418 }
419
420 fn bind_monotonic_clock_internal(&mut self, clock: Option<Arc<MonotonicClock>>) {
421 self.engine.bind_clock(clock);
422 }
423
424 fn bind_deadline_waker_internal(&mut self, waker: Option<Arc<DeadlineWaker>>) {
425 self.engine.bind_waker(waker);
426 }
427
428 fn advance_monotonic_time_internal(&mut self, now: Duration) -> bool {
429 self.profile.expire(now);
430 self.engine.advance_to(now)
431 }
432
433 fn next_monotonic_deadline_internal(&self) -> Option<Duration> {
434 self.engine.deadline()
435 }
436
437 fn cov_snapshot_internal(&self) -> Option<Box<dyn BACnetObject>> {
441 Some(Box::new(Self {
442 engine: self.engine.frozen(),
443 ..self.clone()
444 }))
445 }
446}