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::{ObjectIdentifier, PropertyValue};
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>,
88 protocol_services_supported: Vec<u8>,
92 active_cov_subscriptions: Vec<BACnetCOVSubscription>,
94}
95
96impl DeviceObject {
97 pub fn new(config: DeviceConfig) -> Result<Self, Error> {
99 let oid = ObjectIdentifier::new(ObjectType::DEVICE, config.instance)?;
100 let mut properties = HashMap::new();
101
102 properties.insert(
103 PropertyIdentifier::OBJECT_IDENTIFIER,
104 PropertyValue::ObjectIdentifier(oid),
105 );
106 properties.insert(
107 PropertyIdentifier::OBJECT_NAME,
108 PropertyValue::CharacterString(config.name),
109 );
110 properties.insert(
111 PropertyIdentifier::OBJECT_TYPE,
112 PropertyValue::Enumerated(ObjectType::DEVICE.to_raw()),
113 );
114 properties.insert(
115 PropertyIdentifier::SYSTEM_STATUS,
116 PropertyValue::Enumerated(0), );
118 properties.insert(
119 PropertyIdentifier::VENDOR_NAME,
120 PropertyValue::CharacterString(config.vendor_name),
121 );
122 properties.insert(
123 PropertyIdentifier::VENDOR_IDENTIFIER,
124 PropertyValue::Unsigned(config.vendor_id as u64),
125 );
126 properties.insert(
127 PropertyIdentifier::MODEL_NAME,
128 PropertyValue::CharacterString(config.model_name),
129 );
130 properties.insert(
131 PropertyIdentifier::FIRMWARE_REVISION,
132 PropertyValue::CharacterString(config.firmware_revision),
133 );
134 properties.insert(
135 PropertyIdentifier::APPLICATION_SOFTWARE_VERSION,
136 PropertyValue::CharacterString(config.application_software_version),
137 );
138 properties.insert(
139 PropertyIdentifier::PROTOCOL_VERSION,
140 PropertyValue::Unsigned(1),
141 );
142 properties.insert(
143 PropertyIdentifier::PROTOCOL_REVISION,
144 PropertyValue::Unsigned(22), );
146 properties.insert(
147 PropertyIdentifier::MAX_APDU_LENGTH_ACCEPTED,
148 PropertyValue::Unsigned(config.max_apdu_length as u64),
149 );
150 properties.insert(
151 PropertyIdentifier::SEGMENTATION_SUPPORTED,
152 PropertyValue::Enumerated(config.segmentation_supported.to_raw() as u32),
153 );
154 properties.insert(
155 PropertyIdentifier::APDU_TIMEOUT,
156 PropertyValue::Unsigned(config.apdu_timeout as u64),
157 );
158 properties.insert(
159 PropertyIdentifier::NUMBER_OF_APDU_RETRIES,
160 PropertyValue::Unsigned(config.apdu_retries as u64),
161 );
162 properties.insert(
163 PropertyIdentifier::DATABASE_REVISION,
164 PropertyValue::Unsigned(0),
165 );
166 properties.insert(
167 PropertyIdentifier::DESCRIPTION,
168 PropertyValue::CharacterString(String::new()),
169 );
170
171 let protocol_object_types_supported = compute_object_types_supported(&[
175 ObjectType::ANALOG_INPUT.to_raw(),
176 ObjectType::ANALOG_OUTPUT.to_raw(),
177 ObjectType::ANALOG_VALUE.to_raw(),
178 ObjectType::BINARY_INPUT.to_raw(),
179 ObjectType::BINARY_OUTPUT.to_raw(),
180 ObjectType::BINARY_VALUE.to_raw(),
181 ObjectType::CALENDAR.to_raw(),
182 ObjectType::COMMAND.to_raw(),
183 ObjectType::DEVICE.to_raw(),
184 ObjectType::EVENT_ENROLLMENT.to_raw(),
185 ObjectType::FILE.to_raw(),
186 ObjectType::GROUP.to_raw(),
187 ObjectType::LOOP.to_raw(),
188 ObjectType::MULTI_STATE_INPUT.to_raw(),
189 ObjectType::MULTI_STATE_OUTPUT.to_raw(),
190 ObjectType::NOTIFICATION_CLASS.to_raw(),
191 ObjectType::PROGRAM.to_raw(),
192 ObjectType::SCHEDULE.to_raw(),
193 ObjectType::AVERAGING.to_raw(),
194 ObjectType::MULTI_STATE_VALUE.to_raw(),
195 ObjectType::TREND_LOG.to_raw(),
196 ObjectType::LIFE_SAFETY_POINT.to_raw(),
197 ObjectType::LIFE_SAFETY_ZONE.to_raw(),
198 ObjectType::ACCUMULATOR.to_raw(),
199 ObjectType::PULSE_CONVERTER.to_raw(),
200 ObjectType::EVENT_LOG.to_raw(),
201 ObjectType::GLOBAL_GROUP.to_raw(),
202 ObjectType::TREND_LOG_MULTIPLE.to_raw(),
203 ObjectType::LOAD_CONTROL.to_raw(),
204 ObjectType::STRUCTURED_VIEW.to_raw(),
205 ObjectType::ACCESS_DOOR.to_raw(),
206 ObjectType::TIMER.to_raw(),
207 ObjectType::ACCESS_CREDENTIAL.to_raw(),
208 ObjectType::ACCESS_POINT.to_raw(),
209 ObjectType::ACCESS_RIGHTS.to_raw(),
210 ObjectType::ACCESS_USER.to_raw(),
211 ObjectType::ACCESS_ZONE.to_raw(),
212 ObjectType::CREDENTIAL_DATA_INPUT.to_raw(),
213 ObjectType::BITSTRING_VALUE.to_raw(),
214 ObjectType::CHARACTERSTRING_VALUE.to_raw(),
215 ObjectType::DATEPATTERN_VALUE.to_raw(),
216 ObjectType::DATE_VALUE.to_raw(),
217 ObjectType::DATETIMEPATTERN_VALUE.to_raw(),
218 ObjectType::DATETIME_VALUE.to_raw(),
219 ObjectType::INTEGER_VALUE.to_raw(),
220 ObjectType::LARGE_ANALOG_VALUE.to_raw(),
221 ObjectType::OCTETSTRING_VALUE.to_raw(),
222 ObjectType::POSITIVE_INTEGER_VALUE.to_raw(),
223 ObjectType::TIMEPATTERN_VALUE.to_raw(),
224 ObjectType::TIME_VALUE.to_raw(),
225 ObjectType::NOTIFICATION_FORWARDER.to_raw(),
226 ObjectType::ALERT_ENROLLMENT.to_raw(),
227 ObjectType::CHANNEL.to_raw(),
228 ObjectType::LIGHTING_OUTPUT.to_raw(),
229 ObjectType::BINARY_LIGHTING_OUTPUT.to_raw(),
230 ObjectType::NETWORK_PORT.to_raw(),
231 ObjectType::ELEVATOR_GROUP.to_raw(),
232 ObjectType::ESCALATOR.to_raw(),
233 ObjectType::LIFT.to_raw(),
234 ObjectType::STAGING.to_raw(),
235 ObjectType::AUDIT_REPORTER.to_raw(),
236 ObjectType::AUDIT_LOG.to_raw(),
237 ]);
238
239 let protocol_services_supported = vec![0xA4, 0x0B, 0x80, 0x35, 0x80, 0x00];
253
254 Ok(Self {
255 oid,
256 properties,
257 object_list: vec![oid], protocol_object_types_supported,
259 protocol_services_supported,
260 active_cov_subscriptions: Vec::new(),
261 })
262 }
263
264 pub fn set_object_list(&mut self, oids: Vec<ObjectIdentifier>) {
266 self.object_list = oids;
267 }
268
269 pub fn instance(&self) -> u32 {
271 self.oid.instance_number()
272 }
273
274 pub fn set_description(&mut self, desc: impl Into<String>) {
276 self.properties.insert(
277 PropertyIdentifier::DESCRIPTION,
278 PropertyValue::CharacterString(desc.into()),
279 );
280 }
281
282 pub fn set_active_cov_subscriptions(&mut self, subs: Vec<BACnetCOVSubscription>) {
284 self.active_cov_subscriptions = subs;
285 }
286
287 pub fn add_cov_subscription(&mut self, sub: BACnetCOVSubscription) {
289 self.active_cov_subscriptions.push(sub);
290 }
291}
292
293impl BACnetObject for DeviceObject {
294 fn object_identifier(&self) -> ObjectIdentifier {
295 self.oid
296 }
297
298 fn object_name(&self) -> &str {
299 match self.properties.get(&PropertyIdentifier::OBJECT_NAME) {
300 Some(PropertyValue::CharacterString(s)) => s,
301 _ => "Unknown",
302 }
303 }
304
305 fn read_property(
306 &self,
307 property: PropertyIdentifier,
308 array_index: Option<u32>,
309 ) -> Result<PropertyValue, Error> {
310 if property == PropertyIdentifier::OBJECT_LIST {
312 return match array_index {
313 None => {
314 let elements = self
316 .object_list
317 .iter()
318 .map(|oid| PropertyValue::ObjectIdentifier(*oid))
319 .collect();
320 Ok(PropertyValue::List(elements))
321 }
322 Some(0) => {
323 Ok(PropertyValue::Unsigned(self.object_list.len() as u64))
325 }
326 Some(idx) => {
327 let i = (idx - 1) as usize; if i < self.object_list.len() {
329 Ok(PropertyValue::ObjectIdentifier(self.object_list[i]))
330 } else {
331 Err(Error::Protocol {
332 class: ErrorClass::PROPERTY.to_raw() as u32,
333 code: ErrorCode::INVALID_ARRAY_INDEX.to_raw() as u32,
334 })
335 }
336 }
337 };
338 }
339
340 if property == PropertyIdentifier::PROPERTY_LIST {
342 return read_property_list_property(&self.property_list(), array_index);
343 }
344
345 if property == PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED {
347 return Ok(PropertyValue::BitString {
349 unused_bits: 1,
350 data: self.protocol_object_types_supported.clone(),
351 });
352 }
353
354 if property == PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED {
356 return Ok(PropertyValue::BitString {
358 unused_bits: 7,
359 data: self.protocol_services_supported.clone(),
360 });
361 }
362
363 if property == PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS {
365 let elements: Vec<PropertyValue> = self
366 .active_cov_subscriptions
367 .iter()
368 .map(|sub| {
369 let mut entry = vec![
370 PropertyValue::ObjectIdentifier(
371 sub.monitored_property_reference.object_identifier,
372 ),
373 PropertyValue::Unsigned(sub.recipient.process_identifier as u64),
374 PropertyValue::Boolean(sub.issue_confirmed_notifications),
375 PropertyValue::Unsigned(sub.time_remaining as u64),
376 ];
377 if let Some(inc) = sub.cov_increment {
378 entry.push(PropertyValue::Real(inc));
379 }
380 PropertyValue::List(entry)
381 })
382 .collect();
383 return Ok(PropertyValue::List(elements));
384 }
385
386 self.properties
387 .get(&property)
388 .cloned()
389 .ok_or(Error::Protocol {
390 class: ErrorClass::PROPERTY.to_raw() as u32,
391 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
392 })
393 }
394
395 fn write_property(
396 &mut self,
397 property: PropertyIdentifier,
398 _array_index: Option<u32>,
399 value: PropertyValue,
400 _priority: Option<u8>,
401 ) -> Result<(), Error> {
402 if property == PropertyIdentifier::DESCRIPTION {
403 if let PropertyValue::CharacterString(_) = &value {
404 self.properties.insert(property, value);
405 return Ok(());
406 }
407 return Err(Error::Protocol {
408 class: ErrorClass::PROPERTY.to_raw() as u32,
409 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
410 });
411 }
412 Err(Error::Protocol {
413 class: ErrorClass::PROPERTY.to_raw() as u32,
414 code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
415 })
416 }
417
418 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
419 let mut props: Vec<PropertyIdentifier> = self.properties.keys().copied().collect();
420 props.push(PropertyIdentifier::OBJECT_LIST);
421 props.push(PropertyIdentifier::PROPERTY_LIST);
422 props.push(PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED);
423 props.push(PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED);
424 props.push(PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS);
425 props.sort_by_key(|p| p.to_raw());
426 Cow::Owned(props)
427 }
428}
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433
434 fn make_device() -> DeviceObject {
435 DeviceObject::new(DeviceConfig {
436 instance: 1234,
437 name: "Test Device".into(),
438 ..DeviceConfig::default()
439 })
440 .unwrap()
441 }
442
443 #[test]
444 fn read_object_identifier() {
445 let dev = make_device();
446 let val = dev
447 .read_property(PropertyIdentifier::OBJECT_IDENTIFIER, None)
448 .unwrap();
449 let expected_oid = ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap();
450 assert_eq!(val, PropertyValue::ObjectIdentifier(expected_oid));
451 }
452
453 #[test]
454 fn read_object_name() {
455 let dev = make_device();
456 let val = dev
457 .read_property(PropertyIdentifier::OBJECT_NAME, None)
458 .unwrap();
459 assert_eq!(val, PropertyValue::CharacterString("Test Device".into()));
460 }
461
462 #[test]
463 fn read_object_type() {
464 let dev = make_device();
465 let val = dev
466 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
467 .unwrap();
468 assert_eq!(val, PropertyValue::Enumerated(ObjectType::DEVICE.to_raw()));
469 }
470
471 #[test]
472 fn read_vendor_name() {
473 let dev = make_device();
474 let val = dev
475 .read_property(PropertyIdentifier::VENDOR_NAME, None)
476 .unwrap();
477 assert_eq!(val, PropertyValue::CharacterString("Rusty BACnet".into()));
478 }
479
480 #[test]
481 fn read_max_apdu_length() {
482 let dev = make_device();
483 let val = dev
484 .read_property(PropertyIdentifier::MAX_APDU_LENGTH_ACCEPTED, None)
485 .unwrap();
486 assert_eq!(val, PropertyValue::Unsigned(1476));
487 }
488
489 #[test]
490 fn read_unknown_property_fails() {
491 let dev = make_device();
492 let result = dev.read_property(PropertyIdentifier::PRESENT_VALUE, None);
494 assert!(result.is_err());
495 }
496
497 #[test]
498 fn write_property_denied() {
499 let mut dev = make_device();
500 let result = dev.write_property(
501 PropertyIdentifier::OBJECT_NAME,
502 None,
503 PropertyValue::CharacterString("New Name".into()),
504 None,
505 );
506 assert!(result.is_err());
507 }
508
509 #[test]
510 fn device_description_default_empty() {
511 let dev = make_device();
512 let val = dev
513 .read_property(PropertyIdentifier::DESCRIPTION, None)
514 .unwrap();
515 assert_eq!(val, PropertyValue::CharacterString(String::new()));
516 }
517
518 #[test]
519 fn device_description_write_read() {
520 let mut dev = make_device();
521 dev.write_property(
522 PropertyIdentifier::DESCRIPTION,
523 None,
524 PropertyValue::CharacterString("Main building controller".into()),
525 None,
526 )
527 .unwrap();
528 assert_eq!(
529 dev.read_property(PropertyIdentifier::DESCRIPTION, None)
530 .unwrap(),
531 PropertyValue::CharacterString("Main building controller".into())
532 );
533 }
534
535 #[test]
536 fn device_set_description_convenience() {
537 let mut dev = make_device();
538 dev.set_description("Rooftop unit controller");
539 assert_eq!(
540 dev.read_property(PropertyIdentifier::DESCRIPTION, None)
541 .unwrap(),
542 PropertyValue::CharacterString("Rooftop unit controller".into())
543 );
544 }
545
546 #[test]
547 fn device_description_in_property_list() {
548 let dev = make_device();
549 assert!(dev
550 .property_list()
551 .contains(&PropertyIdentifier::DESCRIPTION));
552 }
553
554 #[test]
555 fn object_list_default_contains_device() {
556 let dev = make_device();
557 let val = dev
559 .read_property(PropertyIdentifier::OBJECT_LIST, None)
560 .unwrap();
561 let expected_oid = ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap();
562 assert_eq!(
563 val,
564 PropertyValue::List(vec![PropertyValue::ObjectIdentifier(expected_oid)])
565 );
566 }
567
568 #[test]
569 fn object_list_array_index() {
570 let dev = make_device();
571 let val = dev
573 .read_property(PropertyIdentifier::OBJECT_LIST, Some(0))
574 .unwrap();
575 assert_eq!(val, PropertyValue::Unsigned(1));
576
577 let val = dev
579 .read_property(PropertyIdentifier::OBJECT_LIST, Some(1))
580 .unwrap();
581 let expected_oid = ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap();
582 assert_eq!(val, PropertyValue::ObjectIdentifier(expected_oid));
583
584 let result = dev.read_property(PropertyIdentifier::OBJECT_LIST, Some(2));
586 assert!(result.is_err());
587 }
588
589 #[test]
590 fn set_object_list() {
591 let mut dev = make_device();
592 let dev_oid = dev.object_identifier();
593 let ai1 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
594 let ai2 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 2).unwrap();
595 dev.set_object_list(vec![dev_oid, ai1, ai2]);
596
597 let val = dev
599 .read_property(PropertyIdentifier::OBJECT_LIST, None)
600 .unwrap();
601 assert_eq!(
602 val,
603 PropertyValue::List(vec![
604 PropertyValue::ObjectIdentifier(dev_oid),
605 PropertyValue::ObjectIdentifier(ai1),
606 PropertyValue::ObjectIdentifier(ai2),
607 ])
608 );
609
610 let count = dev
612 .read_property(PropertyIdentifier::OBJECT_LIST, Some(0))
613 .unwrap();
614 assert_eq!(count, PropertyValue::Unsigned(3));
615 }
616
617 #[test]
618 fn property_list_contains_expected() {
619 let dev = make_device();
620 let props = dev.property_list();
621 assert!(props.contains(&PropertyIdentifier::OBJECT_IDENTIFIER));
622 assert!(props.contains(&PropertyIdentifier::OBJECT_NAME));
623 assert!(props.contains(&PropertyIdentifier::OBJECT_TYPE));
624 assert!(props.contains(&PropertyIdentifier::VENDOR_NAME));
625 assert!(props.contains(&PropertyIdentifier::OBJECT_LIST));
626 assert!(props.contains(&PropertyIdentifier::PROPERTY_LIST));
627 assert!(props.contains(&PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED));
628 assert!(props.contains(&PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED));
629 }
630
631 #[test]
632 fn read_protocol_object_types_supported() {
633 let dev = make_device();
634 let val = dev
635 .read_property(PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED, None)
636 .unwrap();
637 match val {
638 PropertyValue::BitString { unused_bits, data } => {
639 assert_eq!(unused_bits, 1);
640 assert_eq!(data.len(), 8);
641 assert_eq!(data[0], 0xFF);
643 assert_eq!(data[1], 0xFF);
645 assert_eq!(data[2], 0xFF);
647 assert_eq!(data[3], 0xFF);
649 assert_eq!(data[4], 0xFD);
651 assert_eq!(data[5], 0xFF);
653 assert_eq!(data[6], 0xFF);
655 assert_eq!(data[7], 0xFE);
657 }
658 _ => panic!("Expected BitString"),
659 }
660 }
661
662 #[test]
663 fn read_protocol_services_supported() {
664 let dev = make_device();
665 let val = dev
666 .read_property(PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED, None)
667 .unwrap();
668 match val {
669 PropertyValue::BitString { unused_bits, data } => {
670 assert_eq!(unused_bits, 7);
671 assert_eq!(data.len(), 6);
672 assert_eq!(data[0], 0xA4);
674 assert_eq!(data[1], 0x0B);
676 assert_eq!(data[4], 0x80);
678 }
679 _ => panic!("Expected BitString"),
680 }
681 }
682
683 #[test]
684 fn active_cov_subscriptions_default_empty() {
685 let dev = make_device();
686 let val = dev
687 .read_property(PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS, None)
688 .unwrap();
689 assert_eq!(val, PropertyValue::List(vec![]));
690 }
691
692 #[test]
693 fn active_cov_subscriptions_in_property_list() {
694 let dev = make_device();
695 assert!(dev
696 .property_list()
697 .contains(&PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS));
698 }
699
700 #[test]
701 fn active_cov_subscriptions_after_add() {
702 use bacnet_types::constructed::{
703 BACnetCOVSubscription, BACnetObjectPropertyReference, BACnetRecipient,
704 BACnetRecipientProcess,
705 };
706
707 let mut dev = make_device();
708 let dev_oid = ObjectIdentifier::new(ObjectType::DEVICE, 200).unwrap();
709 let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
710
711 dev.add_cov_subscription(BACnetCOVSubscription {
712 recipient: BACnetRecipientProcess {
713 recipient: BACnetRecipient::Device(dev_oid),
714 process_identifier: 7,
715 },
716 monitored_property_reference: BACnetObjectPropertyReference::new(
717 ai_oid,
718 PropertyIdentifier::PRESENT_VALUE.to_raw(),
719 ),
720 issue_confirmed_notifications: true,
721 time_remaining: 300,
722 cov_increment: Some(0.5),
723 });
724
725 let val = dev
726 .read_property(PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS, None)
727 .unwrap();
728 match val {
729 PropertyValue::List(subs) => {
730 assert_eq!(subs.len(), 1);
731 match &subs[0] {
732 PropertyValue::List(entry) => {
733 assert_eq!(entry.len(), 5); assert_eq!(entry[0], PropertyValue::ObjectIdentifier(ai_oid));
735 assert_eq!(entry[1], PropertyValue::Unsigned(7));
736 assert_eq!(entry[2], PropertyValue::Boolean(true));
737 assert_eq!(entry[3], PropertyValue::Unsigned(300));
738 assert_eq!(entry[4], PropertyValue::Real(0.5));
739 }
740 _ => panic!("Expected List entry"),
741 }
742 }
743 _ => panic!("Expected List"),
744 }
745 }
746
747 #[test]
748 fn active_cov_subscriptions_without_increment() {
749 use bacnet_types::constructed::{
750 BACnetCOVSubscription, BACnetObjectPropertyReference, BACnetRecipient,
751 BACnetRecipientProcess,
752 };
753
754 let mut dev = make_device();
755 let dev_oid = ObjectIdentifier::new(ObjectType::DEVICE, 50).unwrap();
756 let bv_oid = ObjectIdentifier::new(ObjectType::BINARY_VALUE, 3).unwrap();
757
758 dev.add_cov_subscription(BACnetCOVSubscription {
759 recipient: BACnetRecipientProcess {
760 recipient: BACnetRecipient::Device(dev_oid),
761 process_identifier: 1,
762 },
763 monitored_property_reference: BACnetObjectPropertyReference::new(
764 bv_oid,
765 PropertyIdentifier::PRESENT_VALUE.to_raw(),
766 ),
767 issue_confirmed_notifications: false,
768 time_remaining: 0,
769 cov_increment: None,
770 });
771
772 let val = dev
773 .read_property(PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS, None)
774 .unwrap();
775 match val {
776 PropertyValue::List(subs) => {
777 assert_eq!(subs.len(), 1);
778 match &subs[0] {
779 PropertyValue::List(entry) => {
780 assert_eq!(entry.len(), 4); assert_eq!(entry[2], PropertyValue::Boolean(false));
782 }
783 _ => panic!("Expected List entry"),
784 }
785 }
786 _ => panic!("Expected List"),
787 }
788 }
789
790 #[test]
791 fn active_cov_subscriptions_write_denied() {
792 let mut dev = make_device();
793 let result = dev.write_property(
794 PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS,
795 None,
796 PropertyValue::List(vec![]),
797 None,
798 );
799 assert!(result.is_err());
800 }
801
802 #[test]
803 fn set_active_cov_subscriptions_replaces() {
804 use bacnet_types::constructed::{
805 BACnetCOVSubscription, BACnetObjectPropertyReference, BACnetRecipient,
806 BACnetRecipientProcess,
807 };
808
809 let mut dev = make_device();
810 let dev_oid = ObjectIdentifier::new(ObjectType::DEVICE, 10).unwrap();
811 let ai1 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
812 let ai2 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 2).unwrap();
813
814 let sub1 = BACnetCOVSubscription {
816 recipient: BACnetRecipientProcess {
817 recipient: BACnetRecipient::Device(dev_oid),
818 process_identifier: 1,
819 },
820 monitored_property_reference: BACnetObjectPropertyReference::new(
821 ai1,
822 PropertyIdentifier::PRESENT_VALUE.to_raw(),
823 ),
824 issue_confirmed_notifications: true,
825 time_remaining: 100,
826 cov_increment: None,
827 };
828 let sub2 = BACnetCOVSubscription {
829 recipient: BACnetRecipientProcess {
830 recipient: BACnetRecipient::Device(dev_oid),
831 process_identifier: 2,
832 },
833 monitored_property_reference: BACnetObjectPropertyReference::new(
834 ai2,
835 PropertyIdentifier::PRESENT_VALUE.to_raw(),
836 ),
837 issue_confirmed_notifications: false,
838 time_remaining: 200,
839 cov_increment: Some(1.0),
840 };
841 dev.set_active_cov_subscriptions(vec![sub1, sub2]);
842
843 let val = dev
844 .read_property(PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS, None)
845 .unwrap();
846 match val {
847 PropertyValue::List(subs) => assert_eq!(subs.len(), 2),
848 _ => panic!("Expected List"),
849 }
850
851 dev.set_active_cov_subscriptions(vec![]);
853 let val = dev
854 .read_property(PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS, None)
855 .unwrap();
856 assert_eq!(val, PropertyValue::List(vec![]));
857 }
858
859 #[test]
860 fn compute_object_types_supported_known_inputs() {
861 assert_eq!(compute_object_types_supported(&[0]), vec![0x80]);
862 assert_eq!(compute_object_types_supported(&[8]), vec![0x00, 0x80]);
863 assert_eq!(
864 compute_object_types_supported(&[0, 1, 2, 3, 4, 5]),
865 vec![0xFC]
866 );
867 assert_eq!(compute_object_types_supported(&[]), vec![0x00]);
868 }
869
870 #[test]
871 fn compute_object_types_supported_old_bits_preserved() {
872 let old_types: Vec<u32> = vec![0, 1, 2, 3, 4, 5, 8, 13, 14, 19];
873 let bs = compute_object_types_supported(&old_types);
874 assert_eq!(bs[0], 0xFC);
875 assert_eq!(bs[1], 0x86);
876 assert_eq!(bs[2], 0x10);
877 }
878
879 #[test]
880 fn device_protocol_object_types_has_new_bits() {
881 let dev = DeviceObject::new(DeviceConfig {
882 instance: 1,
883 name: "Test".into(),
884 ..DeviceConfig::default()
885 })
886 .unwrap();
887 let val = dev
888 .read_property(PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED, None)
889 .unwrap();
890 let bits = match val {
891 PropertyValue::BitString { data, .. } => data,
892 _ => panic!("Expected BitString"),
893 };
894 assert!(bits.len() >= 8, "bitstring should cover types up to 62");
895 assert_eq!(bits[0] & 0xFC, 0xFC, "AI/AO/AV/BI/BO/BV");
896 assert_ne!(bits[1] & 0x80, 0, "Device (8)");
897 assert_ne!(bits[1] & 0x04, 0, "MSI (13)");
898 assert_ne!(bits[1] & 0x02, 0, "MSO (14)");
899 assert_ne!(bits[2] & 0x10, 0, "MSV (19)");
900 assert_ne!(bits[0] & 0x03, 0, "Calendar(6) and Command(7)");
901 assert_ne!(bits[3] & 0x80, 0, "Accumulator (24)");
902 assert_ne!(bits[7] & 0x80, 0, "NetworkPort (56)");
903 }
904}