1use 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::traits::{BACnetObject, DeadlineWaker, MonotonicClock};
14
15#[derive(Clone)]
40pub struct LightingOutputObject {
41 oid: ObjectIdentifier,
42 name: String,
43 description: String,
44 present_value: f32,
45 lighting_command: BACnetLightingCommand,
47 lighting_command_default_priority: u32,
48 operation: Option<engine::Operation>,
50 blink_request_count: u64,
52 blink_warn_enable: bool,
53 egress_time: u32,
55 default_fade_time: u32,
57 default_ramp_rate: f32,
59 default_step_increment: f32,
61 cov_increment: f32,
63 out_of_service: bool,
64 status_flags: StatusFlags,
65 reliability: Reliability,
67 priority_array: [Option<f32>; 16],
68 relinquish_default: f32,
69 monotonic_clock: Option<Arc<MonotonicClock>>,
70 deadline_waker: Option<Arc<DeadlineWaker>>,
71 logical_now: Duration,
73}
74
75impl LightingOutputObject {
76 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
78 let oid = ObjectIdentifier::new(ObjectType::LIGHTING_OUTPUT, instance)?;
79 Ok(Self {
80 oid,
81 name: name.into(),
82 description: String::new(),
83 present_value: 0.0,
84 lighting_command: BACnetLightingCommand::new(LightingOperation::NONE),
85 lighting_command_default_priority: 16,
86 operation: None,
87 blink_request_count: 0,
88 blink_warn_enable: false,
89 egress_time: 0,
90 default_fade_time: *DEFAULT_FADE_TIME_MS.start(),
91 default_ramp_rate: 100.0,
92 default_step_increment: 1.0,
93 cov_increment: 0.0,
94 out_of_service: false,
95 status_flags: StatusFlags::empty(),
96 reliability: Reliability::NO_FAULT_DETECTED,
97 priority_array: [None; 16],
98 relinquish_default: 0.0,
99 monotonic_clock: None,
100 deadline_waker: None,
101 logical_now: Duration::ZERO,
102 })
103 }
104
105 pub fn set_description(&mut self, desc: impl Into<String>) {
107 self.description = desc.into();
108 }
109
110 fn recalculate_present_value(&mut self) {
117 self.present_value =
118 common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
119 }
120
121 pub fn set_relinquish_default(&mut self, value: f32) -> Result<(), Error> {
130 self.relinquish_default = normalized_level(value)?;
131 self.recalculate_present_value();
132 Ok(())
133 }
134
135 pub fn lighting_command(&self) -> BACnetLightingCommand {
138 self.lighting_command
139 }
140
141 pub fn set_lighting_command(&mut self, command: BACnetLightingCommand) -> Result<(), Error> {
160 command::check(&command)?;
161 self.take_lighting_command(command);
162 Ok(())
163 }
164
165 fn take_lighting_command(&mut self, command: BACnetLightingCommand) {
167 self.lighting_command = command;
168 let now = self.now();
169 self.execute(&command, now);
170 }
171
172 pub fn set_default_fade_time(&mut self, milliseconds: u32) -> Result<(), Error> {
180 if !DEFAULT_FADE_TIME_MS.contains(&milliseconds) {
181 return Err(common::value_out_of_range_error());
182 }
183 self.default_fade_time = milliseconds;
184 Ok(())
185 }
186
187 pub fn set_default_ramp_rate(&mut self, value: f32) -> Result<(), Error> {
194 self.default_ramp_rate = lighting_percent(value)?;
195 Ok(())
196 }
197
198 pub fn set_default_step_increment(&mut self, value: f32) -> Result<(), Error> {
205 self.default_step_increment = lighting_percent(value)?;
206 Ok(())
207 }
208}
209
210fn normalized_level(value: f32) -> Result<f32, Error> {
221 if !(0.0..=100.0).contains(&value) {
222 return Err(common::value_out_of_range_error());
223 }
224 Ok(if value == 0.0 {
225 0.0
226 } else if value < 1.0 {
227 1.0
228 } else {
229 value
230 })
231}
232
233const DEFAULT_FADE_TIME_MS: std::ops::RangeInclusive<u32> = 100..=86_400_000;
236
237fn lighting_percent(value: f32) -> Result<f32, Error> {
240 if (0.1..=100.0).contains(&value) {
241 Ok(value)
242 } else {
243 Err(common::value_out_of_range_error())
244 }
245}
246
247impl BACnetObject for LightingOutputObject {
248 fn object_identifier(&self) -> ObjectIdentifier {
249 self.oid
250 }
251
252 fn object_name(&self) -> &str {
253 &self.name
254 }
255
256 fn read_property(
257 &self,
258 property: PropertyIdentifier,
259 array_index: Option<u32>,
260 ) -> Result<PropertyValue, Error> {
261 if let Some(result) = read_common_properties!(self, property, array_index) {
262 return result;
263 }
264 match property {
265 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
266 ObjectType::LIGHTING_OUTPUT.to_raw(),
267 )),
268 p if p == PropertyIdentifier::PRESENT_VALUE => {
269 Ok(PropertyValue::Real(self.present_value))
270 }
271 p if p == PropertyIdentifier::TRACKING_VALUE => {
275 Ok(PropertyValue::Real(self.tracking_value_at(self.now())))
276 }
277 p if p == PropertyIdentifier::LIGHTING_COMMAND => {
278 Ok(command::encode(&self.lighting_command))
279 }
280 p if p == PropertyIdentifier::LIGHTING_COMMAND_DEFAULT_PRIORITY => Ok(
281 PropertyValue::Unsigned(self.lighting_command_default_priority as u64),
282 ),
283 p if p == PropertyIdentifier::IN_PROGRESS => Ok(PropertyValue::Enumerated(
284 self.in_progress_at(self.now()).to_raw(),
285 )),
286 p if p == PropertyIdentifier::BLINK_WARN_ENABLE => {
287 Ok(PropertyValue::Boolean(self.blink_warn_enable))
288 }
289 p if p == PropertyIdentifier::EGRESS_TIME => {
290 Ok(PropertyValue::Unsigned(self.egress_time as u64))
291 }
292 p if p == PropertyIdentifier::EGRESS_ACTIVE => {
293 Ok(PropertyValue::Boolean(self.egress_active()))
294 }
295 p if p == PropertyIdentifier::PRIORITY_ARRAY => {
296 read_priority_array!(self, array_index, PropertyValue::Real)
297 }
298 p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
299 Ok(PropertyValue::Real(self.relinquish_default))
300 }
301 p if p == PropertyIdentifier::DEFAULT_FADE_TIME => {
302 Ok(PropertyValue::Unsigned(u64::from(self.default_fade_time)))
303 }
304 p if p == PropertyIdentifier::DEFAULT_RAMP_RATE => {
305 Ok(PropertyValue::Real(self.default_ramp_rate))
306 }
307 p if p == PropertyIdentifier::DEFAULT_STEP_INCREMENT => {
308 Ok(PropertyValue::Real(self.default_step_increment))
309 }
310 p if p == PropertyIdentifier::COV_INCREMENT => {
311 Ok(PropertyValue::Real(self.cov_increment))
312 }
313 p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
314 Ok(common::current_command_priority(&self.priority_array))
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 property == PropertyIdentifier::PRESENT_VALUE {
332 let priority = priority.unwrap_or(16);
333 if !(1..=16).contains(&priority) {
334 return Err(common::value_out_of_range_error());
335 }
336 let write = engine::PresentValueWrite::decode(value)?;
337 let now = self.now();
338 self.write_present_value(priority, write, now);
339 return Ok(());
340 }
341
342 if property == PropertyIdentifier::LIGHTING_COMMAND {
345 let command = command::decode_write(value)?;
346 self.take_lighting_command(command);
347 return Ok(());
348 }
349
350 if property == PropertyIdentifier::LIGHTING_COMMAND_DEFAULT_PRIORITY {
354 if let PropertyValue::Unsigned(v) = value {
355 if !(1..=16).contains(&v) || v == 6 {
356 return Err(common::value_out_of_range_error());
357 }
358 self.lighting_command_default_priority = v as u32;
359 return Ok(());
360 }
361 return Err(common::invalid_data_type_error());
362 }
363
364 if property == PropertyIdentifier::RELINQUISH_DEFAULT {
368 if let PropertyValue::Real(f) = value {
369 return self.set_relinquish_default(f);
370 }
371 return Err(common::invalid_data_type_error());
372 }
373
374 if property == PropertyIdentifier::BLINK_WARN_ENABLE {
376 if let PropertyValue::Boolean(v) = value {
377 self.blink_warn_enable = v;
378 return Ok(());
379 }
380 return Err(common::invalid_data_type_error());
381 }
382
383 if property == PropertyIdentifier::EGRESS_TIME {
385 if let PropertyValue::Unsigned(v) = value {
386 self.egress_time = common::u64_to_u32(v)?;
387 return Ok(());
388 }
389 return Err(common::invalid_data_type_error());
390 }
391
392 if property == PropertyIdentifier::DEFAULT_FADE_TIME {
396 if let PropertyValue::Unsigned(v) = value {
397 return self.set_default_fade_time(common::u64_to_u32(v)?);
398 }
399 return Err(common::invalid_data_type_error());
400 }
401 if property == PropertyIdentifier::DEFAULT_RAMP_RATE {
402 if let PropertyValue::Real(v) = value {
403 return self.set_default_ramp_rate(v);
404 }
405 return Err(common::invalid_data_type_error());
406 }
407 if property == PropertyIdentifier::DEFAULT_STEP_INCREMENT {
408 if let PropertyValue::Real(v) = value {
409 return self.set_default_step_increment(v);
410 }
411 return Err(common::invalid_data_type_error());
412 }
413
414 if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
415 {
416 return result;
417 }
418 if let Some(result) =
419 common::write_out_of_service(&mut self.out_of_service, property, &value)
420 {
421 return result;
422 }
423 if let Some(result) = common::write_description(&mut self.description, property, &value) {
424 return result;
425 }
426 Err(crate::common::unhandled_write_error(
427 self.property_metadata().as_ref(),
428 property,
429 _array_index,
430 ))
431 }
432
433 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
434 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
435 }
436
437 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
438 metadata::for_lighting_output_object(self)
439 }
440
441 fn cov_increment(&self) -> Option<f64> {
442 Some(f64::from(self.cov_increment))
443 }
444
445 fn supports_cov(&self) -> bool {
446 true
447 }
448
449 fn advance_time_internal(&mut self, elapsed: Duration) -> bool {
450 self.logical_now = self.logical_now.saturating_add(elapsed);
451 self.advance_to(self.logical_now)
452 }
453
454 fn bind_monotonic_clock_internal(&mut self, clock: Option<Arc<MonotonicClock>>) {
455 self.monotonic_clock = clock;
456 }
457
458 fn bind_deadline_waker_internal(&mut self, waker: Option<Arc<DeadlineWaker>>) {
459 self.deadline_waker = waker;
460 }
461
462 fn advance_monotonic_time_internal(&mut self, now: Duration) -> bool {
463 self.advance_to(now)
464 }
465
466 fn next_monotonic_deadline_internal(&self) -> Option<Duration> {
467 self.next_deadline()
468 }
469
470 fn cov_snapshot_internal(&self) -> Option<Box<dyn BACnetObject>> {
474 let mut snapshot = self.clone();
475 snapshot.logical_now = self.now();
476 snapshot.monotonic_clock = None;
477 snapshot.deadline_waker = None;
478 Some(Box::new(snapshot))
479 }
480
481 fn lighting_blink_count_internal(&self) -> u64 {
482 self.blink_request_count
483 }
484}
485
486mod binary;
487mod command;
488mod engine;
489mod metadata;
490pub use binary::BinaryLightingOutputObject;
491
492#[cfg(test)]
497mod tests;
498
499#[cfg(test)]
500mod required_rows_tests;
501
502#[cfg(test)]
503mod command_tests;
504
505#[cfg(test)]
506mod present_value_tests;
507
508#[cfg(test)]
509mod engine_tests;
510
511#[cfg(test)]
512mod warn_tests;