1use bacnet_types::enums::{
5 ErrorClass, ErrorCode, LifeSafetyOperation, ObjectType, PropertyIdentifier, SilencedState,
6};
7use bacnet_types::error::Error;
8use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
9use std::borrow::Cow;
10
11use crate::common::{self, read_common_properties};
12use crate::traits::{BACnetObject, LifeSafetyOperationEffect, LifeSafetyOperationOutcome};
13
14mod reset;
15
16pub use reset::{
17 LifeSafetyPointResetCommit, LifeSafetyPointResetContext, LifeSafetyPointResetExecutor,
18 LifeSafetyResetError, LifeSafetyZoneResetCommit, LifeSafetyZoneResetContext,
19 LifeSafetyZoneResetExecutor,
20};
21
22fn life_safety_error(code: ErrorCode) -> Error {
23 Error::Protocol {
24 class: ErrorClass::OBJECT.to_raw() as u32,
25 code: code.to_raw() as u32,
26 }
27}
28
29fn apply_silenced_operation(
30 silenced: &mut u32,
31 operation_expected: &mut u32,
32 operation: LifeSafetyOperation,
33) -> Result<LifeSafetyOperationEffect, Error> {
34 let current = *silenced;
35 if current > SilencedState::ALL_SILENCED.to_raw() {
36 return Err(life_safety_error(
37 ErrorCode::INVALID_OPERATION_IN_THIS_STATE,
38 ));
39 }
40
41 let desired = if operation == LifeSafetyOperation::SILENCE {
42 SilencedState::ALL_SILENCED.to_raw()
43 } else if operation == LifeSafetyOperation::SILENCE_AUDIBLE {
44 current | SilencedState::AUDIBLE_SILENCED.to_raw()
45 } else if operation == LifeSafetyOperation::SILENCE_VISUAL {
46 current | SilencedState::VISIBLE_SILENCED.to_raw()
47 } else if operation == LifeSafetyOperation::UNSILENCE {
48 SilencedState::UNSILENCED.to_raw()
49 } else if operation == LifeSafetyOperation::UNSILENCE_AUDIBLE {
50 current & !SilencedState::AUDIBLE_SILENCED.to_raw()
51 } else if operation == LifeSafetyOperation::UNSILENCE_VISUAL {
52 current & !SilencedState::VISIBLE_SILENCED.to_raw()
53 } else {
54 return Err(life_safety_error(ErrorCode::VALUE_OUT_OF_RANGE));
55 };
56
57 if *operation_expected != operation.to_raw() {
58 return Err(life_safety_error(
59 ErrorCode::INVALID_OPERATION_IN_THIS_STATE,
60 ));
61 }
62
63 *silenced = desired;
64 *operation_expected = LifeSafetyOperation::NONE.to_raw();
65 Ok(LifeSafetyOperationEffect::Applied)
66}
67
68const POINT_COV_PROPERTIES: [PropertyIdentifier; 5] = [
69 PropertyIdentifier::PRESENT_VALUE,
70 PropertyIdentifier::TRACKING_VALUE,
71 PropertyIdentifier::SILENCED,
72 PropertyIdentifier::OPERATION_EXPECTED,
73 PropertyIdentifier::STATUS_FLAGS,
74];
75
76const ZONE_COV_PROPERTIES: [PropertyIdentifier; 4] = [
77 PropertyIdentifier::PRESENT_VALUE,
78 PropertyIdentifier::SILENCED,
79 PropertyIdentifier::OPERATION_EXPECTED,
80 PropertyIdentifier::STATUS_FLAGS,
81];
82
83fn operation_outcome(
84 object: &dyn BACnetObject,
85 before: Vec<(PropertyIdentifier, PropertyValue)>,
86 effect: LifeSafetyOperationEffect,
87) -> LifeSafetyOperationOutcome {
88 let changed_properties = before
89 .into_iter()
90 .filter_map(|(property, previous)| {
91 object
92 .read_property(property, None)
93 .is_ok_and(|current| current != previous)
94 .then_some(property)
95 })
96 .collect();
97 LifeSafetyOperationOutcome {
98 effect,
99 changed_properties,
100 }
101}
102
103pub struct LifeSafetyPointObject {
113 oid: ObjectIdentifier,
114 name: String,
115 description: String,
116 present_value: u32,
118 mode: u32,
120 silenced: u32,
122 operation_expected: u32,
124 tracking_value: u32,
126 member_of: Vec<ObjectIdentifier>,
128 direct_reading: f32,
130 maintenance_required: bool,
132 event_state: u32,
134 status_flags: StatusFlags,
135 out_of_service: bool,
136 reliability: u32,
138 reset_executor: Option<LifeSafetyPointResetExecutor>,
140}
141
142impl LifeSafetyPointObject {
143 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
148 let oid = ObjectIdentifier::new(ObjectType::LIFE_SAFETY_POINT, instance)?;
149 Ok(Self {
150 oid,
151 name: name.into(),
152 description: String::new(),
153 present_value: 0, mode: 0, silenced: 0, operation_expected: 0, tracking_value: 0, member_of: Vec::new(),
159 direct_reading: 0.0,
160 maintenance_required: false,
161 event_state: 0, status_flags: StatusFlags::empty(),
163 out_of_service: false,
164 reliability: 0,
165 reset_executor: None,
166 })
167 }
168
169 pub fn set_present_value(&mut self, state: u32) {
171 self.present_value = state;
172 }
173
174 pub fn set_mode(&mut self, mode: u32) {
176 self.mode = mode;
177 }
178
179 pub fn set_tracking_value(&mut self, state: u32) {
181 self.tracking_value = state;
182 }
183
184 pub fn set_silenced(&mut self, state: SilencedState) {
186 self.silenced = state.to_raw();
187 }
188
189 pub fn set_operation_expected(&mut self, operation: LifeSafetyOperation) {
191 self.operation_expected = operation.to_raw();
192 }
193
194 pub fn set_reset_executor(&mut self, executor: LifeSafetyPointResetExecutor) {
199 self.reset_executor = Some(executor);
200 }
201
202 pub fn with_reset_executor(mut self, executor: LifeSafetyPointResetExecutor) -> Self {
204 self.set_reset_executor(executor);
205 self
206 }
207
208 pub fn set_direct_reading(&mut self, value: f32) {
210 self.direct_reading = value;
211 }
212
213 pub fn set_description(&mut self, desc: impl Into<String>) {
215 self.description = desc.into();
216 }
217
218 pub fn add_member(&mut self, zone_oid: ObjectIdentifier) {
220 self.member_of.push(zone_oid);
221 }
222}
223
224impl BACnetObject for LifeSafetyPointObject {
225 fn object_identifier(&self) -> ObjectIdentifier {
226 self.oid
227 }
228
229 fn object_name(&self) -> &str {
230 &self.name
231 }
232
233 fn read_property(
234 &self,
235 property: PropertyIdentifier,
236 array_index: Option<u32>,
237 ) -> Result<PropertyValue, Error> {
238 if let Some(result) = read_common_properties!(self, property, array_index) {
239 return result;
240 }
241 match property {
242 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
243 ObjectType::LIFE_SAFETY_POINT.to_raw(),
244 )),
245 p if p == PropertyIdentifier::PRESENT_VALUE => {
246 Ok(PropertyValue::Enumerated(self.present_value))
247 }
248 p if p == PropertyIdentifier::MODE => Ok(PropertyValue::Enumerated(self.mode)),
249 p if p == PropertyIdentifier::SILENCED => Ok(PropertyValue::Enumerated(self.silenced)),
250 p if p == PropertyIdentifier::OPERATION_EXPECTED => {
251 Ok(PropertyValue::Enumerated(self.operation_expected))
252 }
253 p if p == PropertyIdentifier::TRACKING_VALUE => {
254 Ok(PropertyValue::Enumerated(self.tracking_value))
255 }
256 p if p == PropertyIdentifier::MEMBER_OF => Ok(PropertyValue::List(
257 self.member_of
258 .iter()
259 .map(|oid| PropertyValue::ObjectIdentifier(*oid))
260 .collect(),
261 )),
262 p if p == PropertyIdentifier::DIRECT_READING => {
263 Ok(PropertyValue::Real(self.direct_reading))
264 }
265 p if p == PropertyIdentifier::MAINTENANCE_REQUIRED => {
266 Ok(PropertyValue::Boolean(self.maintenance_required))
267 }
268 p if p == PropertyIdentifier::EVENT_STATE => {
269 Ok(PropertyValue::Enumerated(self.event_state))
270 }
271 _ => Err(common::unknown_property_error()),
272 }
273 }
274
275 fn write_property(
276 &mut self,
277 property: PropertyIdentifier,
278 _array_index: Option<u32>,
279 value: PropertyValue,
280 _priority: Option<u8>,
281 ) -> Result<(), Error> {
282 if property == PropertyIdentifier::PRESENT_VALUE {
284 return Err(common::write_access_denied_error());
285 }
286 if property == PropertyIdentifier::MODE {
287 if let PropertyValue::Enumerated(v) = value {
288 self.mode = v;
289 return Ok(());
290 }
291 return Err(common::invalid_data_type_error());
292 }
293 if property == PropertyIdentifier::SILENCED
294 || property == PropertyIdentifier::OPERATION_EXPECTED
295 {
296 return Err(common::write_access_denied_error());
297 }
298 if property == PropertyIdentifier::DIRECT_READING {
299 if let PropertyValue::Real(v) = value {
300 common::reject_non_finite(v)?;
301 self.direct_reading = v;
302 return Ok(());
303 }
304 return Err(common::invalid_data_type_error());
305 }
306 if property == PropertyIdentifier::MAINTENANCE_REQUIRED {
307 if let PropertyValue::Boolean(v) = value {
308 self.maintenance_required = v;
309 return Ok(());
310 }
311 return Err(common::invalid_data_type_error());
312 }
313 if let Some(result) =
314 common::write_out_of_service(&mut self.out_of_service, property, &value)
315 {
316 return result;
317 }
318 if let Some(result) = common::write_description(&mut self.description, property, &value) {
319 return result;
320 }
321 Err(common::write_access_denied_error())
322 }
323
324 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
325 static PROPS: &[PropertyIdentifier] = &[
326 PropertyIdentifier::OBJECT_IDENTIFIER,
327 PropertyIdentifier::OBJECT_NAME,
328 PropertyIdentifier::OBJECT_TYPE,
329 PropertyIdentifier::DESCRIPTION,
330 PropertyIdentifier::PRESENT_VALUE,
331 PropertyIdentifier::MODE,
332 PropertyIdentifier::SILENCED,
333 PropertyIdentifier::OPERATION_EXPECTED,
334 PropertyIdentifier::TRACKING_VALUE,
335 PropertyIdentifier::MEMBER_OF,
336 PropertyIdentifier::DIRECT_READING,
337 PropertyIdentifier::MAINTENANCE_REQUIRED,
338 PropertyIdentifier::EVENT_STATE,
339 PropertyIdentifier::STATUS_FLAGS,
340 PropertyIdentifier::OUT_OF_SERVICE,
341 PropertyIdentifier::RELIABILITY,
342 ];
343 Cow::Borrowed(PROPS)
344 }
345
346 fn supports_cov(&self) -> bool {
347 true
348 }
349
350 fn supports_cov_property(&self, property: PropertyIdentifier) -> bool {
351 POINT_COV_PROPERTIES.contains(&property)
352 }
353
354 fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
355 matches!(
356 property,
357 PropertyIdentifier::MODE
358 | PropertyIdentifier::DIRECT_READING
359 | PropertyIdentifier::MAINTENANCE_REQUIRED
360 | PropertyIdentifier::DESCRIPTION
361 | PropertyIdentifier::OUT_OF_SERVICE
362 )
363 }
364
365 fn apply_life_safety_operation(
366 &mut self,
367 operation: LifeSafetyOperation,
368 ) -> Result<LifeSafetyOperationEffect, Error> {
369 if reset::is_reset_operation(operation) {
370 self.apply_reset_operation(operation)
371 } else {
372 apply_silenced_operation(&mut self.silenced, &mut self.operation_expected, operation)
373 }
374 }
375
376 fn apply_life_safety_operation_detailed(
377 &mut self,
378 operation: LifeSafetyOperation,
379 ) -> Result<LifeSafetyOperationOutcome, Error> {
380 let before = POINT_COV_PROPERTIES
381 .into_iter()
382 .filter_map(|property| {
383 self.read_property(property, None)
384 .ok()
385 .map(|value| (property, value))
386 })
387 .collect();
388 let effect = self.apply_life_safety_operation(operation)?;
389 Ok(operation_outcome(self, before, effect))
390 }
391
392 fn set_life_safety_operation_expected_internal(
393 &mut self,
394 operation: LifeSafetyOperation,
395 ) -> Result<(), Error> {
396 self.set_operation_expected(operation);
397 Ok(())
398 }
399}
400
401pub struct LifeSafetyZoneObject {
411 oid: ObjectIdentifier,
412 name: String,
413 description: String,
414 present_value: u32,
416 mode: u32,
418 silenced: u32,
420 operation_expected: u32,
422 zone_members: Vec<ObjectIdentifier>,
424 event_state: u32,
426 status_flags: StatusFlags,
427 out_of_service: bool,
428 reliability: u32,
430 reset_executor: Option<LifeSafetyZoneResetExecutor>,
432}
433
434impl LifeSafetyZoneObject {
435 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
440 let oid = ObjectIdentifier::new(ObjectType::LIFE_SAFETY_ZONE, instance)?;
441 Ok(Self {
442 oid,
443 name: name.into(),
444 description: String::new(),
445 present_value: 0, mode: 0, silenced: 0, operation_expected: 0, zone_members: Vec::new(),
450 event_state: 0, status_flags: StatusFlags::empty(),
452 out_of_service: false,
453 reliability: 0,
454 reset_executor: None,
455 })
456 }
457
458 pub fn set_present_value(&mut self, state: u32) {
460 self.present_value = state;
461 }
462
463 pub fn set_mode(&mut self, mode: u32) {
465 self.mode = mode;
466 }
467
468 pub fn set_silenced(&mut self, state: SilencedState) {
470 self.silenced = state.to_raw();
471 }
472
473 pub fn set_operation_expected(&mut self, operation: LifeSafetyOperation) {
475 self.operation_expected = operation.to_raw();
476 }
477
478 pub fn set_reset_executor(&mut self, executor: LifeSafetyZoneResetExecutor) {
483 self.reset_executor = Some(executor);
484 }
485
486 pub fn with_reset_executor(mut self, executor: LifeSafetyZoneResetExecutor) -> Self {
488 self.set_reset_executor(executor);
489 self
490 }
491
492 pub fn set_description(&mut self, desc: impl Into<String>) {
494 self.description = desc.into();
495 }
496
497 pub fn add_zone_member(&mut self, point_oid: ObjectIdentifier) {
499 self.zone_members.push(point_oid);
500 }
501}
502
503impl BACnetObject for LifeSafetyZoneObject {
504 fn object_identifier(&self) -> ObjectIdentifier {
505 self.oid
506 }
507
508 fn object_name(&self) -> &str {
509 &self.name
510 }
511
512 fn read_property(
513 &self,
514 property: PropertyIdentifier,
515 array_index: Option<u32>,
516 ) -> Result<PropertyValue, Error> {
517 if let Some(result) = read_common_properties!(self, property, array_index) {
518 return result;
519 }
520 match property {
521 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
522 ObjectType::LIFE_SAFETY_ZONE.to_raw(),
523 )),
524 p if p == PropertyIdentifier::PRESENT_VALUE => {
525 Ok(PropertyValue::Enumerated(self.present_value))
526 }
527 p if p == PropertyIdentifier::MODE => Ok(PropertyValue::Enumerated(self.mode)),
528 p if p == PropertyIdentifier::SILENCED => Ok(PropertyValue::Enumerated(self.silenced)),
529 p if p == PropertyIdentifier::OPERATION_EXPECTED => {
530 Ok(PropertyValue::Enumerated(self.operation_expected))
531 }
532 p if p == PropertyIdentifier::ZONE_MEMBERS => Ok(PropertyValue::List(
533 self.zone_members
534 .iter()
535 .map(|oid| PropertyValue::ObjectIdentifier(*oid))
536 .collect(),
537 )),
538 p if p == PropertyIdentifier::EVENT_STATE => {
539 Ok(PropertyValue::Enumerated(self.event_state))
540 }
541 _ => Err(common::unknown_property_error()),
542 }
543 }
544
545 fn write_property(
546 &mut self,
547 property: PropertyIdentifier,
548 _array_index: Option<u32>,
549 value: PropertyValue,
550 _priority: Option<u8>,
551 ) -> Result<(), Error> {
552 if property == PropertyIdentifier::PRESENT_VALUE {
554 return Err(common::write_access_denied_error());
555 }
556 if property == PropertyIdentifier::MODE {
557 if let PropertyValue::Enumerated(v) = value {
558 self.mode = v;
559 return Ok(());
560 }
561 return Err(common::invalid_data_type_error());
562 }
563 if property == PropertyIdentifier::SILENCED
564 || property == PropertyIdentifier::OPERATION_EXPECTED
565 {
566 return Err(common::write_access_denied_error());
567 }
568 if let Some(result) =
569 common::write_out_of_service(&mut self.out_of_service, property, &value)
570 {
571 return result;
572 }
573 if let Some(result) = common::write_description(&mut self.description, property, &value) {
574 return result;
575 }
576 Err(common::write_access_denied_error())
577 }
578
579 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
580 static PROPS: &[PropertyIdentifier] = &[
581 PropertyIdentifier::OBJECT_IDENTIFIER,
582 PropertyIdentifier::OBJECT_NAME,
583 PropertyIdentifier::OBJECT_TYPE,
584 PropertyIdentifier::DESCRIPTION,
585 PropertyIdentifier::PRESENT_VALUE,
586 PropertyIdentifier::MODE,
587 PropertyIdentifier::SILENCED,
588 PropertyIdentifier::OPERATION_EXPECTED,
589 PropertyIdentifier::ZONE_MEMBERS,
590 PropertyIdentifier::EVENT_STATE,
591 PropertyIdentifier::STATUS_FLAGS,
592 PropertyIdentifier::OUT_OF_SERVICE,
593 PropertyIdentifier::RELIABILITY,
594 ];
595 Cow::Borrowed(PROPS)
596 }
597
598 fn supports_cov(&self) -> bool {
599 true
600 }
601
602 fn supports_cov_property(&self, property: PropertyIdentifier) -> bool {
603 ZONE_COV_PROPERTIES.contains(&property)
604 }
605
606 fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
607 matches!(
608 property,
609 PropertyIdentifier::MODE
610 | PropertyIdentifier::DESCRIPTION
611 | PropertyIdentifier::OUT_OF_SERVICE
612 )
613 }
614
615 fn apply_life_safety_operation(
616 &mut self,
617 operation: LifeSafetyOperation,
618 ) -> Result<LifeSafetyOperationEffect, Error> {
619 if reset::is_reset_operation(operation) {
620 self.apply_reset_operation(operation)
621 } else {
622 apply_silenced_operation(&mut self.silenced, &mut self.operation_expected, operation)
623 }
624 }
625
626 fn apply_life_safety_operation_detailed(
627 &mut self,
628 operation: LifeSafetyOperation,
629 ) -> Result<LifeSafetyOperationOutcome, Error> {
630 let before = ZONE_COV_PROPERTIES
631 .into_iter()
632 .filter_map(|property| {
633 self.read_property(property, None)
634 .ok()
635 .map(|value| (property, value))
636 })
637 .collect();
638 let effect = self.apply_life_safety_operation(operation)?;
639 Ok(operation_outcome(self, before, effect))
640 }
641
642 fn set_life_safety_operation_expected_internal(
643 &mut self,
644 operation: LifeSafetyOperation,
645 ) -> Result<(), Error> {
646 self.set_operation_expected(operation);
647 Ok(())
648 }
649}
650
651#[cfg(test)]
656mod tests;
657
658#[cfg(test)]
659mod reset_tests;