1use crate::object::{
7 event_state::EventState, reliability::Reliability, BacnetObject, ObjectError, ObjectIdentifier,
8 ObjectType, PropertyIdentifier, PropertyValue, Result,
9};
10
11#[cfg(not(feature = "std"))]
12use alloc::{string::String, vec::Vec};
13
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[repr(u32)]
20pub enum BinaryPV {
21 Inactive = 0,
22 Active = 1,
23}
24
25impl From<bool> for BinaryPV {
26 fn from(value: bool) -> Self {
27 if value {
28 BinaryPV::Active
29 } else {
30 BinaryPV::Inactive
31 }
32 }
33}
34
35impl From<BinaryPV> for bool {
36 fn from(value: BinaryPV) -> Self {
37 value == BinaryPV::Active
38 }
39}
40
41#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44#[repr(u32)]
45pub enum Polarity {
46 Normal = 0,
47 Reverse = 1,
48}
49
50impl TryFrom<u32> for Polarity {
51 type Error = ObjectError;
52
53 fn try_from(value: u32) -> Result<Self> {
54 match value {
55 0 => Ok(Polarity::Normal),
56 1 => Ok(Polarity::Reverse),
57 _ => Err(ObjectError::InvalidValue(
58 "Polarity must be 0 or 1".to_string(),
59 )),
60 }
61 }
62}
63
64#[derive(Debug, Clone)]
66pub struct BinaryInput {
67 pub identifier: ObjectIdentifier,
69 pub object_name: String,
71 pub present_value: BinaryPV,
73 pub description: String,
75 pub device_type: String,
77 pub status_flags: u8,
79 pub event_state: EventState,
81 pub reliability: Reliability,
83 pub out_of_service: bool,
85 pub polarity: Polarity,
87 pub inactive_text: String,
89 pub active_text: String,
91 pub change_of_state_time: Option<crate::object::Time>,
93 pub change_of_state_count: u32,
95 pub time_of_state_count_reset: Option<crate::object::Time>,
97}
98
99#[derive(Debug, Clone)]
101pub struct BinaryOutput {
102 pub identifier: ObjectIdentifier,
104 pub object_name: String,
106 pub present_value: BinaryPV,
108 pub description: String,
110 pub device_type: String,
112 pub status_flags: u8,
114 pub event_state: EventState,
116 pub reliability: Reliability,
118 pub out_of_service: bool,
120 pub polarity: Polarity,
122 pub inactive_text: String,
124 pub active_text: String,
126 pub priority_array: [Option<BinaryPV>; 16],
128 pub relinquish_default: BinaryPV,
130 pub minimum_off_time: u32,
132 pub minimum_on_time: u32,
134}
135
136#[derive(Debug, Clone)]
138pub struct BinaryValue {
139 pub identifier: ObjectIdentifier,
141 pub object_name: String,
143 pub present_value: BinaryPV,
145 pub description: String,
147 pub status_flags: u8,
149 pub event_state: EventState,
151 pub reliability: Reliability,
153 pub out_of_service: bool,
155 pub inactive_text: String,
157 pub active_text: String,
159 pub priority_array: [Option<BinaryPV>; 16],
161 pub relinquish_default: BinaryPV,
163}
164
165impl BinaryInput {
166 pub fn new(instance: u32, object_name: String) -> Self {
168 Self {
169 identifier: ObjectIdentifier::new(ObjectType::BinaryInput, instance),
170 object_name,
171 present_value: BinaryPV::Inactive,
172 description: String::new(),
173 device_type: String::new(),
174 status_flags: 0,
175 event_state: EventState::Normal,
176 reliability: Reliability::NoFaultDetected,
177 out_of_service: false,
178 polarity: Polarity::Normal,
179 inactive_text: "INACTIVE".to_string(),
180 active_text: "ACTIVE".to_string(),
181 change_of_state_time: None,
182 change_of_state_count: 0,
183 time_of_state_count_reset: None,
184 }
185 }
186
187 pub fn set_present_value(&mut self, value: BinaryPV) {
189 if value != self.present_value {
190 self.present_value = value;
191 self.change_of_state_count += 1;
192 }
194 }
195
196 pub fn get_status_flags(&self) -> (bool, bool, bool, bool) {
198 (
199 (self.status_flags & 0x08) != 0, (self.status_flags & 0x04) != 0, (self.status_flags & 0x02) != 0, (self.status_flags & 0x01) != 0, )
204 }
205
206 pub fn set_status_flags(
208 &mut self,
209 in_alarm: bool,
210 fault: bool,
211 overridden: bool,
212 out_of_service: bool,
213 ) {
214 self.status_flags = 0;
215 if in_alarm {
216 self.status_flags |= 0x08;
217 }
218 if fault {
219 self.status_flags |= 0x04;
220 }
221 if overridden {
222 self.status_flags |= 0x02;
223 }
224 if out_of_service {
225 self.status_flags |= 0x01;
226 }
227 }
228}
229
230impl BinaryOutput {
231 pub fn new(instance: u32, object_name: String) -> Self {
233 Self {
234 identifier: ObjectIdentifier::new(ObjectType::BinaryOutput, instance),
235 object_name,
236 present_value: BinaryPV::Inactive,
237 description: String::new(),
238 device_type: String::new(),
239 status_flags: 0,
240 event_state: EventState::Normal,
241 reliability: Reliability::NoFaultDetected,
242 out_of_service: false,
243 polarity: Polarity::Normal,
244 inactive_text: "INACTIVE".to_string(),
245 active_text: "ACTIVE".to_string(),
246 priority_array: [None; 16],
247 relinquish_default: BinaryPV::Inactive,
248 minimum_off_time: 0,
249 minimum_on_time: 0,
250 }
251 }
252
253 pub fn write_priority(&mut self, priority: u8, value: Option<BinaryPV>) -> Result<()> {
255 if !(1..=16).contains(&priority) {
256 return Err(ObjectError::InvalidValue(
257 "Priority must be 1-16".to_string(),
258 ));
259 }
260 self.priority_array[(priority - 1) as usize] = value;
261 self.update_present_value();
262 Ok(())
263 }
264
265 fn update_present_value(&mut self) {
267 if let Some(value) = self.priority_array.iter().flatten().next() {
269 self.present_value = *value;
270 return;
271 }
272 self.present_value = self.relinquish_default;
274 }
275
276 pub fn get_effective_priority(&self) -> Option<u8> {
278 for (i, priority_value) in self.priority_array.iter().enumerate() {
279 if priority_value.is_some() {
280 return Some((i + 1) as u8);
281 }
282 }
283 None
284 }
285}
286
287impl BinaryValue {
288 pub fn new(instance: u32, object_name: String) -> Self {
290 Self {
291 identifier: ObjectIdentifier::new(ObjectType::BinaryValue, instance),
292 object_name,
293 present_value: BinaryPV::Inactive,
294 description: String::new(),
295 status_flags: 0,
296 event_state: EventState::Normal,
297 reliability: Reliability::NoFaultDetected,
298 out_of_service: false,
299 inactive_text: "INACTIVE".to_string(),
300 active_text: "ACTIVE".to_string(),
301 priority_array: [None; 16],
302 relinquish_default: BinaryPV::Inactive,
303 }
304 }
305
306 pub fn write_priority(&mut self, priority: u8, value: Option<BinaryPV>) -> Result<()> {
308 if !(1..=16).contains(&priority) {
309 return Err(ObjectError::InvalidValue(
310 "Priority must be 1-16".to_string(),
311 ));
312 }
313 self.priority_array[(priority - 1) as usize] = value;
314 self.update_present_value();
315 Ok(())
316 }
317
318 fn update_present_value(&mut self) {
320 if let Some(value) = self.priority_array.iter().flatten().next() {
322 self.present_value = *value;
323 return;
324 }
325 self.present_value = self.relinquish_default;
327 }
328}
329
330impl BacnetObject for BinaryInput {
331 fn identifier(&self) -> ObjectIdentifier {
332 self.identifier
333 }
334
335 fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue> {
336 match property {
337 PropertyIdentifier::ObjectIdentifier => {
338 Ok(PropertyValue::ObjectIdentifier(self.identifier))
339 }
340 PropertyIdentifier::ObjectName => {
341 Ok(PropertyValue::CharacterString(self.object_name.clone()))
342 }
343 PropertyIdentifier::ObjectType => Ok(PropertyValue::Enumerated(u32::from(
344 ObjectType::BinaryInput,
345 ))),
346 PropertyIdentifier::PresentValue => {
347 Ok(PropertyValue::Enumerated(self.present_value as u32))
348 }
349 PropertyIdentifier::OutOfService => Ok(PropertyValue::Boolean(self.out_of_service)),
350 _ => Err(ObjectError::UnknownProperty),
351 }
352 }
353
354 fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()> {
355 match property {
356 PropertyIdentifier::ObjectName => {
357 if let PropertyValue::CharacterString(name) = value {
358 self.object_name = name;
359 Ok(())
360 } else {
361 Err(ObjectError::InvalidPropertyType)
362 }
363 }
364 PropertyIdentifier::OutOfService => {
365 if let PropertyValue::Boolean(oos) = value {
366 self.out_of_service = oos;
367 Ok(())
368 } else {
369 Err(ObjectError::InvalidPropertyType)
370 }
371 }
372 _ => Err(ObjectError::PropertyNotWritable),
373 }
374 }
375
376 fn is_property_writable(&self, property: PropertyIdentifier) -> bool {
377 matches!(
378 property,
379 PropertyIdentifier::ObjectName | PropertyIdentifier::OutOfService
380 )
381 }
382
383 fn property_list(&self) -> Vec<PropertyIdentifier> {
384 vec![
385 PropertyIdentifier::ObjectIdentifier,
386 PropertyIdentifier::ObjectName,
387 PropertyIdentifier::ObjectType,
388 PropertyIdentifier::PresentValue,
389 PropertyIdentifier::OutOfService,
390 ]
391 }
392}
393
394impl BacnetObject for BinaryOutput {
395 fn identifier(&self) -> ObjectIdentifier {
396 self.identifier
397 }
398
399 fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue> {
400 match property {
401 PropertyIdentifier::ObjectIdentifier => {
402 Ok(PropertyValue::ObjectIdentifier(self.identifier))
403 }
404 PropertyIdentifier::ObjectName => {
405 Ok(PropertyValue::CharacterString(self.object_name.clone()))
406 }
407 PropertyIdentifier::ObjectType => Ok(PropertyValue::Enumerated(u32::from(
408 ObjectType::BinaryOutput,
409 ))),
410 PropertyIdentifier::PresentValue => {
411 Ok(PropertyValue::Enumerated(self.present_value as u32))
412 }
413 PropertyIdentifier::OutOfService => Ok(PropertyValue::Boolean(self.out_of_service)),
414 PropertyIdentifier::PriorityArray => {
415 let array: Vec<PropertyValue> = self
416 .priority_array
417 .iter()
418 .map(|&v| match v {
419 Some(val) => PropertyValue::Enumerated(val as u32),
420 None => PropertyValue::Null,
421 })
422 .collect();
423 Ok(PropertyValue::Array(array))
424 }
425 _ => Err(ObjectError::UnknownProperty),
426 }
427 }
428
429 fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()> {
430 match property {
431 PropertyIdentifier::ObjectName => {
432 if let PropertyValue::CharacterString(name) = value {
433 self.object_name = name;
434 Ok(())
435 } else {
436 Err(ObjectError::InvalidPropertyType)
437 }
438 }
439 PropertyIdentifier::PresentValue => {
440 if let PropertyValue::Enumerated(val) = value {
441 let binary_val = match val {
442 0 => BinaryPV::Inactive,
443 1 => BinaryPV::Active,
444 _ => {
445 return Err(ObjectError::InvalidValue(
446 "Binary value must be 0 or 1".to_string(),
447 ))
448 }
449 };
450 self.write_priority(8, Some(binary_val))
452 } else {
453 Err(ObjectError::InvalidPropertyType)
454 }
455 }
456 PropertyIdentifier::OutOfService => {
457 if let PropertyValue::Boolean(oos) = value {
458 self.out_of_service = oos;
459 Ok(())
460 } else {
461 Err(ObjectError::InvalidPropertyType)
462 }
463 }
464 _ => Err(ObjectError::PropertyNotWritable),
465 }
466 }
467
468 fn is_property_writable(&self, property: PropertyIdentifier) -> bool {
469 matches!(
470 property,
471 PropertyIdentifier::ObjectName
472 | PropertyIdentifier::PresentValue
473 | PropertyIdentifier::OutOfService
474 )
475 }
476
477 fn property_list(&self) -> Vec<PropertyIdentifier> {
478 vec![
479 PropertyIdentifier::ObjectIdentifier,
480 PropertyIdentifier::ObjectName,
481 PropertyIdentifier::ObjectType,
482 PropertyIdentifier::PresentValue,
483 PropertyIdentifier::OutOfService,
484 PropertyIdentifier::PriorityArray,
485 ]
486 }
487}
488
489impl BacnetObject for BinaryValue {
490 fn identifier(&self) -> ObjectIdentifier {
491 self.identifier
492 }
493
494 fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue> {
495 match property {
496 PropertyIdentifier::ObjectIdentifier => {
497 Ok(PropertyValue::ObjectIdentifier(self.identifier))
498 }
499 PropertyIdentifier::ObjectName => {
500 Ok(PropertyValue::CharacterString(self.object_name.clone()))
501 }
502 PropertyIdentifier::ObjectType => Ok(PropertyValue::Enumerated(u32::from(
503 ObjectType::BinaryValue,
504 ))),
505 PropertyIdentifier::PresentValue => {
506 Ok(PropertyValue::Enumerated(self.present_value as u32))
507 }
508 PropertyIdentifier::OutOfService => Ok(PropertyValue::Boolean(self.out_of_service)),
509 PropertyIdentifier::PriorityArray => {
510 let array: Vec<PropertyValue> = self
511 .priority_array
512 .iter()
513 .map(|&v| match v {
514 Some(val) => PropertyValue::Enumerated(val as u32),
515 None => PropertyValue::Null,
516 })
517 .collect();
518 Ok(PropertyValue::Array(array))
519 }
520 _ => Err(ObjectError::UnknownProperty),
521 }
522 }
523
524 fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()> {
525 match property {
526 PropertyIdentifier::ObjectName => {
527 if let PropertyValue::CharacterString(name) = value {
528 self.object_name = name;
529 Ok(())
530 } else {
531 Err(ObjectError::InvalidPropertyType)
532 }
533 }
534 PropertyIdentifier::PresentValue => {
535 if let PropertyValue::Enumerated(val) = value {
536 let binary_val = match val {
537 0 => BinaryPV::Inactive,
538 1 => BinaryPV::Active,
539 _ => {
540 return Err(ObjectError::InvalidValue(
541 "Binary value must be 0 or 1".to_string(),
542 ))
543 }
544 };
545 self.write_priority(8, Some(binary_val))
547 } else {
548 Err(ObjectError::InvalidPropertyType)
549 }
550 }
551 PropertyIdentifier::OutOfService => {
552 if let PropertyValue::Boolean(oos) = value {
553 self.out_of_service = oos;
554 Ok(())
555 } else {
556 Err(ObjectError::InvalidPropertyType)
557 }
558 }
559 _ => Err(ObjectError::PropertyNotWritable),
560 }
561 }
562
563 fn is_property_writable(&self, property: PropertyIdentifier) -> bool {
564 matches!(
565 property,
566 PropertyIdentifier::ObjectName
567 | PropertyIdentifier::PresentValue
568 | PropertyIdentifier::OutOfService
569 )
570 }
571
572 fn property_list(&self) -> Vec<PropertyIdentifier> {
573 vec![
574 PropertyIdentifier::ObjectIdentifier,
575 PropertyIdentifier::ObjectName,
576 PropertyIdentifier::ObjectType,
577 PropertyIdentifier::PresentValue,
578 PropertyIdentifier::OutOfService,
579 PropertyIdentifier::PriorityArray,
580 ]
581 }
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587
588 #[test]
589 fn test_binary_pv_conversions() {
590 assert_eq!(BinaryPV::from(true), BinaryPV::Active);
591 assert_eq!(BinaryPV::from(false), BinaryPV::Inactive);
592 assert!(bool::from(BinaryPV::Active));
593 assert!(!bool::from(BinaryPV::Inactive));
594 }
595
596 #[test]
597 fn test_binary_input_creation() {
598 let bi = BinaryInput::new(1, "Door Switch".to_string());
599 assert_eq!(bi.identifier.instance, 1);
600 assert_eq!(bi.object_name, "Door Switch");
601 assert_eq!(bi.present_value, BinaryPV::Inactive);
602 assert_eq!(bi.change_of_state_count, 0);
603 }
604
605 #[test]
606 fn test_binary_input_change_of_state() {
607 let mut bi = BinaryInput::new(1, "Test".to_string());
608
609 bi.set_present_value(BinaryPV::Active);
610 assert_eq!(bi.present_value, BinaryPV::Active);
611 assert_eq!(bi.change_of_state_count, 1);
612
613 bi.set_present_value(BinaryPV::Active); assert_eq!(bi.change_of_state_count, 1);
615
616 bi.set_present_value(BinaryPV::Inactive);
617 assert_eq!(bi.change_of_state_count, 2);
618 }
619
620 #[test]
621 fn test_binary_output_priority() {
622 let mut bo = BinaryOutput::new(1, "Fan Control".to_string());
623
624 bo.write_priority(8, Some(BinaryPV::Active)).unwrap();
626 assert_eq!(bo.present_value, BinaryPV::Active);
627 assert_eq!(bo.get_effective_priority(), Some(8));
628
629 bo.write_priority(3, Some(BinaryPV::Inactive)).unwrap();
631 assert_eq!(bo.present_value, BinaryPV::Inactive);
632 assert_eq!(bo.get_effective_priority(), Some(3));
633
634 bo.write_priority(3, None).unwrap();
636 assert_eq!(bo.present_value, BinaryPV::Active);
637 assert_eq!(bo.get_effective_priority(), Some(8));
638 }
639
640 #[test]
641 fn test_binary_object_properties() {
642 let mut bv = BinaryValue::new(1, "Test Value".to_string());
643
644 let name = bv.get_property(PropertyIdentifier::ObjectName).unwrap();
646 if let PropertyValue::CharacterString(n) = name {
647 assert_eq!(n, "Test Value");
648 } else {
649 panic!("Expected CharacterString");
650 }
651
652 bv.set_property(
654 PropertyIdentifier::PresentValue,
655 PropertyValue::Enumerated(1),
656 )
657 .unwrap();
658 assert_eq!(bv.present_value, BinaryPV::Active);
659
660 let result = bv.set_property(
662 PropertyIdentifier::PresentValue,
663 PropertyValue::Enumerated(2),
664 );
665 assert!(result.is_err());
666 }
667}