bacnet_objects/lighting/mod.rs
1//! Lighting Output (type 54) and Binary Lighting Output (type 55) objects per
2//! ASHRAE 135-2020 Clauses 12.54 and 12.55.
3
4use bacnet_types::constructed::BACnetLightingCommand;
5use bacnet_types::enums::{LightingOperation, ObjectType, PropertyIdentifier, Reliability};
6use bacnet_types::error::Error;
7use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
8use std::borrow::Cow;
9use std::sync::Arc;
10use std::time::Duration;
11
12use crate::common::{self, read_common_properties, read_priority_array};
13use crate::object_profile::{ObjectProfile, ProfileState, TagsPersistence};
14use crate::traits::{BACnetObject, DeadlineWaker, MonotonicClock};
15
16// ---------------------------------------------------------------------------
17// LightingOutput (type 54)
18// ---------------------------------------------------------------------------
19
20/// BACnet Lighting Output object.
21///
22/// Commandable output with a 16-level priority array controlling a
23/// floating-point present-value (0.0 to 100.0 percent). A commanded level
24/// above 0.0 and below 1.0 is stored as 1.0, and so is such a
25/// Relinquish_Default (see [`set_relinquish_default`](Self::set_relinquish_default)).
26///
27/// Lighting_Command takes a [`BACnetLightingCommand`] checked against its
28/// operation (see [`set_lighting_command`](Self::set_lighting_command)),
29/// serves the last one taken and carries it out (#1384): fades and ramps move
30/// Tracking_Value to a new level over time with In_Progress showing which,
31/// steps change the level at once, and the warn operations blink and hold
32/// the level for Egress_Time before relinquishing or turning it off.
33/// Present_Value takes the blink-warn values -1.0, -2.0 and -3.0 as the
34/// three warn commands. Tracking_Value equals Present_Value whenever no fade
35/// or ramp is moving it and no trim holds it.
36///
37/// Color_Reference, Color_Override and Override_Color_Reference are absent
38/// until [`set_color_link`](Self::set_color_link) links the output to its
39/// colour objects (#1527).
40///
41/// High_End_Trim, Low_End_Trim and Trim_Fade_Time are absent until set
42/// (#1528; see [`set_high_end_trim`](Self::set_high_end_trim)). Once set,
43/// Tracking_Value is held between the trims, In_Progress reads TRIM_ACTIVE
44/// while that keeps it from Present_Value, and a trim change takes
45/// Trim_Fade_Time to show.
46///
47/// Fades, ramps and egress timers run on the monotonic clock the database
48/// binds; the server's monotonic task advances them and fans their COV out.
49/// With no clock bound they wait on `advance_time_internal`.
50///
51/// Cloning copies served state into an in-memory object. Tags persistence and
52/// pending saves remain solely with the original; cloning or dropping the copy
53/// never saves, corrects, settles, or waits on the original writer.
54#[derive(Clone)]
55pub struct LightingOutputObject {
56 oid: ObjectIdentifier,
57 name: String,
58 description: String,
59 present_value: f32,
60 /// The last command written; operation NONE until then.
61 lighting_command: BACnetLightingCommand,
62 lighting_command_default_priority: u32,
63 /// The fade, ramp or egress in progress, if any.
64 operation: Option<engine::Operation>,
65 /// Blink-warn notifications requested so far, for tests to observe.
66 blink_request_count: u64,
67 blink_warn_enable: bool,
68 /// Egress_Time in seconds.
69 egress_time: u32,
70 /// Default_Fade_Time in milliseconds, within 100..=86_400_000.
71 default_fade_time: u32,
72 /// Default_Ramp_Rate in percent per second, within 0.1..=100.0.
73 default_ramp_rate: f32,
74 /// Default_Step_Increment in percent, within 0.1..=100.0.
75 default_step_increment: f32,
76 /// COV_Increment: the Present_Value change that triggers a notification.
77 cov_increment: f32,
78 /// The finest COV increment a Tracking_Value subscriber asks for, as the
79 /// server last passed it (#1510).
80 finest_tracking_increment: Option<f64>,
81 out_of_service: bool,
82 status_flags: StatusFlags,
83 /// Reliability; NO_FAULT_DETECTED until a fault is evaluated or simulated.
84 reliability: Reliability,
85 priority_array: [Option<f32>; 16],
86 relinquish_default: f32,
87 /// High_End_Trim, Low_End_Trim and Trim_Fade_Time, and a trim change
88 /// under way.
89 trims: trim::Trims,
90 /// Color_Reference, with Color_Override and Override_Color_Reference
91 /// when overridable; absent until set.
92 color_link: Option<ColorLink>,
93 /// Tags, Profile_Location and Profile_Name, once provisioned (#1553).
94 profile: ProfileState,
95 monotonic_clock: Option<Arc<MonotonicClock>>,
96 deadline_waker: Option<Arc<DeadlineWaker>>,
97 /// The time an object with no clock bound has been advanced to.
98 logical_now: Duration,
99}
100
101impl LightingOutputObject {
102 /// Create a new Lighting Output object.
103 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
104 let oid = ObjectIdentifier::new(ObjectType::LIGHTING_OUTPUT, instance)?;
105 Ok(Self {
106 oid,
107 name: name.into(),
108 description: String::new(),
109 present_value: 0.0,
110 lighting_command: BACnetLightingCommand::new(LightingOperation::NONE),
111 lighting_command_default_priority: 16,
112 operation: None,
113 blink_request_count: 0,
114 blink_warn_enable: false,
115 egress_time: 0,
116 default_fade_time: *DEFAULT_FADE_TIME_MS.start(),
117 default_ramp_rate: 100.0,
118 default_step_increment: 1.0,
119 cov_increment: 0.0,
120 finest_tracking_increment: None,
121 out_of_service: false,
122 status_flags: StatusFlags::empty(),
123 reliability: Reliability::NO_FAULT_DETECTED,
124 priority_array: [None; 16],
125 relinquish_default: 0.0,
126 trims: trim::Trims::NONE,
127 color_link: None,
128 profile: ProfileState::default(),
129 monotonic_clock: None,
130 deadline_waker: None,
131 logical_now: Duration::ZERO,
132 })
133 }
134
135 /// Set the description string.
136 pub fn set_description(&mut self, desc: impl Into<String>) {
137 self.description = desc.into();
138 }
139
140 /// Recalculate present-value from the priority array.
141 ///
142 /// Tracking_Value isn't stored: it reads as Present_Value while
143 /// In_Progress is IDLE and as the fade or ramp's current value while one
144 /// runs (Clause 12.54.5), so a new Present_Value shows in it only once
145 /// nothing is moving it.
146 fn recalculate_present_value(&mut self) {
147 self.present_value =
148 common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
149 }
150
151 /// Set the Relinquish_Default (#270).
152 ///
153 /// Checked the same way a commanded Present_Value is: a value outside
154 /// 0.0 to 100.0, or NaN, is refused with VALUE_OUT_OF_RANGE, and one
155 /// above 0.0 and below 1.0 is stored as 1.0, so Present_Value never falls
156 /// back to a level between off and the dimmest on level. After the store,
157 /// Present_Value is resolved anew from the priority array so an empty
158 /// array falls back to the new default immediately.
159 pub fn set_relinquish_default(&mut self, value: f32) -> Result<(), Error> {
160 self.relinquish_default = normalized_level(value)?;
161 self.recalculate_present_value();
162 Ok(())
163 }
164
165 /// The last command written to Lighting_Command, or operation NONE with no
166 /// other field before any write.
167 pub fn lighting_command(&self) -> BACnetLightingCommand {
168 self.lighting_command
169 }
170
171 /// Set Lighting_Command and carry the command out, as a WriteProperty of
172 /// the encoded command would.
173 ///
174 /// The command is checked against its operation (Clause 12.54, Table
175 /// 12-67). NONE, a reserved operation (11 to 255) and anything past
176 /// 65,535 are refused; FADE_TO and RAMP_TO need a target level. A field
177 /// the operation uses must be in range: target level 0.0 to 100.0, fade
178 /// time 100 to 86,400,000 ms, ramp rate and step increment 0.1 to 100.0,
179 /// priority 1 to 16. Fields it doesn't use are kept unchecked, and a
180 /// proprietary operation (256 to 65,535) has only its priority checked. A
181 /// refusal is VALUE_OUT_OF_RANGE and leaves the object unchanged.
182 ///
183 /// A command taken is stored as written and carried out at its priority,
184 /// or at Lighting_Command_Default_Priority when it has none. A level it
185 /// puts in a priority slot is normalized as a commanded Present_Value is
186 /// (Clause 12.54.4), so a FADE_TO 0.5 fades to 1.0 while Lighting_Command
187 /// still reads 0.5. A proprietary operation is stored and does nothing
188 /// else.
189 pub fn set_lighting_command(&mut self, command: BACnetLightingCommand) -> Result<(), Error> {
190 command::check(&command)?;
191 self.take_lighting_command(command);
192 Ok(())
193 }
194
195 /// Store a checked command and carry it out now.
196 fn take_lighting_command(&mut self, command: BACnetLightingCommand) {
197 self.lighting_command = command;
198 let now = self.now();
199 self.execute(&command, now);
200 }
201
202 /// Link the output to the colour objects that set its colour, or take
203 /// the link away with `None` (#1527, Addendum 135-2020ca part 4).
204 ///
205 /// A link serves Color_Reference, and with a [`ColorOverride`] also
206 /// Color_Override and Override_Color_Reference; all of them take
207 /// writes. Each reference must name a colour object, Color or Color
208 /// Temperature (instance 4194303 names none); anything else is refused
209 /// with VALUE_OUT_OF_RANGE and changes nothing. The object only stores
210 /// the references:
211 /// [`ObjectDatabase::lighting_color`](crate::database::ObjectDatabase::lighting_color)
212 /// follows them.
213 pub fn set_color_link(&mut self, link: Option<ColorLink>) -> Result<(), Error> {
214 self.color_link = link.map(ColorLink::checked).transpose()?;
215 Ok(())
216 }
217
218 /// The colour link, if one is set.
219 pub fn color_link(&self) -> Option<&ColorLink> {
220 self.color_link.as_ref()
221 }
222
223 /// Build with application-owned Tags storage. Saved Tags override configured
224 /// Tags only while `set_profile` provisions the row. Loaded data must satisfy
225 /// the normal Tags rules and the opt-in 1 MiB snapshot limit.
226 pub fn with_tags_persistence(
227 instance: u32,
228 name: impl Into<String>,
229 persistence: Arc<dyn TagsPersistence>,
230 ) -> Result<Self, Error> {
231 let mut object = Self::new(instance, name)?;
232 object.profile = ProfileState::persistent(object.oid, persistence)?;
233 Ok(object)
234 }
235
236 /// Wait for queued save attempts to finish. This is not a success receipt;
237 /// writes report their own save outcomes. Unstaged local writes block.
238 pub fn wait_for_tag_saves(&self) {
239 self.profile.wait_for_saves();
240 }
241
242 /// Provision the optional Tags, Profile_Location and Profile_Name rows
243 /// before registration (#1553), as a Color object's
244 /// [`set_profile`](crate::color::ColorObject::set_profile) does.
245 pub fn set_profile(&mut self, profile: ObjectProfile) -> Result<(), Error> {
246 self.profile.provision(profile)
247 }
248
249 /// Set Default_Fade_Time, the milliseconds a fade request without its own
250 /// fade time takes. A new object uses 100, the shortest fade the clause
251 /// allows, as Default_Ramp_Rate starts at its fastest rate.
252 ///
253 /// Clause 12.54.16 bounds it to 100..=86_400_000 (one day); a value
254 /// outside that range is refused with VALUE_OUT_OF_RANGE and the property
255 /// is left unchanged. WriteProperty applies the same check.
256 pub fn set_default_fade_time(&mut self, milliseconds: u32) -> Result<(), Error> {
257 if !DEFAULT_FADE_TIME_MS.contains(&milliseconds) {
258 return Err(common::value_out_of_range_error());
259 }
260 self.default_fade_time = milliseconds;
261 Ok(())
262 }
263
264 /// Set Default_Ramp_Rate, the percent-per-second rate a ramp request
265 /// without its own rate uses. A new object uses 100.0.
266 ///
267 /// Clause 12.54.17 bounds it to 0.1..=100.0; a value outside that range,
268 /// or a non-finite one, is refused with VALUE_OUT_OF_RANGE and the
269 /// property is left unchanged. WriteProperty applies the same check.
270 pub fn set_default_ramp_rate(&mut self, value: f32) -> Result<(), Error> {
271 self.default_ramp_rate = lighting_percent(value)?;
272 Ok(())
273 }
274
275 /// Set Default_Step_Increment, the percent a step request without its own
276 /// increment adds. A new object uses 1.0.
277 ///
278 /// Clause 12.54.18 bounds it to 0.1..=100.0; a value outside that range,
279 /// or a non-finite one, is refused with VALUE_OUT_OF_RANGE and the
280 /// property is left unchanged. WriteProperty applies the same check.
281 pub fn set_default_step_increment(&mut self, value: f32) -> Result<(), Error> {
282 self.default_step_increment = lighting_percent(value)?;
283 Ok(())
284 }
285}
286
287/// Check a light level written to Present_Value or Relinquish_Default.
288///
289/// Both are percentages on the Clause 12.54 normalized scale: 0.0 is off,
290/// 1.0 is the dimmest on level and 100.0 the brightest, and no level lies
291/// strictly between 0.0 and 1.0. Clause 12.54.4 has a Present_Value write in
292/// that gap taken as 1.0, so such a level comes back as 1.0. 0.0 and levels
293/// from 1.0 to 100.0 come back unchanged. A level below 0.0 or above 100.0,
294/// NaN included, is VALUE_OUT_OF_RANGE; a Present_Value write tells the
295/// blink-warn values -1.0 to -3.0 apart before it gets here. -0.0 is off, so
296/// it comes back as 0.0 rather than keeping its sign on the wire.
297fn normalized_level(value: f32) -> Result<f32, Error> {
298 if !(0.0..=100.0).contains(&value) {
299 return Err(common::value_out_of_range_error());
300 }
301 Ok(if value == 0.0 {
302 0.0
303 } else if value < 1.0 {
304 1.0
305 } else {
306 value
307 })
308}
309
310/// The Default_Fade_Time range of Clause 12.54.16, in milliseconds, which a
311/// lighting command's fade time shares (Table 12-66).
312const DEFAULT_FADE_TIME_MS: std::ops::RangeInclusive<u32> = 100..=86_400_000;
313
314/// Check a Default_Ramp_Rate or Default_Step_Increment value, or a lighting
315/// command's ramp rate or step increment, which share the 0.1..=100.0 range.
316fn lighting_percent(value: f32) -> Result<f32, Error> {
317 if (0.1..=100.0).contains(&value) {
318 Ok(value)
319 } else {
320 Err(common::value_out_of_range_error())
321 }
322}
323
324impl BACnetObject for LightingOutputObject {
325 fn object_identifier(&self) -> ObjectIdentifier {
326 self.oid
327 }
328
329 fn object_name(&self) -> &str {
330 &self.name
331 }
332
333 fn read_property(
334 &self,
335 property: PropertyIdentifier,
336 array_index: Option<u32>,
337 ) -> Result<PropertyValue, Error> {
338 if let Some(result) = read_common_properties!(self, property, array_index) {
339 return result;
340 }
341 match property {
342 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
343 ObjectType::LIGHTING_OUTPUT.to_raw(),
344 )),
345 p if p == PropertyIdentifier::PRESENT_VALUE => {
346 Ok(PropertyValue::Real(self.present_value))
347 }
348 // Each read takes its own instant, so one ReadPropertyMultiple
349 // that straddles a fade's end may pair a Tracking_Value just short
350 // of the level with In_Progress IDLE.
351 p if p == PropertyIdentifier::TRACKING_VALUE => {
352 Ok(PropertyValue::Real(self.tracking_value_at(self.now())))
353 }
354 p if p == PropertyIdentifier::LIGHTING_COMMAND => {
355 Ok(command::encode(&self.lighting_command))
356 }
357 p if p == PropertyIdentifier::LIGHTING_COMMAND_DEFAULT_PRIORITY => Ok(
358 PropertyValue::Unsigned(self.lighting_command_default_priority as u64),
359 ),
360 p if p == PropertyIdentifier::IN_PROGRESS => Ok(PropertyValue::Enumerated(
361 self.in_progress_at(self.now()).to_raw(),
362 )),
363 p if p == PropertyIdentifier::BLINK_WARN_ENABLE => {
364 Ok(PropertyValue::Boolean(self.blink_warn_enable))
365 }
366 p if p == PropertyIdentifier::EGRESS_TIME => {
367 Ok(PropertyValue::Unsigned(self.egress_time as u64))
368 }
369 p if p == PropertyIdentifier::EGRESS_ACTIVE => {
370 Ok(PropertyValue::Boolean(self.egress_active()))
371 }
372 p if p == PropertyIdentifier::PRIORITY_ARRAY => {
373 read_priority_array!(self, array_index, PropertyValue::Real)
374 }
375 p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
376 Ok(PropertyValue::Real(self.relinquish_default))
377 }
378 p if p == PropertyIdentifier::DEFAULT_FADE_TIME => {
379 Ok(PropertyValue::Unsigned(u64::from(self.default_fade_time)))
380 }
381 p if p == PropertyIdentifier::DEFAULT_RAMP_RATE => {
382 Ok(PropertyValue::Real(self.default_ramp_rate))
383 }
384 p if p == PropertyIdentifier::DEFAULT_STEP_INCREMENT => {
385 Ok(PropertyValue::Real(self.default_step_increment))
386 }
387 p if p == PropertyIdentifier::COV_INCREMENT => {
388 Ok(PropertyValue::Real(self.cov_increment))
389 }
390 p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
391 Ok(common::current_command_priority(&self.priority_array))
392 }
393 p => color_link::read(self.color_link.as_ref(), p)
394 .or_else(|| self.read_trim(p))
395 .or_else(|| self.profile.read(p, array_index))
396 .unwrap_or_else(|| Err(common::unknown_property_error())),
397 }
398 }
399
400 fn write_property(
401 &mut self,
402 property: PropertyIdentifier,
403 array_index: Option<u32>,
404 value: PropertyValue,
405 priority: Option<u8>,
406 ) -> Result<(), Error> {
407 // A level is normalized before it reaches the slot, so the slot,
408 // Present_Value, Tracking_Value and a COV report all see 1.0 for a
409 // write between 0.0 and 1.0. -1.0, -2.0 and -3.0 are the warn
410 // commands (Table 12-65) and never reach a slot themselves.
411 if property == PropertyIdentifier::PRESENT_VALUE {
412 let priority = priority.unwrap_or(16);
413 if !(1..=16).contains(&priority) {
414 return Err(common::value_out_of_range_error());
415 }
416 let write = engine::PresentValueWrite::decode(value)?;
417 let now = self.now();
418 self.write_present_value(priority, write, now);
419 return Ok(());
420 }
421
422 // LIGHTING_COMMAND: a BACnetLightingCommand, checked against its
423 // operation, then carried out.
424 if property == PropertyIdentifier::LIGHTING_COMMAND {
425 let command = command::decode_write(value)?;
426 self.take_lighting_command(command);
427 return Ok(());
428 }
429
430 // LIGHTING_COMMAND_DEFAULT_PRIORITY: 1 to 16, but not 6. Clause
431 // 12.54.27 keeps 6 out: Table 19-1 gives that slot to Minimum On/Off,
432 // and a command written without a priority acts at this one.
433 if property == PropertyIdentifier::LIGHTING_COMMAND_DEFAULT_PRIORITY {
434 if let PropertyValue::Unsigned(v) = value {
435 if !(1..=16).contains(&v) || v == 6 {
436 return Err(common::value_out_of_range_error());
437 }
438 self.lighting_command_default_priority = v as u32;
439 return Ok(());
440 }
441 return Err(common::invalid_data_type_error());
442 }
443
444 // RELINQUISH_DEFAULT — writable per Table 12-64 (R; the standard
445 // permits writability), validated the same way a commanded
446 // Present_Value is by the shared setter.
447 if property == PropertyIdentifier::RELINQUISH_DEFAULT {
448 if let PropertyValue::Real(f) = value {
449 return self.set_relinquish_default(f);
450 }
451 return Err(common::invalid_data_type_error());
452 }
453
454 // BLINK_WARN_ENABLE
455 if property == PropertyIdentifier::BLINK_WARN_ENABLE {
456 if let PropertyValue::Boolean(v) = value {
457 self.blink_warn_enable = v;
458 return Ok(());
459 }
460 return Err(common::invalid_data_type_error());
461 }
462
463 // EGRESS_TIME
464 if property == PropertyIdentifier::EGRESS_TIME {
465 if let PropertyValue::Unsigned(v) = value {
466 self.egress_time = common::u64_to_u32(v)?;
467 return Ok(());
468 }
469 return Err(common::invalid_data_type_error());
470 }
471
472 // DEFAULT_FADE_TIME, DEFAULT_RAMP_RATE and DEFAULT_STEP_INCREMENT go
473 // through the range-checked setters. A fade time too large for the
474 // setter's u32 is past the range too.
475 if property == PropertyIdentifier::DEFAULT_FADE_TIME {
476 if let PropertyValue::Unsigned(v) = value {
477 return self.set_default_fade_time(common::u64_to_u32(v)?);
478 }
479 return Err(common::invalid_data_type_error());
480 }
481 if property == PropertyIdentifier::DEFAULT_RAMP_RATE {
482 if let PropertyValue::Real(v) = value {
483 return self.set_default_ramp_rate(v);
484 }
485 return Err(common::invalid_data_type_error());
486 }
487 if property == PropertyIdentifier::DEFAULT_STEP_INCREMENT {
488 if let PropertyValue::Real(v) = value {
489 return self.set_default_step_increment(v);
490 }
491 return Err(common::invalid_data_type_error());
492 }
493
494 // HIGH_END_TRIM, LOW_END_TRIM and TRIM_FADE_TIME, and the colour
495 // links, once present.
496 if let Some(result) = self.write_trim(property, &value) {
497 return result;
498 }
499 if let Some(result) = color_link::write(&mut self.color_link, property, &value) {
500 return result;
501 }
502 if let Some(result) = self.profile.write(property, array_index, &value) {
503 return result;
504 }
505 if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
506 {
507 return result;
508 }
509 if let Some(result) =
510 common::write_out_of_service(&mut self.out_of_service, property, &value)
511 {
512 return result;
513 }
514 if let Some(result) = common::write_description(&mut self.description, property, &value) {
515 return result;
516 }
517 Err(crate::common::unhandled_write_error(
518 self.property_metadata().as_ref(),
519 property,
520 array_index,
521 ))
522 }
523
524 fn durable_writes_internal(&mut self) -> Option<&mut dyn crate::durable::DurableWrites> {
525 self.profile.capability()
526 }
527
528 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
529 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
530 }
531
532 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
533 metadata::for_lighting_output_object(self)
534 }
535
536 fn cov_increment(&self) -> Option<f64> {
537 Some(f64::from(self.cov_increment))
538 }
539
540 fn supports_cov(&self) -> bool {
541 true
542 }
543
544 fn advance_time_internal(&mut self, elapsed: Duration) -> bool {
545 self.logical_now = self.logical_now.saturating_add(elapsed);
546 self.advance_to(self.logical_now)
547 }
548
549 fn bind_monotonic_clock_internal(&mut self, clock: Option<Arc<MonotonicClock>>) {
550 self.monotonic_clock = clock;
551 }
552
553 fn bind_deadline_waker_internal(&mut self, waker: Option<Arc<DeadlineWaker>>) {
554 self.deadline_waker = waker;
555 }
556
557 fn advance_monotonic_time_internal(&mut self, now: Duration) -> bool {
558 self.profile.expire(now);
559 self.advance_to(now)
560 }
561
562 fn next_monotonic_deadline_internal(&self) -> Option<Duration> {
563 self.next_deadline()
564 }
565
566 fn set_tracking_cov_increment_internal(&mut self, finest: Option<f64>) {
567 self.finest_tracking_increment = finest;
568 }
569
570 /// A copy that reads as the object does now: its clock stops at this
571 /// instant, so a fade's Tracking_Value in a COV report is the value at
572 /// the moment the report was taken.
573 fn cov_snapshot_internal(&self) -> Option<Box<dyn BACnetObject>> {
574 let mut snapshot = self.clone();
575 snapshot.logical_now = self.now();
576 snapshot.monotonic_clock = None;
577 snapshot.deadline_waker = None;
578 Some(Box::new(snapshot))
579 }
580
581 fn lighting_blink_count_internal(&self) -> u64 {
582 self.blink_request_count
583 }
584}
585
586mod binary;
587mod color_link;
588mod command;
589mod engine;
590mod metadata;
591mod trim;
592pub use binary::BinaryLightingOutputObject;
593pub use color_link::{ColorLink, ColorOverride, LightingColor, OutputColor};
594
595// ---------------------------------------------------------------------------
596// Tests
597// ---------------------------------------------------------------------------
598
599#[cfg(test)]
600mod tests;
601
602#[cfg(test)]
603mod required_rows_tests;
604
605#[cfg(test)]
606mod command_tests;
607
608#[cfg(test)]
609mod present_value_tests;
610
611#[cfg(test)]
612mod engine_tests;
613
614#[cfg(test)]
615mod warn_tests;
616
617#[cfg(test)]
618mod trim_tests;
619
620#[cfg(test)]
621mod color_link_tests;