1use std::borrow::Cow;
8use std::collections::HashMap;
9
10use bacnet_types::constructed::BACnetCOVSubscription;
11use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType, PropertyIdentifier, Segmentation};
12use bacnet_types::error::Error;
13use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, Time};
14
15use crate::common::read_property_list_property;
16use crate::traits::BACnetObject;
17
18fn compute_object_types_supported(types: &[u32]) -> Vec<u8> {
21 let max_type = types.iter().copied().max().unwrap_or(0) as usize;
22 let num_bytes = (max_type / 8) + 1;
23 let mut bitstring = vec![0u8; num_bytes];
24 for &t in types {
25 let byte_idx = (t as usize) / 8;
26 let bit_pos = 7 - ((t as usize) % 8);
27 if byte_idx < bitstring.len() {
28 bitstring[byte_idx] |= 1 << bit_pos;
29 }
30 }
31 bitstring
32}
33
34pub struct DeviceConfig {
36 pub instance: u32,
38 pub name: String,
40 pub vendor_name: String,
42 pub vendor_id: u16,
44 pub model_name: String,
46 pub firmware_revision: String,
48 pub application_software_version: String,
50 pub max_apdu_length: u32,
52 pub segmentation_supported: Segmentation,
54 pub apdu_timeout: u32,
56 pub apdu_retries: u32,
58}
59
60impl Default for DeviceConfig {
61 fn default() -> Self {
62 Self {
63 instance: 1,
64 name: "BACnet Device".into(),
65 vendor_name: "Rusty BACnet".into(),
66 vendor_id: 0,
67 model_name: "rusty-bacnet".into(),
68 firmware_revision: "0.1.0".into(),
69 application_software_version: "0.1.0".into(),
70 max_apdu_length: 1476,
71 segmentation_supported: Segmentation::NONE,
72 apdu_timeout: 6000,
73 apdu_retries: 3,
74 }
75 }
76}
77
78pub struct DeviceObject {
80 oid: ObjectIdentifier,
81 properties: HashMap<PropertyIdentifier, PropertyValue>,
82 object_list: Vec<ObjectIdentifier>,
84 protocol_object_types_supported: Vec<u8>,
87 protocol_services_supported: Vec<u8>,
90 active_cov_subscriptions: Vec<BACnetCOVSubscription>,
92}
93
94impl DeviceObject {
95 pub fn new(config: DeviceConfig) -> Result<Self, Error> {
97 let oid = ObjectIdentifier::new(ObjectType::DEVICE, config.instance)?;
98 let mut properties = HashMap::new();
99
100 properties.insert(
101 PropertyIdentifier::OBJECT_IDENTIFIER,
102 PropertyValue::ObjectIdentifier(oid),
103 );
104 properties.insert(
105 PropertyIdentifier::OBJECT_NAME,
106 PropertyValue::CharacterString(config.name),
107 );
108 properties.insert(
109 PropertyIdentifier::OBJECT_TYPE,
110 PropertyValue::Enumerated(ObjectType::DEVICE.to_raw()),
111 );
112 properties.insert(
113 PropertyIdentifier::SYSTEM_STATUS,
114 PropertyValue::Enumerated(0), );
116 properties.insert(
117 PropertyIdentifier::VENDOR_NAME,
118 PropertyValue::CharacterString(config.vendor_name),
119 );
120 properties.insert(
121 PropertyIdentifier::VENDOR_IDENTIFIER,
122 PropertyValue::Unsigned(config.vendor_id as u64),
123 );
124 properties.insert(
125 PropertyIdentifier::MODEL_NAME,
126 PropertyValue::CharacterString(config.model_name),
127 );
128 properties.insert(
129 PropertyIdentifier::FIRMWARE_REVISION,
130 PropertyValue::CharacterString(config.firmware_revision),
131 );
132 properties.insert(
133 PropertyIdentifier::APPLICATION_SOFTWARE_VERSION,
134 PropertyValue::CharacterString(config.application_software_version),
135 );
136 properties.insert(
137 PropertyIdentifier::PROTOCOL_VERSION,
138 PropertyValue::Unsigned(1),
139 );
140 properties.insert(
141 PropertyIdentifier::PROTOCOL_REVISION,
142 PropertyValue::Unsigned(22), );
144 properties.insert(
145 PropertyIdentifier::MAX_APDU_LENGTH_ACCEPTED,
146 PropertyValue::Unsigned(config.max_apdu_length as u64),
147 );
148 properties.insert(
149 PropertyIdentifier::SEGMENTATION_SUPPORTED,
150 PropertyValue::Enumerated(config.segmentation_supported.to_raw() as u32),
151 );
152 properties.insert(
153 PropertyIdentifier::APDU_TIMEOUT,
154 PropertyValue::Unsigned(config.apdu_timeout as u64),
155 );
156 properties.insert(
157 PropertyIdentifier::NUMBER_OF_APDU_RETRIES,
158 PropertyValue::Unsigned(config.apdu_retries as u64),
159 );
160 properties.insert(
161 PropertyIdentifier::DATABASE_REVISION,
162 PropertyValue::Unsigned(0),
163 );
164 properties.insert(
165 PropertyIdentifier::DESCRIPTION,
166 PropertyValue::CharacterString(String::new()),
167 );
168
169 properties.insert(
171 PropertyIdentifier::DEVICE_ADDRESS_BINDING,
172 PropertyValue::List(Vec::new()),
173 );
174
175 properties.insert(
177 PropertyIdentifier::LOCAL_DATE,
178 PropertyValue::Date(Date {
179 year: 126, month: 3,
181 day: 18,
182 day_of_week: 3, }),
184 );
185 properties.insert(
186 PropertyIdentifier::LOCAL_TIME,
187 PropertyValue::Time(Time {
188 hour: 12,
189 minute: 0,
190 second: 0,
191 hundredths: 0,
192 }),
193 );
194
195 properties.insert(
197 PropertyIdentifier::UTC_OFFSET,
198 PropertyValue::Signed(0), );
200
201 properties.insert(
203 PropertyIdentifier::LAST_RESTART_REASON,
204 PropertyValue::Enumerated(0), );
206
207 properties.insert(
209 PropertyIdentifier::DEVICE_UUID,
210 PropertyValue::OctetString(vec![0u8; 16]),
211 );
212
213 if config.segmentation_supported != Segmentation::NONE {
215 properties.insert(
216 PropertyIdentifier::MAX_SEGMENTS_ACCEPTED,
217 PropertyValue::Unsigned(65), );
219 }
220
221 let protocol_object_types_supported = compute_object_types_supported(&[
225 ObjectType::ANALOG_INPUT.to_raw(),
226 ObjectType::ANALOG_OUTPUT.to_raw(),
227 ObjectType::ANALOG_VALUE.to_raw(),
228 ObjectType::BINARY_INPUT.to_raw(),
229 ObjectType::BINARY_OUTPUT.to_raw(),
230 ObjectType::BINARY_VALUE.to_raw(),
231 ObjectType::CALENDAR.to_raw(),
232 ObjectType::COMMAND.to_raw(),
233 ObjectType::DEVICE.to_raw(),
234 ObjectType::EVENT_ENROLLMENT.to_raw(),
235 ObjectType::FILE.to_raw(),
236 ObjectType::GROUP.to_raw(),
237 ObjectType::LOOP.to_raw(),
238 ObjectType::MULTI_STATE_INPUT.to_raw(),
239 ObjectType::MULTI_STATE_OUTPUT.to_raw(),
240 ObjectType::NOTIFICATION_CLASS.to_raw(),
241 ObjectType::PROGRAM.to_raw(),
242 ObjectType::SCHEDULE.to_raw(),
243 ObjectType::AVERAGING.to_raw(),
244 ObjectType::MULTI_STATE_VALUE.to_raw(),
245 ObjectType::TREND_LOG.to_raw(),
246 ObjectType::LIFE_SAFETY_POINT.to_raw(),
247 ObjectType::LIFE_SAFETY_ZONE.to_raw(),
248 ObjectType::ACCUMULATOR.to_raw(),
249 ObjectType::PULSE_CONVERTER.to_raw(),
250 ObjectType::EVENT_LOG.to_raw(),
251 ObjectType::GLOBAL_GROUP.to_raw(),
252 ObjectType::TREND_LOG_MULTIPLE.to_raw(),
253 ObjectType::LOAD_CONTROL.to_raw(),
254 ObjectType::STRUCTURED_VIEW.to_raw(),
255 ObjectType::ACCESS_DOOR.to_raw(),
256 ObjectType::TIMER.to_raw(),
257 ObjectType::ACCESS_CREDENTIAL.to_raw(),
258 ObjectType::ACCESS_POINT.to_raw(),
259 ObjectType::ACCESS_RIGHTS.to_raw(),
260 ObjectType::ACCESS_USER.to_raw(),
261 ObjectType::ACCESS_ZONE.to_raw(),
262 ObjectType::CREDENTIAL_DATA_INPUT.to_raw(),
263 ObjectType::BITSTRING_VALUE.to_raw(),
264 ObjectType::CHARACTERSTRING_VALUE.to_raw(),
265 ObjectType::DATEPATTERN_VALUE.to_raw(),
266 ObjectType::DATE_VALUE.to_raw(),
267 ObjectType::DATETIMEPATTERN_VALUE.to_raw(),
268 ObjectType::DATETIME_VALUE.to_raw(),
269 ObjectType::INTEGER_VALUE.to_raw(),
270 ObjectType::LARGE_ANALOG_VALUE.to_raw(),
271 ObjectType::OCTETSTRING_VALUE.to_raw(),
272 ObjectType::POSITIVE_INTEGER_VALUE.to_raw(),
273 ObjectType::TIMEPATTERN_VALUE.to_raw(),
274 ObjectType::TIME_VALUE.to_raw(),
275 ObjectType::NOTIFICATION_FORWARDER.to_raw(),
276 ObjectType::ALERT_ENROLLMENT.to_raw(),
277 ObjectType::CHANNEL.to_raw(),
278 ObjectType::LIGHTING_OUTPUT.to_raw(),
279 ObjectType::BINARY_LIGHTING_OUTPUT.to_raw(),
280 ObjectType::NETWORK_PORT.to_raw(),
281 ObjectType::ELEVATOR_GROUP.to_raw(),
282 ObjectType::ESCALATOR.to_raw(),
283 ObjectType::LIFT.to_raw(),
284 ObjectType::STAGING.to_raw(),
285 ObjectType::AUDIT_REPORTER.to_raw(),
286 ObjectType::AUDIT_LOG.to_raw(),
287 ObjectType::COLOR.to_raw(),
288 ObjectType::COLOR_TEMPERATURE.to_raw(),
289 ]);
290
291 let protocol_services_supported = vec![0xA4, 0x0B, 0x80, 0x35, 0x80, 0x00];
305
306 Ok(Self {
307 oid,
308 properties,
309 object_list: vec![oid], protocol_object_types_supported,
311 protocol_services_supported,
312 active_cov_subscriptions: Vec::new(),
313 })
314 }
315
316 pub fn set_object_list(&mut self, oids: Vec<ObjectIdentifier>) {
318 self.object_list = oids;
319 }
320
321 pub fn instance(&self) -> u32 {
323 self.oid.instance_number()
324 }
325
326 pub fn set_description(&mut self, desc: impl Into<String>) {
328 self.properties.insert(
329 PropertyIdentifier::DESCRIPTION,
330 PropertyValue::CharacterString(desc.into()),
331 );
332 }
333
334 pub fn set_active_cov_subscriptions(&mut self, subs: Vec<BACnetCOVSubscription>) {
336 self.active_cov_subscriptions = subs;
337 }
338
339 pub fn add_cov_subscription(&mut self, sub: BACnetCOVSubscription) {
341 self.active_cov_subscriptions.push(sub);
342 }
343}
344
345impl BACnetObject for DeviceObject {
346 fn object_identifier(&self) -> ObjectIdentifier {
347 self.oid
348 }
349
350 fn object_name(&self) -> &str {
351 match self.properties.get(&PropertyIdentifier::OBJECT_NAME) {
352 Some(PropertyValue::CharacterString(s)) => s,
353 _ => "Unknown",
354 }
355 }
356
357 fn read_property(
358 &self,
359 property: PropertyIdentifier,
360 array_index: Option<u32>,
361 ) -> Result<PropertyValue, Error> {
362 if property == PropertyIdentifier::OBJECT_LIST {
363 return match array_index {
364 None => {
365 let elements = self
366 .object_list
367 .iter()
368 .map(|oid| PropertyValue::ObjectIdentifier(*oid))
369 .collect();
370 Ok(PropertyValue::List(elements))
371 }
372 Some(0) => {
373 Ok(PropertyValue::Unsigned(self.object_list.len() as u64))
375 }
376 Some(idx) => {
377 let i = (idx - 1) as usize; if i < self.object_list.len() {
379 Ok(PropertyValue::ObjectIdentifier(self.object_list[i]))
380 } else {
381 Err(Error::Protocol {
382 class: ErrorClass::PROPERTY.to_raw() as u32,
383 code: ErrorCode::INVALID_ARRAY_INDEX.to_raw() as u32,
384 })
385 }
386 }
387 };
388 }
389
390 if property == PropertyIdentifier::PROPERTY_LIST {
391 return read_property_list_property(&self.property_list(), array_index);
392 }
393
394 if property == PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED {
395 let num_bytes = self.protocol_object_types_supported.len();
396 let total_bits = num_bytes * 8;
397 let mut max_type = 0u32;
399 for (byte_idx, &byte) in self.protocol_object_types_supported.iter().enumerate() {
400 for bit in 0..8 {
401 if byte & (1 << (7 - bit)) != 0 {
402 max_type = (byte_idx * 8 + bit) as u32;
403 }
404 }
405 }
406 let used_bits = max_type as usize + 1;
407 let unused = (total_bits - used_bits) as u8;
408 return Ok(PropertyValue::BitString {
409 unused_bits: unused,
410 data: self.protocol_object_types_supported.clone(),
411 });
412 }
413
414 if property == PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED {
415 return Ok(PropertyValue::BitString {
417 unused_bits: 7,
418 data: self.protocol_services_supported.clone(),
419 });
420 }
421
422 if property == PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS {
423 let elements: Vec<PropertyValue> = self
424 .active_cov_subscriptions
425 .iter()
426 .map(|sub| {
427 let mut entry = vec![
428 PropertyValue::ObjectIdentifier(
429 sub.monitored_property_reference.object_identifier,
430 ),
431 PropertyValue::Unsigned(sub.recipient.process_identifier as u64),
432 PropertyValue::Boolean(sub.issue_confirmed_notifications),
433 PropertyValue::Unsigned(sub.time_remaining as u64),
434 ];
435 if let Some(inc) = sub.cov_increment {
436 entry.push(PropertyValue::Real(inc));
437 }
438 PropertyValue::List(entry)
439 })
440 .collect();
441 return Ok(PropertyValue::List(elements));
442 }
443
444 self.properties
445 .get(&property)
446 .cloned()
447 .ok_or(Error::Protocol {
448 class: ErrorClass::PROPERTY.to_raw() as u32,
449 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
450 })
451 }
452
453 fn write_property(
454 &mut self,
455 property: PropertyIdentifier,
456 _array_index: Option<u32>,
457 value: PropertyValue,
458 _priority: Option<u8>,
459 ) -> Result<(), Error> {
460 if property == PropertyIdentifier::DESCRIPTION {
461 if let PropertyValue::CharacterString(_) = &value {
462 self.properties.insert(property, value);
463 return Ok(());
464 }
465 return Err(Error::Protocol {
466 class: ErrorClass::PROPERTY.to_raw() as u32,
467 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
468 });
469 }
470 Err(Error::Protocol {
471 class: ErrorClass::PROPERTY.to_raw() as u32,
472 code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
473 })
474 }
475
476 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
477 let mut props: Vec<PropertyIdentifier> = self.properties.keys().copied().collect();
478 props.push(PropertyIdentifier::OBJECT_LIST);
479 props.push(PropertyIdentifier::PROPERTY_LIST);
480 props.push(PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED);
481 props.push(PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED);
482 props.push(PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS);
483 props.sort_by_key(|p| p.to_raw());
484 Cow::Owned(props)
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491
492 fn make_device() -> DeviceObject {
493 DeviceObject::new(DeviceConfig {
494 instance: 1234,
495 name: "Test Device".into(),
496 ..DeviceConfig::default()
497 })
498 .unwrap()
499 }
500
501 #[test]
502 fn read_object_identifier() {
503 let dev = make_device();
504 let val = dev
505 .read_property(PropertyIdentifier::OBJECT_IDENTIFIER, None)
506 .unwrap();
507 let expected_oid = ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap();
508 assert_eq!(val, PropertyValue::ObjectIdentifier(expected_oid));
509 }
510
511 #[test]
512 fn read_object_name() {
513 let dev = make_device();
514 let val = dev
515 .read_property(PropertyIdentifier::OBJECT_NAME, None)
516 .unwrap();
517 assert_eq!(val, PropertyValue::CharacterString("Test Device".into()));
518 }
519
520 #[test]
521 fn read_object_type() {
522 let dev = make_device();
523 let val = dev
524 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
525 .unwrap();
526 assert_eq!(val, PropertyValue::Enumerated(ObjectType::DEVICE.to_raw()));
527 }
528
529 #[test]
530 fn read_vendor_name() {
531 let dev = make_device();
532 let val = dev
533 .read_property(PropertyIdentifier::VENDOR_NAME, None)
534 .unwrap();
535 assert_eq!(val, PropertyValue::CharacterString("Rusty BACnet".into()));
536 }
537
538 #[test]
539 fn read_max_apdu_length() {
540 let dev = make_device();
541 let val = dev
542 .read_property(PropertyIdentifier::MAX_APDU_LENGTH_ACCEPTED, None)
543 .unwrap();
544 assert_eq!(val, PropertyValue::Unsigned(1476));
545 }
546
547 #[test]
548 fn read_unknown_property_fails() {
549 let dev = make_device();
550 let result = dev.read_property(PropertyIdentifier::PRESENT_VALUE, None);
552 assert!(result.is_err());
553 }
554
555 #[test]
556 fn write_property_denied() {
557 let mut dev = make_device();
558 let result = dev.write_property(
559 PropertyIdentifier::OBJECT_NAME,
560 None,
561 PropertyValue::CharacterString("New Name".into()),
562 None,
563 );
564 assert!(result.is_err());
565 }
566
567 #[test]
568 fn device_description_default_empty() {
569 let dev = make_device();
570 let val = dev
571 .read_property(PropertyIdentifier::DESCRIPTION, None)
572 .unwrap();
573 assert_eq!(val, PropertyValue::CharacterString(String::new()));
574 }
575
576 #[test]
577 fn device_description_write_read() {
578 let mut dev = make_device();
579 dev.write_property(
580 PropertyIdentifier::DESCRIPTION,
581 None,
582 PropertyValue::CharacterString("Main building controller".into()),
583 None,
584 )
585 .unwrap();
586 assert_eq!(
587 dev.read_property(PropertyIdentifier::DESCRIPTION, None)
588 .unwrap(),
589 PropertyValue::CharacterString("Main building controller".into())
590 );
591 }
592
593 #[test]
594 fn device_set_description_convenience() {
595 let mut dev = make_device();
596 dev.set_description("Rooftop unit controller");
597 assert_eq!(
598 dev.read_property(PropertyIdentifier::DESCRIPTION, None)
599 .unwrap(),
600 PropertyValue::CharacterString("Rooftop unit controller".into())
601 );
602 }
603
604 #[test]
605 fn device_description_in_property_list() {
606 let dev = make_device();
607 assert!(dev
608 .property_list()
609 .contains(&PropertyIdentifier::DESCRIPTION));
610 }
611
612 #[test]
613 fn object_list_default_contains_device() {
614 let dev = make_device();
615 let val = dev
617 .read_property(PropertyIdentifier::OBJECT_LIST, None)
618 .unwrap();
619 let expected_oid = ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap();
620 assert_eq!(
621 val,
622 PropertyValue::List(vec![PropertyValue::ObjectIdentifier(expected_oid)])
623 );
624 }
625
626 #[test]
627 fn object_list_array_index() {
628 let dev = make_device();
629 let val = dev
631 .read_property(PropertyIdentifier::OBJECT_LIST, Some(0))
632 .unwrap();
633 assert_eq!(val, PropertyValue::Unsigned(1));
634
635 let val = dev
637 .read_property(PropertyIdentifier::OBJECT_LIST, Some(1))
638 .unwrap();
639 let expected_oid = ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap();
640 assert_eq!(val, PropertyValue::ObjectIdentifier(expected_oid));
641
642 let result = dev.read_property(PropertyIdentifier::OBJECT_LIST, Some(2));
644 assert!(result.is_err());
645 }
646
647 #[test]
648 fn set_object_list() {
649 let mut dev = make_device();
650 let dev_oid = dev.object_identifier();
651 let ai1 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
652 let ai2 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 2).unwrap();
653 dev.set_object_list(vec![dev_oid, ai1, ai2]);
654
655 let val = dev
657 .read_property(PropertyIdentifier::OBJECT_LIST, None)
658 .unwrap();
659 assert_eq!(
660 val,
661 PropertyValue::List(vec![
662 PropertyValue::ObjectIdentifier(dev_oid),
663 PropertyValue::ObjectIdentifier(ai1),
664 PropertyValue::ObjectIdentifier(ai2),
665 ])
666 );
667
668 let count = dev
670 .read_property(PropertyIdentifier::OBJECT_LIST, Some(0))
671 .unwrap();
672 assert_eq!(count, PropertyValue::Unsigned(3));
673 }
674
675 #[test]
676 fn property_list_contains_expected() {
677 let dev = make_device();
678 let props = dev.property_list();
679 assert!(props.contains(&PropertyIdentifier::OBJECT_IDENTIFIER));
680 assert!(props.contains(&PropertyIdentifier::OBJECT_NAME));
681 assert!(props.contains(&PropertyIdentifier::OBJECT_TYPE));
682 assert!(props.contains(&PropertyIdentifier::VENDOR_NAME));
683 assert!(props.contains(&PropertyIdentifier::OBJECT_LIST));
684 assert!(props.contains(&PropertyIdentifier::PROPERTY_LIST));
685 assert!(props.contains(&PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED));
686 assert!(props.contains(&PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED));
687 }
688
689 #[test]
690 fn read_protocol_object_types_supported() {
691 let dev = make_device();
692 let val = dev
693 .read_property(PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED, None)
694 .unwrap();
695 match val {
696 PropertyValue::BitString { unused_bits, data } => {
697 assert_eq!(unused_bits, 7);
698 assert_eq!(data.len(), 9);
699 assert_eq!(data[0], 0xFF);
701 assert_eq!(data[1], 0xFF);
703 assert_eq!(data[2], 0xFF);
705 assert_eq!(data[3], 0xFF);
707 assert_eq!(data[4], 0xFD);
709 assert_eq!(data[5], 0xFF);
711 assert_eq!(data[6], 0xFF);
713 assert_eq!(data[7], 0xFF);
715 assert_eq!(data[8], 0x80);
717 }
718 _ => panic!("Expected BitString"),
719 }
720 }
721
722 #[test]
723 fn read_protocol_services_supported() {
724 let dev = make_device();
725 let val = dev
726 .read_property(PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED, None)
727 .unwrap();
728 match val {
729 PropertyValue::BitString { unused_bits, data } => {
730 assert_eq!(unused_bits, 7);
731 assert_eq!(data.len(), 6);
732 assert_eq!(data[0], 0xA4);
734 assert_eq!(data[1], 0x0B);
736 assert_eq!(data[4], 0x80);
738 }
739 _ => panic!("Expected BitString"),
740 }
741 }
742
743 #[test]
744 fn active_cov_subscriptions_default_empty() {
745 let dev = make_device();
746 let val = dev
747 .read_property(PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS, None)
748 .unwrap();
749 assert_eq!(val, PropertyValue::List(vec![]));
750 }
751
752 #[test]
753 fn active_cov_subscriptions_in_property_list() {
754 let dev = make_device();
755 assert!(dev
756 .property_list()
757 .contains(&PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS));
758 }
759
760 #[test]
761 fn active_cov_subscriptions_after_add() {
762 use bacnet_types::constructed::{
763 BACnetCOVSubscription, BACnetObjectPropertyReference, BACnetRecipient,
764 BACnetRecipientProcess,
765 };
766
767 let mut dev = make_device();
768 let dev_oid = ObjectIdentifier::new(ObjectType::DEVICE, 200).unwrap();
769 let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
770
771 dev.add_cov_subscription(BACnetCOVSubscription {
772 recipient: BACnetRecipientProcess {
773 recipient: BACnetRecipient::Device(dev_oid),
774 process_identifier: 7,
775 },
776 monitored_property_reference: BACnetObjectPropertyReference::new(
777 ai_oid,
778 PropertyIdentifier::PRESENT_VALUE.to_raw(),
779 ),
780 issue_confirmed_notifications: true,
781 time_remaining: 300,
782 cov_increment: Some(0.5),
783 });
784
785 let val = dev
786 .read_property(PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS, None)
787 .unwrap();
788 match val {
789 PropertyValue::List(subs) => {
790 assert_eq!(subs.len(), 1);
791 match &subs[0] {
792 PropertyValue::List(entry) => {
793 assert_eq!(entry.len(), 5); assert_eq!(entry[0], PropertyValue::ObjectIdentifier(ai_oid));
795 assert_eq!(entry[1], PropertyValue::Unsigned(7));
796 assert_eq!(entry[2], PropertyValue::Boolean(true));
797 assert_eq!(entry[3], PropertyValue::Unsigned(300));
798 assert_eq!(entry[4], PropertyValue::Real(0.5));
799 }
800 _ => panic!("Expected List entry"),
801 }
802 }
803 _ => panic!("Expected List"),
804 }
805 }
806
807 #[test]
808 fn active_cov_subscriptions_without_increment() {
809 use bacnet_types::constructed::{
810 BACnetCOVSubscription, BACnetObjectPropertyReference, BACnetRecipient,
811 BACnetRecipientProcess,
812 };
813
814 let mut dev = make_device();
815 let dev_oid = ObjectIdentifier::new(ObjectType::DEVICE, 50).unwrap();
816 let bv_oid = ObjectIdentifier::new(ObjectType::BINARY_VALUE, 3).unwrap();
817
818 dev.add_cov_subscription(BACnetCOVSubscription {
819 recipient: BACnetRecipientProcess {
820 recipient: BACnetRecipient::Device(dev_oid),
821 process_identifier: 1,
822 },
823 monitored_property_reference: BACnetObjectPropertyReference::new(
824 bv_oid,
825 PropertyIdentifier::PRESENT_VALUE.to_raw(),
826 ),
827 issue_confirmed_notifications: false,
828 time_remaining: 0,
829 cov_increment: None,
830 });
831
832 let val = dev
833 .read_property(PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS, None)
834 .unwrap();
835 match val {
836 PropertyValue::List(subs) => {
837 assert_eq!(subs.len(), 1);
838 match &subs[0] {
839 PropertyValue::List(entry) => {
840 assert_eq!(entry.len(), 4); assert_eq!(entry[2], PropertyValue::Boolean(false));
842 }
843 _ => panic!("Expected List entry"),
844 }
845 }
846 _ => panic!("Expected List"),
847 }
848 }
849
850 #[test]
851 fn active_cov_subscriptions_write_denied() {
852 let mut dev = make_device();
853 let result = dev.write_property(
854 PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS,
855 None,
856 PropertyValue::List(vec![]),
857 None,
858 );
859 assert!(result.is_err());
860 }
861
862 #[test]
863 fn set_active_cov_subscriptions_replaces() {
864 use bacnet_types::constructed::{
865 BACnetCOVSubscription, BACnetObjectPropertyReference, BACnetRecipient,
866 BACnetRecipientProcess,
867 };
868
869 let mut dev = make_device();
870 let dev_oid = ObjectIdentifier::new(ObjectType::DEVICE, 10).unwrap();
871 let ai1 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
872 let ai2 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 2).unwrap();
873
874 let sub1 = BACnetCOVSubscription {
876 recipient: BACnetRecipientProcess {
877 recipient: BACnetRecipient::Device(dev_oid),
878 process_identifier: 1,
879 },
880 monitored_property_reference: BACnetObjectPropertyReference::new(
881 ai1,
882 PropertyIdentifier::PRESENT_VALUE.to_raw(),
883 ),
884 issue_confirmed_notifications: true,
885 time_remaining: 100,
886 cov_increment: None,
887 };
888 let sub2 = BACnetCOVSubscription {
889 recipient: BACnetRecipientProcess {
890 recipient: BACnetRecipient::Device(dev_oid),
891 process_identifier: 2,
892 },
893 monitored_property_reference: BACnetObjectPropertyReference::new(
894 ai2,
895 PropertyIdentifier::PRESENT_VALUE.to_raw(),
896 ),
897 issue_confirmed_notifications: false,
898 time_remaining: 200,
899 cov_increment: Some(1.0),
900 };
901 dev.set_active_cov_subscriptions(vec![sub1, sub2]);
902
903 let val = dev
904 .read_property(PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS, None)
905 .unwrap();
906 match val {
907 PropertyValue::List(subs) => assert_eq!(subs.len(), 2),
908 _ => panic!("Expected List"),
909 }
910
911 dev.set_active_cov_subscriptions(vec![]);
913 let val = dev
914 .read_property(PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS, None)
915 .unwrap();
916 assert_eq!(val, PropertyValue::List(vec![]));
917 }
918
919 #[test]
920 fn compute_object_types_supported_known_inputs() {
921 assert_eq!(compute_object_types_supported(&[0]), vec![0x80]);
922 assert_eq!(compute_object_types_supported(&[8]), vec![0x00, 0x80]);
923 assert_eq!(
924 compute_object_types_supported(&[0, 1, 2, 3, 4, 5]),
925 vec![0xFC]
926 );
927 assert_eq!(compute_object_types_supported(&[]), vec![0x00]);
928 }
929
930 #[test]
931 fn compute_object_types_supported_old_bits_preserved() {
932 let old_types: Vec<u32> = vec![0, 1, 2, 3, 4, 5, 8, 13, 14, 19];
933 let bs = compute_object_types_supported(&old_types);
934 assert_eq!(bs[0], 0xFC);
935 assert_eq!(bs[1], 0x86);
936 assert_eq!(bs[2], 0x10);
937 }
938
939 #[test]
940 fn device_protocol_object_types_has_new_bits() {
941 let dev = DeviceObject::new(DeviceConfig {
942 instance: 1,
943 name: "Test".into(),
944 ..DeviceConfig::default()
945 })
946 .unwrap();
947 let val = dev
948 .read_property(PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED, None)
949 .unwrap();
950 let bits = match val {
951 PropertyValue::BitString { data, .. } => data,
952 _ => panic!("Expected BitString"),
953 };
954 assert!(bits.len() >= 8, "bitstring should cover types up to 62");
955 assert_eq!(bits[0] & 0xFC, 0xFC, "AI/AO/AV/BI/BO/BV");
956 assert_ne!(bits[1] & 0x80, 0, "Device (8)");
957 assert_ne!(bits[1] & 0x04, 0, "MSI (13)");
958 assert_ne!(bits[1] & 0x02, 0, "MSO (14)");
959 assert_ne!(bits[2] & 0x10, 0, "MSV (19)");
960 assert_ne!(bits[0] & 0x03, 0, "Calendar(6) and Command(7)");
961 assert_ne!(bits[3] & 0x80, 0, "Accumulator (24)");
962 assert_ne!(bits[7] & 0x80, 0, "NetworkPort (56)");
963 }
964}