1use bacnet_types::constructed::BACnetDeviceObjectPropertyReference;
8use bacnet_types::enums::{ObjectType, PropertyIdentifier};
9use bacnet_types::error::Error;
10use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
11use std::borrow::Cow;
12
13use crate::common::{self, read_common_properties};
14use crate::traits::BACnetObject;
15
16pub struct GroupObject {
26 oid: ObjectIdentifier,
27 name: String,
28 description: String,
29 status_flags: StatusFlags,
30 out_of_service: bool,
31 reliability: u32,
32 pub list_of_group_members: Vec<ObjectIdentifier>,
34 pub present_value: Vec<PropertyValue>,
36}
37
38impl GroupObject {
39 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
41 let oid = ObjectIdentifier::new(ObjectType::GROUP, instance)?;
42 Ok(Self {
43 oid,
44 name: name.into(),
45 description: String::new(),
46 status_flags: StatusFlags::empty(),
47 out_of_service: false,
48 reliability: 0,
49 list_of_group_members: Vec::new(),
50 present_value: Vec::new(),
51 })
52 }
53
54 pub fn add_member(&mut self, oid: ObjectIdentifier) {
56 self.list_of_group_members.push(oid);
57 }
58
59 pub fn clear_members(&mut self) {
61 self.list_of_group_members.clear();
62 self.present_value.clear();
63 }
64}
65
66impl BACnetObject for GroupObject {
67 fn object_identifier(&self) -> ObjectIdentifier {
68 self.oid
69 }
70
71 fn object_name(&self) -> &str {
72 &self.name
73 }
74
75 fn read_property(
76 &self,
77 property: PropertyIdentifier,
78 array_index: Option<u32>,
79 ) -> Result<PropertyValue, Error> {
80 if let Some(result) = read_common_properties!(self, property, array_index) {
81 return result;
82 }
83 match property {
84 p if p == PropertyIdentifier::OBJECT_TYPE => {
85 Ok(PropertyValue::Enumerated(ObjectType::GROUP.to_raw()))
86 }
87 p if p == PropertyIdentifier::LIST_OF_GROUP_MEMBERS => Ok(PropertyValue::List(
88 self.list_of_group_members
89 .iter()
90 .map(|oid| PropertyValue::ObjectIdentifier(*oid))
91 .collect(),
92 )),
93 p if p == PropertyIdentifier::PRESENT_VALUE => {
94 Ok(PropertyValue::List(self.present_value.clone()))
95 }
96 _ => Err(common::unknown_property_error()),
97 }
98 }
99
100 fn write_property(
101 &mut self,
102 property: PropertyIdentifier,
103 _array_index: Option<u32>,
104 value: PropertyValue,
105 _priority: Option<u8>,
106 ) -> Result<(), Error> {
107 if let Some(result) =
108 common::write_out_of_service(&mut self.out_of_service, property, &value)
109 {
110 return result;
111 }
112 if let Some(result) = common::write_description(&mut self.description, property, &value) {
113 return result;
114 }
115 Err(common::write_access_denied_error())
116 }
117
118 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
119 static PROPS: &[PropertyIdentifier] = &[
120 PropertyIdentifier::OBJECT_IDENTIFIER,
121 PropertyIdentifier::OBJECT_NAME,
122 PropertyIdentifier::DESCRIPTION,
123 PropertyIdentifier::OBJECT_TYPE,
124 PropertyIdentifier::LIST_OF_GROUP_MEMBERS,
125 PropertyIdentifier::PRESENT_VALUE,
126 PropertyIdentifier::STATUS_FLAGS,
127 PropertyIdentifier::OUT_OF_SERVICE,
128 PropertyIdentifier::RELIABILITY,
129 ];
130 Cow::Borrowed(PROPS)
131 }
132}
133
134pub struct GlobalGroupObject {
144 oid: ObjectIdentifier,
145 name: String,
146 description: String,
147 status_flags: StatusFlags,
148 out_of_service: bool,
149 reliability: u32,
150 pub group_members: Vec<BACnetDeviceObjectPropertyReference>,
152 pub present_value: Vec<PropertyValue>,
154 pub group_member_names: Vec<String>,
156}
157
158impl GlobalGroupObject {
159 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
161 let oid = ObjectIdentifier::new(ObjectType::GLOBAL_GROUP, instance)?;
162 Ok(Self {
163 oid,
164 name: name.into(),
165 description: String::new(),
166 status_flags: StatusFlags::empty(),
167 out_of_service: false,
168 reliability: 0,
169 group_members: Vec::new(),
170 present_value: Vec::new(),
171 group_member_names: Vec::new(),
172 })
173 }
174}
175
176impl BACnetObject for GlobalGroupObject {
177 fn object_identifier(&self) -> ObjectIdentifier {
178 self.oid
179 }
180
181 fn object_name(&self) -> &str {
182 &self.name
183 }
184
185 fn read_property(
186 &self,
187 property: PropertyIdentifier,
188 array_index: Option<u32>,
189 ) -> Result<PropertyValue, Error> {
190 if let Some(result) = read_common_properties!(self, property, array_index) {
191 return result;
192 }
193 match property {
194 p if p == PropertyIdentifier::OBJECT_TYPE => {
195 Ok(PropertyValue::Enumerated(ObjectType::GLOBAL_GROUP.to_raw()))
196 }
197 p if p == PropertyIdentifier::GROUP_MEMBERS => Ok(PropertyValue::List(
198 self.group_members
199 .iter()
200 .map(|r| {
201 PropertyValue::List(vec![
202 PropertyValue::ObjectIdentifier(r.object_identifier),
203 PropertyValue::Unsigned(r.property_identifier as u64),
204 match r.property_array_index {
205 Some(idx) => PropertyValue::Unsigned(idx as u64),
206 None => PropertyValue::Null,
207 },
208 match r.device_identifier {
209 Some(dev) => PropertyValue::ObjectIdentifier(dev),
210 None => PropertyValue::Null,
211 },
212 ])
213 })
214 .collect(),
215 )),
216 p if p == PropertyIdentifier::PRESENT_VALUE => {
217 Ok(PropertyValue::List(self.present_value.clone()))
218 }
219 p if p == PropertyIdentifier::GROUP_MEMBER_NAMES => Ok(PropertyValue::List(
220 self.group_member_names
221 .iter()
222 .map(|n| PropertyValue::CharacterString(n.clone()))
223 .collect(),
224 )),
225 _ => Err(common::unknown_property_error()),
226 }
227 }
228
229 fn write_property(
230 &mut self,
231 property: PropertyIdentifier,
232 _array_index: Option<u32>,
233 value: PropertyValue,
234 _priority: Option<u8>,
235 ) -> Result<(), Error> {
236 if let Some(result) =
237 common::write_out_of_service(&mut self.out_of_service, property, &value)
238 {
239 return result;
240 }
241 if let Some(result) = common::write_description(&mut self.description, property, &value) {
242 return result;
243 }
244 Err(common::write_access_denied_error())
245 }
246
247 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
248 static PROPS: &[PropertyIdentifier] = &[
249 PropertyIdentifier::OBJECT_IDENTIFIER,
250 PropertyIdentifier::OBJECT_NAME,
251 PropertyIdentifier::DESCRIPTION,
252 PropertyIdentifier::OBJECT_TYPE,
253 PropertyIdentifier::GROUP_MEMBERS,
254 PropertyIdentifier::PRESENT_VALUE,
255 PropertyIdentifier::GROUP_MEMBER_NAMES,
256 PropertyIdentifier::STATUS_FLAGS,
257 PropertyIdentifier::OUT_OF_SERVICE,
258 PropertyIdentifier::RELIABILITY,
259 ];
260 Cow::Borrowed(PROPS)
261 }
262}
263
264pub struct StructuredViewObject {
274 oid: ObjectIdentifier,
275 name: String,
276 description: String,
277 status_flags: StatusFlags,
278 out_of_service: bool,
279 reliability: u32,
280 pub node_type: u32,
282 pub node_subtype: String,
284 pub subordinate_list: Vec<ObjectIdentifier>,
286 pub subordinate_annotations: Vec<String>,
288}
289
290impl StructuredViewObject {
291 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
293 let oid = ObjectIdentifier::new(ObjectType::STRUCTURED_VIEW, instance)?;
294 Ok(Self {
295 oid,
296 name: name.into(),
297 description: String::new(),
298 status_flags: StatusFlags::empty(),
299 out_of_service: false,
300 reliability: 0,
301 node_type: 0,
302 node_subtype: String::new(),
303 subordinate_list: Vec::new(),
304 subordinate_annotations: Vec::new(),
305 })
306 }
307
308 pub fn add_subordinate(&mut self, oid: ObjectIdentifier, annotation: impl Into<String>) {
310 self.subordinate_list.push(oid);
311 self.subordinate_annotations.push(annotation.into());
312 }
313}
314
315impl BACnetObject for StructuredViewObject {
316 fn object_identifier(&self) -> ObjectIdentifier {
317 self.oid
318 }
319
320 fn object_name(&self) -> &str {
321 &self.name
322 }
323
324 fn read_property(
325 &self,
326 property: PropertyIdentifier,
327 array_index: Option<u32>,
328 ) -> Result<PropertyValue, Error> {
329 if let Some(result) = read_common_properties!(self, property, array_index) {
330 return result;
331 }
332 match property {
333 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
334 ObjectType::STRUCTURED_VIEW.to_raw(),
335 )),
336 p if p == PropertyIdentifier::NODE_TYPE => {
337 Ok(PropertyValue::Enumerated(self.node_type))
338 }
339 p if p == PropertyIdentifier::NODE_SUBTYPE => {
340 Ok(PropertyValue::CharacterString(self.node_subtype.clone()))
341 }
342 p if p == PropertyIdentifier::SUBORDINATE_LIST => Ok(PropertyValue::List(
343 self.subordinate_list
344 .iter()
345 .map(|oid| PropertyValue::ObjectIdentifier(*oid))
346 .collect(),
347 )),
348 p if p == PropertyIdentifier::SUBORDINATE_ANNOTATIONS => Ok(PropertyValue::List(
349 self.subordinate_annotations
350 .iter()
351 .map(|a| PropertyValue::CharacterString(a.clone()))
352 .collect(),
353 )),
354 _ => Err(common::unknown_property_error()),
355 }
356 }
357
358 fn write_property(
359 &mut self,
360 property: PropertyIdentifier,
361 _array_index: Option<u32>,
362 value: PropertyValue,
363 _priority: Option<u8>,
364 ) -> Result<(), Error> {
365 if let Some(result) =
366 common::write_out_of_service(&mut self.out_of_service, property, &value)
367 {
368 return result;
369 }
370 if let Some(result) = common::write_description(&mut self.description, property, &value) {
371 return result;
372 }
373 Err(common::write_access_denied_error())
374 }
375
376 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
377 static PROPS: &[PropertyIdentifier] = &[
378 PropertyIdentifier::OBJECT_IDENTIFIER,
379 PropertyIdentifier::OBJECT_NAME,
380 PropertyIdentifier::DESCRIPTION,
381 PropertyIdentifier::OBJECT_TYPE,
382 PropertyIdentifier::NODE_TYPE,
383 PropertyIdentifier::NODE_SUBTYPE,
384 PropertyIdentifier::SUBORDINATE_LIST,
385 PropertyIdentifier::SUBORDINATE_ANNOTATIONS,
386 PropertyIdentifier::STATUS_FLAGS,
387 PropertyIdentifier::OUT_OF_SERVICE,
388 PropertyIdentifier::RELIABILITY,
389 ];
390 Cow::Borrowed(PROPS)
391 }
392}
393
394#[cfg(test)]
399mod tests {
400 use super::*;
401
402 #[test]
407 fn group_create() {
408 let g = GroupObject::new(1, "Group-1").unwrap();
409 assert_eq!(g.object_identifier().object_type(), ObjectType::GROUP);
410 assert_eq!(g.object_identifier().instance_number(), 1);
411 assert_eq!(g.object_name(), "Group-1");
412 }
413
414 #[test]
415 fn group_object_type() {
416 let g = GroupObject::new(1, "G").unwrap();
417 let val = g
418 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
419 .unwrap();
420 assert_eq!(val, PropertyValue::Enumerated(ObjectType::GROUP.to_raw()));
421 }
422
423 #[test]
424 fn group_add_members() {
425 let mut g = GroupObject::new(1, "G").unwrap();
426 let ai1 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
427 let ai2 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 2).unwrap();
428 g.add_member(ai1);
429 g.add_member(ai2);
430
431 let val = g
432 .read_property(PropertyIdentifier::LIST_OF_GROUP_MEMBERS, None)
433 .unwrap();
434 if let PropertyValue::List(items) = val {
435 assert_eq!(items.len(), 2);
436 assert_eq!(items[0], PropertyValue::ObjectIdentifier(ai1));
437 assert_eq!(items[1], PropertyValue::ObjectIdentifier(ai2));
438 } else {
439 panic!("Expected List");
440 }
441 }
442
443 #[test]
444 fn group_clear_members() {
445 let mut g = GroupObject::new(1, "G").unwrap();
446 let ai1 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
447 g.add_member(ai1);
448 assert_eq!(g.list_of_group_members.len(), 1);
449 g.clear_members();
450 assert!(g.list_of_group_members.is_empty());
451 }
452
453 #[test]
454 fn group_present_value_empty() {
455 let g = GroupObject::new(1, "G").unwrap();
456 let val = g
457 .read_property(PropertyIdentifier::PRESENT_VALUE, None)
458 .unwrap();
459 if let PropertyValue::List(items) = val {
460 assert!(items.is_empty());
461 } else {
462 panic!("Expected List");
463 }
464 }
465
466 #[test]
467 fn group_property_list() {
468 let g = GroupObject::new(1, "G").unwrap();
469 let props = g.property_list();
470 assert!(props.contains(&PropertyIdentifier::LIST_OF_GROUP_MEMBERS));
471 assert!(props.contains(&PropertyIdentifier::PRESENT_VALUE));
472 assert!(props.contains(&PropertyIdentifier::STATUS_FLAGS));
473 }
474
475 #[test]
480 fn global_group_create() {
481 let gg = GlobalGroupObject::new(1, "GG-1").unwrap();
482 assert_eq!(
483 gg.object_identifier().object_type(),
484 ObjectType::GLOBAL_GROUP
485 );
486 assert_eq!(gg.object_identifier().instance_number(), 1);
487 assert_eq!(gg.object_name(), "GG-1");
488 }
489
490 #[test]
491 fn global_group_object_type() {
492 let gg = GlobalGroupObject::new(1, "GG").unwrap();
493 let val = gg
494 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
495 .unwrap();
496 assert_eq!(
497 val,
498 PropertyValue::Enumerated(ObjectType::GLOBAL_GROUP.to_raw())
499 );
500 }
501
502 #[test]
503 fn global_group_members_empty() {
504 let gg = GlobalGroupObject::new(1, "GG").unwrap();
505 let val = gg
506 .read_property(PropertyIdentifier::GROUP_MEMBERS, None)
507 .unwrap();
508 if let PropertyValue::List(items) = val {
509 assert!(items.is_empty());
510 } else {
511 panic!("Expected List");
512 }
513 }
514
515 #[test]
516 fn global_group_member_names() {
517 let mut gg = GlobalGroupObject::new(1, "GG").unwrap();
518 gg.group_member_names.push("Temp Sensor".into());
519 gg.group_member_names.push("Humidity".into());
520
521 let val = gg
522 .read_property(PropertyIdentifier::GROUP_MEMBER_NAMES, None)
523 .unwrap();
524 if let PropertyValue::List(items) = val {
525 assert_eq!(items.len(), 2);
526 assert_eq!(
527 items[0],
528 PropertyValue::CharacterString("Temp Sensor".into())
529 );
530 assert_eq!(items[1], PropertyValue::CharacterString("Humidity".into()));
531 } else {
532 panic!("Expected List");
533 }
534 }
535
536 #[test]
537 fn global_group_property_list() {
538 let gg = GlobalGroupObject::new(1, "GG").unwrap();
539 let props = gg.property_list();
540 assert!(props.contains(&PropertyIdentifier::GROUP_MEMBERS));
541 assert!(props.contains(&PropertyIdentifier::PRESENT_VALUE));
542 assert!(props.contains(&PropertyIdentifier::GROUP_MEMBER_NAMES));
543 }
544
545 #[test]
550 fn structured_view_create() {
551 let sv = StructuredViewObject::new(1, "SV-1").unwrap();
552 assert_eq!(
553 sv.object_identifier().object_type(),
554 ObjectType::STRUCTURED_VIEW
555 );
556 assert_eq!(sv.object_identifier().instance_number(), 1);
557 assert_eq!(sv.object_name(), "SV-1");
558 }
559
560 #[test]
561 fn structured_view_object_type() {
562 let sv = StructuredViewObject::new(1, "SV").unwrap();
563 let val = sv
564 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
565 .unwrap();
566 assert_eq!(
567 val,
568 PropertyValue::Enumerated(ObjectType::STRUCTURED_VIEW.to_raw())
569 );
570 }
571
572 #[test]
573 fn structured_view_add_subordinates() {
574 let mut sv = StructuredViewObject::new(1, "SV").unwrap();
575 let ai1 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
576 let bi1 = ObjectIdentifier::new(ObjectType::BINARY_INPUT, 1).unwrap();
577 sv.add_subordinate(ai1, "Temperature");
578 sv.add_subordinate(bi1, "Occupancy");
579
580 let val = sv
581 .read_property(PropertyIdentifier::SUBORDINATE_LIST, None)
582 .unwrap();
583 if let PropertyValue::List(items) = val {
584 assert_eq!(items.len(), 2);
585 assert_eq!(items[0], PropertyValue::ObjectIdentifier(ai1));
586 assert_eq!(items[1], PropertyValue::ObjectIdentifier(bi1));
587 } else {
588 panic!("Expected List");
589 }
590
591 let ann = sv
592 .read_property(PropertyIdentifier::SUBORDINATE_ANNOTATIONS, None)
593 .unwrap();
594 if let PropertyValue::List(items) = ann {
595 assert_eq!(items.len(), 2);
596 assert_eq!(
597 items[0],
598 PropertyValue::CharacterString("Temperature".into())
599 );
600 assert_eq!(items[1], PropertyValue::CharacterString("Occupancy".into()));
601 } else {
602 panic!("Expected List");
603 }
604 }
605
606 #[test]
607 fn structured_view_node_type() {
608 let sv = StructuredViewObject::new(1, "SV").unwrap();
609 let val = sv
610 .read_property(PropertyIdentifier::NODE_TYPE, None)
611 .unwrap();
612 assert_eq!(val, PropertyValue::Enumerated(0));
613 }
614
615 #[test]
616 fn structured_view_node_subtype() {
617 let sv = StructuredViewObject::new(1, "SV").unwrap();
618 let val = sv
619 .read_property(PropertyIdentifier::NODE_SUBTYPE, None)
620 .unwrap();
621 assert_eq!(val, PropertyValue::CharacterString(String::new()));
622 }
623
624 #[test]
625 fn structured_view_property_list() {
626 let sv = StructuredViewObject::new(1, "SV").unwrap();
627 let props = sv.property_list();
628 assert!(props.contains(&PropertyIdentifier::NODE_TYPE));
629 assert!(props.contains(&PropertyIdentifier::NODE_SUBTYPE));
630 assert!(props.contains(&PropertyIdentifier::SUBORDINATE_LIST));
631 assert!(props.contains(&PropertyIdentifier::SUBORDINATE_ANNOTATIONS));
632 }
633}