1use std::collections::BTreeMap;
7use std::fmt;
8
9use bacnet_encoding::primitives::charset;
10use bacnet_objects::database::ObjectDatabase;
11use bacnet_types::bitstring::ServicesSupported;
12use bacnet_types::enums::{ObjectType, PropertyIdentifier, ServiceSupported};
13use bacnet_types::primitives::PropertyValue;
14
15use crate::server::ServerConfig;
16
17const PROPERTY_CAPABILITIES_EXPLANATION: &str = "Property rows aggregate configured instances: a row or access flag means at least one instance supports it. Actual availability and access depend on the concrete object. Optional is the metadata conformance classification; a required declaration wins, and absent rows do not vote.";
18
19const CREATION_ONLY_LABEL: &str = "Whole value set only by CreateObject";
23
24fn access_text(row: &PropertySupport) -> String {
29 match row.written_through {
30 Some(through) => format!(
31 "{} (resized through {through}: a whole write or its size at index 0)",
32 row.access
33 ),
34 None => row.access.to_string(),
35 }
36}
37
38fn creation_only_names(support: &ObjectTypeSupport) -> Option<String> {
41 let names: Vec<_> = support
42 .creation_only_properties
43 .iter()
44 .map(ToString::to_string)
45 .collect();
46 (!names.is_empty()).then(|| names.join(", "))
47}
48
49#[derive(Debug, Clone)]
53pub struct Pics {
54 pub vendor_info: VendorInfo,
56 pub device_profile: DeviceProfile,
58 pub supported_object_types: Vec<ObjectTypeSupport>,
60 pub supported_services: Vec<ServiceSupport>,
62 pub data_link_layers: Vec<DataLinkSupport>,
64 pub network_layer: NetworkLayerSupport,
66 pub character_sets: Vec<CharacterSet>,
68 pub special_functionality: Vec<String>,
70}
71
72#[derive(Debug, Clone)]
74pub struct VendorInfo {
75 pub vendor_id: u16,
77 pub vendor_name: String,
79 pub model_name: String,
81 pub firmware_revision: String,
83 pub application_software_version: String,
85 pub protocol_version: u16,
87 pub protocol_revision: u16,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93pub enum DeviceProfile {
94 BAac,
96 BAsc,
98 BOws,
100 BBc,
102 BOp,
104 BRouter,
106 BGw,
108 BSc,
110 Custom(String),
112}
113
114impl fmt::Display for DeviceProfile {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 match self {
117 Self::BAac => f.write_str("B-AAC"),
118 Self::BAsc => f.write_str("B-ASC"),
119 Self::BOws => f.write_str("B-OWS"),
120 Self::BBc => f.write_str("B-BC"),
121 Self::BOp => f.write_str("B-OP"),
122 Self::BRouter => f.write_str("B-ROUTER"),
123 Self::BGw => f.write_str("B-GW"),
124 Self::BSc => f.write_str("B-SC"),
125 Self::Custom(s) => f.write_str(s),
126 }
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub struct PropertyAccess {
135 pub readable: bool,
137 pub writable: bool,
139 pub optional: bool,
141}
142
143impl fmt::Display for PropertyAccess {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 let r = if self.readable { "R" } else { "" };
146 let w = if self.writable { "W" } else { "" };
147 let o = if self.optional { "O" } else { "" };
148 write!(f, "{r}{w}{o}")
149 }
150}
151
152#[derive(Debug, Clone)]
154pub struct PropertySupport {
155 pub property_id: PropertyIdentifier,
157 pub access: PropertyAccess,
159 pub written_through: Option<PropertyIdentifier>,
163}
164
165#[derive(Debug, Clone)]
167pub struct ObjectTypeSupport {
168 pub object_type: ObjectType,
170 pub createable: bool,
172 pub creation_only_properties: Vec<PropertyIdentifier>,
178 pub deleteable: bool,
180 pub supported_properties: Vec<PropertySupport>,
182}
183
184#[derive(Debug, Clone)]
186pub struct ServiceSupport {
187 pub service_name: String,
189 pub initiator: bool,
191 pub executor: bool,
193}
194
195#[derive(Debug, Clone, PartialEq, Eq)]
197pub enum DataLinkSupport {
198 BipV4,
200 BipV6,
202 Mstp,
204 Ethernet,
206 BacnetSc,
208}
209
210impl fmt::Display for DataLinkSupport {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 match self {
213 Self::BipV4 => f.write_str("BACnet/IP (Annex J)"),
214 Self::BipV6 => f.write_str("BACnet/IPv6 (Annex U)"),
215 Self::Mstp => f.write_str("MS/TP (Clause 9)"),
216 Self::Ethernet => f.write_str("BACnet Ethernet (Clause 7)"),
217 Self::BacnetSc => f.write_str("BACnet/SC (Annex AB)"),
218 }
219 }
220}
221
222#[derive(Debug, Clone)]
224pub struct NetworkLayerSupport {
225 pub router: bool,
227 pub bbmd: bool,
229 pub foreign_device: bool,
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
240#[repr(u8)]
241pub enum CharacterSet {
242 Utf8 = charset::UTF8,
244 IbmMicrosoftDbcs = charset::IBM_MICROSOFT_DBCS,
246 JisX0208 = charset::JIS_X_0208,
248 Ucs4 = charset::UCS4,
250 Ucs2 = charset::UCS2,
252 Iso8859_1 = charset::ISO_8859_1,
254}
255
256impl CharacterSet {
257 pub const ALL: [Self; 6] = [
259 Self::Utf8,
260 Self::IbmMicrosoftDbcs,
261 Self::JisX0208,
262 Self::Ucs4,
263 Self::Ucs2,
264 Self::Iso8859_1,
265 ];
266
267 pub const fn code(self) -> u8 {
269 self as u8
270 }
271}
272
273impl fmt::Display for CharacterSet {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 f.write_str(match self {
276 Self::Utf8 => "ISO 10646 (UTF-8)",
277 Self::IbmMicrosoftDbcs => "IBM/Microsoft DBCS",
278 Self::JisX0208 => "JIS X 0208",
279 Self::Ucs4 => "ISO 10646 (UCS-4)",
280 Self::Ucs2 => "ISO 10646 (UCS-2)",
281 Self::Iso8859_1 => "ISO 8859-1",
282 })
283 }
284}
285
286#[derive(Debug, Clone)]
290pub struct PicsConfig {
291 pub vendor_name: String,
293 pub model_name: String,
295 pub firmware_revision: String,
297 pub application_software_version: String,
299 pub protocol_version: u16,
301 pub protocol_revision: u16,
303 pub device_profile: DeviceProfile,
305 pub data_link_layers: Vec<DataLinkSupport>,
307 pub network_layer: NetworkLayerSupport,
309 pub character_sets: Vec<CharacterSet>,
311 pub special_functionality: Vec<String>,
313}
314
315impl Default for PicsConfig {
316 fn default() -> Self {
317 Self {
318 vendor_name: String::new(),
319 model_name: String::new(),
320 firmware_revision: String::new(),
321 application_software_version: String::new(),
322 protocol_version: 1,
323 protocol_revision: 24,
324 device_profile: DeviceProfile::BAsc,
325 data_link_layers: vec![DataLinkSupport::BipV4],
326 network_layer: NetworkLayerSupport {
327 router: false,
328 bbmd: false,
329 foreign_device: false,
330 },
331 character_sets: vec![CharacterSet::Utf8],
332 special_functionality: Vec::new(),
333 }
334 }
335}
336
337pub struct PicsGenerator<'a> {
343 db: &'a ObjectDatabase,
344 server_config: &'a ServerConfig,
345 pics_config: &'a PicsConfig,
346 served: bool,
347}
348
349impl<'a> PicsGenerator<'a> {
350 pub fn new(
352 db: &'a ObjectDatabase,
353 server_config: &'a ServerConfig,
354 pics_config: &'a PicsConfig,
355 ) -> Self {
356 Self {
357 db,
358 server_config,
359 pics_config,
360 served: false,
361 }
362 }
363
364 pub(crate) fn for_server(mut self) -> Self {
365 self.served = true;
366 self
367 }
368
369 pub fn generate(&self) -> Pics {
371 Pics {
372 vendor_info: self.build_vendor_info(),
373 device_profile: self.pics_config.device_profile.clone(),
374 supported_object_types: self.build_object_types(),
375 supported_services: self.build_services(),
376 data_link_layers: self.pics_config.data_link_layers.clone(),
377 network_layer: self.pics_config.network_layer.clone(),
378 character_sets: self.pics_config.character_sets.clone(),
379 special_functionality: self.pics_config.special_functionality.clone(),
380 }
381 }
382
383 fn build_vendor_info(&self) -> VendorInfo {
384 VendorInfo {
385 vendor_id: self.server_config.vendor_id,
386 vendor_name: self.pics_config.vendor_name.clone(),
387 model_name: self.pics_config.model_name.clone(),
388 firmware_revision: self.pics_config.firmware_revision.clone(),
389 application_software_version: self.pics_config.application_software_version.clone(),
390 protocol_version: self.pics_config.protocol_version,
391 protocol_revision: self.pics_config.protocol_revision,
392 }
393 }
394
395 fn build_object_types(&self) -> Vec<ObjectTypeSupport> {
396 let mut by_type: BTreeMap<u32, Vec<&dyn bacnet_objects::traits::BACnetObject>> =
397 BTreeMap::new();
398 for (_oid, obj) in self.db.iter_objects() {
399 by_type
400 .entry(obj.object_identifier().object_type().to_raw())
401 .or_default()
402 .push(obj);
403 }
404
405 let mut result = Vec::with_capacity(by_type.len());
406 for (raw_type, objects) in &by_type {
407 let object_type = ObjectType::from_raw(*raw_type);
408 let representative = objects[0];
409 let view = (self.served && object_type == ObjectType::DEVICE).then(|| {
410 crate::device_view::DeviceReadContext::new(
411 self.db,
412 crate::device_view::DeviceExecution::FullServer,
413 )
414 });
415 let supported_properties =
416 Self::union_property_support(objects.iter().flat_map(|object| {
417 if let Some(view) = &view {
418 Self::object_property_support(&view.object(*object))
419 } else {
420 Self::object_property_support(*object)
421 }
422 }));
423
424 let createable = representative.is_createable();
428 let deleteable = representative.is_deleteable();
429 let written_through = |property: &PropertyIdentifier| {
430 supported_properties
431 .iter()
432 .any(|row| row.property_id == *property && row.written_through.is_some())
433 };
434 let creation_only_properties = if createable {
435 let mut properties = representative.creation_only_properties().to_vec();
436 properties.retain(|property| !written_through(property));
437 properties
438 } else {
439 Vec::new()
440 };
441
442 result.push(ObjectTypeSupport {
443 object_type,
444 createable,
445 creation_only_properties,
446 deleteable,
447 supported_properties,
448 });
449 }
450 result
451 }
452
453 fn object_property_support(
454 object: &dyn bacnet_objects::traits::BACnetObject,
455 ) -> Vec<PropertySupport> {
456 let metadata = object.property_metadata();
457 if metadata.is_empty() {
458 let all_props = object.property_list();
459 let required = object.required_properties();
460 all_props
461 .iter()
462 .map(|&property_id| PropertySupport {
463 property_id,
464 access: PropertyAccess {
465 readable: true,
466 writable: object.is_writable_property(property_id),
467 optional: !required.contains(&property_id),
468 },
469 written_through: None,
470 })
471 .collect()
472 } else {
473 metadata
474 .iter()
475 .map(|row| PropertySupport {
476 property_id: row.property_identifier,
477 access: PropertyAccess {
478 readable: true,
479 writable: row.write_capability.is_writable(),
480 optional: !row.is_required(),
481 },
482 written_through: row.write_capability.written_through(),
483 })
484 .collect()
485 }
486 }
487
488 fn union_property_support(
489 rows: impl IntoIterator<Item = PropertySupport>,
490 ) -> Vec<PropertySupport> {
491 let mut properties: BTreeMap<u32, PropertySupport> = BTreeMap::new();
495 for row in rows {
496 properties
497 .entry(row.property_id.to_raw())
498 .and_modify(|existing| {
499 existing.access.readable |= row.access.readable;
500 existing.access.writable |= row.access.writable;
501 existing.access.optional &= row.access.optional;
502 existing.written_through = existing.written_through.or(row.written_through);
503 })
504 .or_insert(row);
505 }
506 properties.into_values().collect()
507 }
508
509 const INITIATED_SERVICES: &'static [ServiceSupported] = &[
514 ServiceSupported::I_AM,
515 ServiceSupported::I_HAVE,
516 ServiceSupported::CONFIRMED_COV_NOTIFICATION,
517 ServiceSupported::CONFIRMED_EVENT_NOTIFICATION,
518 ServiceSupported::UNCONFIRMED_COV_NOTIFICATION,
519 ServiceSupported::UNCONFIRMED_EVENT_NOTIFICATION,
520 ServiceSupported::CONFIRMED_COV_NOTIFICATION_MULTIPLE,
521 ServiceSupported::UNCONFIRMED_COV_NOTIFICATION_MULTIPLE,
522 ];
523
524 fn build_services(&self) -> Vec<ServiceSupport> {
525 let effective_executed = (!self.served)
530 .then(|| {
531 let device = self.db.get(&self.db.selected_device()?)?;
532 match device
533 .read_property(PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED, None)
534 .ok()?
535 {
536 PropertyValue::BitString { data, .. } => {
537 Some(ServicesSupported::from_bacnet(&data))
538 }
539 _ => None,
540 }
541 })
542 .flatten();
543 let mut service_map: BTreeMap<&'static str, (bool, bool)> = BTreeMap::new();
544 let clock_available = self.db.clock_frame().is_some();
545 let executed: Box<dyn Iterator<Item = ServiceSupported> + '_> = match &effective_executed {
546 Some(services) => Box::new(services.iter()),
547 None => {
548 Box::new(crate::device_view::DeviceExecution::FullServer.services(clock_available))
549 }
550 };
551 for service in executed {
552 service_map
553 .entry(service_display_name(service))
554 .or_default()
555 .1 = true;
556 }
557 for service in Self::INITIATED_SERVICES {
558 service_map
559 .entry(service_display_name(*service))
560 .or_default()
561 .0 = true;
562 }
563
564 service_map
565 .into_iter()
566 .map(|(name, (initiator, executor))| ServiceSupport {
567 service_name: name.to_string(),
568 initiator,
569 executor,
570 })
571 .collect()
572 }
573}
574
575impl Pics {
578 pub fn generate_text(&self) -> String {
580 let mut out = String::with_capacity(4096);
581
582 out.push_str("=== BACnet Protocol Implementation Conformance Statement (PICS) ===\n");
583 out.push_str(" Per ASHRAE 135-2020 Annex A\n\n");
584
585 out.push_str("--- Vendor Information ---\n");
586 out.push_str(&format!(
587 "Vendor ID: {}\n",
588 self.vendor_info.vendor_id
589 ));
590 out.push_str(&format!(
591 "Vendor Name: {}\n",
592 self.vendor_info.vendor_name
593 ));
594 out.push_str(&format!(
595 "Model Name: {}\n",
596 self.vendor_info.model_name
597 ));
598 out.push_str(&format!(
599 "Firmware Revision: {}\n",
600 self.vendor_info.firmware_revision
601 ));
602 out.push_str(&format!(
603 "Application Software Version: {}\n",
604 self.vendor_info.application_software_version
605 ));
606 out.push_str(&format!(
607 "Protocol Version: {}\n",
608 self.vendor_info.protocol_version
609 ));
610 out.push_str(&format!(
611 "Protocol Revision: {}\n\n",
612 self.vendor_info.protocol_revision
613 ));
614
615 out.push_str("--- BACnet Device Profile ---\n");
616 out.push_str(&format!("Profile: {}\n\n", self.device_profile));
617
618 out.push_str("--- Supported Object Types ---\n");
619 out.push_str(PROPERTY_CAPABILITIES_EXPLANATION);
620 out.push('\n');
621 for ot in &self.supported_object_types {
622 out.push_str(&format!(
623 "\n Object Type: {} (createable={}, deleteable={})\n",
624 ot.object_type, ot.createable, ot.deleteable
625 ));
626 if let Some(names) = creation_only_names(ot) {
627 out.push_str(&format!(" {CREATION_ONLY_LABEL}: {names}\n"));
628 }
629 out.push_str(" Properties:\n");
630 for prop in &ot.supported_properties {
631 out.push_str(&format!(
632 " {:<40} {}\n",
633 prop.property_id.to_string(),
634 access_text(prop)
635 ));
636 }
637 }
638 out.push('\n');
639
640 out.push_str("--- Supported Services ---\n");
641 out.push_str(&format!(
642 " {:<45} {:>9} {:>9}\n",
643 "Service", "Initiator", "Executor"
644 ));
645 out.push_str(&format!(" {:-<45} {:-<9} {:-<9}\n", "", "", ""));
646 for svc in &self.supported_services {
647 let init = if svc.initiator { "Yes" } else { "No" };
648 let exec = if svc.executor { "Yes" } else { "No" };
649 out.push_str(&format!(
650 " {:<45} {:>9} {:>9}\n",
651 svc.service_name, init, exec
652 ));
653 }
654 out.push('\n');
655
656 out.push_str("--- Data Link Layer Support ---\n");
657 for dl in &self.data_link_layers {
658 out.push_str(&format!(" {dl}\n"));
659 }
660 out.push('\n');
661
662 out.push_str("--- Network Layer Options ---\n");
663 out.push_str(&format!(
664 " Router: {}\n",
665 self.network_layer.router
666 ));
667 out.push_str(&format!(" BBMD: {}\n", self.network_layer.bbmd));
668 out.push_str(&format!(
669 " Foreign Device: {}\n\n",
670 self.network_layer.foreign_device
671 ));
672
673 out.push_str("--- Character Sets Supported ---\n");
674 for cs in &self.character_sets {
675 out.push_str(&format!(" {cs}\n"));
676 }
677 out.push('\n');
678
679 if !self.special_functionality.is_empty() {
680 out.push_str("--- Special Functionality ---\n");
681 for sf in &self.special_functionality {
682 out.push_str(&format!(" {sf}\n"));
683 }
684 out.push('\n');
685 }
686
687 out
688 }
689
690 pub fn generate_markdown(&self) -> String {
692 let mut out = String::with_capacity(4096);
693
694 out.push_str("# BACnet Protocol Implementation Conformance Statement (PICS)\n\n");
695 out.push_str("*Per ASHRAE 135-2020 Annex A*\n\n");
696
697 out.push_str("## Vendor Information\n\n");
698 out.push_str("| Field | Value |\n");
699 out.push_str("|-------|-------|\n");
700 out.push_str(&format!("| Vendor ID | {} |\n", self.vendor_info.vendor_id));
701 out.push_str(&format!(
702 "| Vendor Name | {} |\n",
703 self.vendor_info.vendor_name
704 ));
705 out.push_str(&format!(
706 "| Model Name | {} |\n",
707 self.vendor_info.model_name
708 ));
709 out.push_str(&format!(
710 "| Firmware Revision | {} |\n",
711 self.vendor_info.firmware_revision
712 ));
713 out.push_str(&format!(
714 "| Application Software Version | {} |\n",
715 self.vendor_info.application_software_version
716 ));
717 out.push_str(&format!(
718 "| Protocol Version | {} |\n",
719 self.vendor_info.protocol_version
720 ));
721 out.push_str(&format!(
722 "| Protocol Revision | {} |\n\n",
723 self.vendor_info.protocol_revision
724 ));
725
726 out.push_str("## BACnet Device Profile\n\n");
727 out.push_str(&format!("**{}**\n\n", self.device_profile));
728
729 out.push_str("## Supported Object Types\n\n");
730 out.push_str(PROPERTY_CAPABILITIES_EXPLANATION);
731 out.push_str("\n\n");
732 for ot in &self.supported_object_types {
733 out.push_str(&format!(
734 "### {}\n\n- Createable: {}\n- Deleteable: {}\n",
735 ot.object_type, ot.createable, ot.deleteable
736 ));
737 if let Some(names) = creation_only_names(ot) {
738 out.push_str(&format!("- {CREATION_ONLY_LABEL}: {names}\n"));
739 }
740 out.push('\n');
741 out.push_str("| Property | Access |\n");
742 out.push_str("|----------|--------|\n");
743 for prop in &ot.supported_properties {
744 out.push_str(&format!(
745 "| {} | {} |\n",
746 prop.property_id,
747 access_text(prop)
748 ));
749 }
750 out.push('\n');
751 }
752
753 out.push_str("## Supported Services\n\n");
754 out.push_str("| Service | Initiator | Executor |\n");
755 out.push_str("|---------|-----------|----------|\n");
756 for svc in &self.supported_services {
757 let init = if svc.initiator { "Yes" } else { "No" };
758 let exec = if svc.executor { "Yes" } else { "No" };
759 out.push_str(&format!("| {} | {} | {} |\n", svc.service_name, init, exec));
760 }
761 out.push('\n');
762
763 out.push_str("## Data Link Layer Support\n\n");
764 for dl in &self.data_link_layers {
765 out.push_str(&format!("- {dl}\n"));
766 }
767 out.push('\n');
768
769 out.push_str("## Network Layer Options\n\n");
770 out.push_str("| Feature | Supported |\n");
771 out.push_str("|---------|-----------|\n");
772 out.push_str(&format!("| Router | {} |\n", self.network_layer.router));
773 out.push_str(&format!("| BBMD | {} |\n", self.network_layer.bbmd));
774 out.push_str(&format!(
775 "| Foreign Device | {} |\n\n",
776 self.network_layer.foreign_device
777 ));
778
779 out.push_str("## Character Sets Supported\n\n");
780 for cs in &self.character_sets {
781 out.push_str(&format!("- {cs}\n"));
782 }
783 out.push('\n');
784
785 if !self.special_functionality.is_empty() {
786 out.push_str("## Special Functionality\n\n");
787 for sf in &self.special_functionality {
788 out.push_str(&format!("- {sf}\n"));
789 }
790 out.push('\n');
791 }
792
793 out
794 }
795}
796
797pub fn generate_pics(
803 db: &ObjectDatabase,
804 server_config: &ServerConfig,
805 pics_config: &PicsConfig,
806) -> Pics {
807 PicsGenerator::new(db, server_config, pics_config).generate()
808}
809
810fn service_display_name(service: ServiceSupported) -> &'static str {
814 match service.to_raw() {
815 0 => "AcknowledgeAlarm",
816 1 => "ConfirmedCOVNotification",
817 2 => "ConfirmedEventNotification",
818 3 => "GetAlarmSummary",
819 4 => "GetEnrollmentSummary",
820 5 => "SubscribeCOV",
821 6 => "AtomicReadFile",
822 7 => "AtomicWriteFile",
823 8 => "AddListElement",
824 9 => "RemoveListElement",
825 10 => "CreateObject",
826 11 => "DeleteObject",
827 12 => "ReadProperty",
828 14 => "ReadPropertyMultiple",
829 15 => "WriteProperty",
830 16 => "WritePropertyMultiple",
831 17 => "DeviceCommunicationControl",
832 18 => "ConfirmedPrivateTransfer",
833 19 => "ConfirmedTextMessage",
834 20 => "ReinitializeDevice",
835 21 => "VT-Open",
836 22 => "VT-Close",
837 23 => "VT-Data",
838 26 => "I-Am",
839 27 => "I-Have",
840 28 => "UnconfirmedCOVNotification",
841 29 => "UnconfirmedEventNotification",
842 30 => "UnconfirmedPrivateTransfer",
843 31 => "UnconfirmedTextMessage",
844 32 => "TimeSynchronization",
845 33 => "WhoHas",
846 34 => "WhoIs",
847 35 => "ReadRange",
848 36 => "UTCTimeSynchronization",
849 37 => "LifeSafetyOperation",
850 38 => "SubscribeCOVProperty",
851 39 => "GetEventInformation",
852 40 => "WriteGroup",
853 41 => "SubscribeCOVPropertyMultiple",
854 42 => "ConfirmedCOVNotificationMultiple",
855 43 => "UnconfirmedCOVNotificationMultiple",
856 44 => "ConfirmedAuditNotification",
857 45 => "AuditLogQuery",
858 46 => "UnconfirmedAuditNotification",
859 47 => "Who-Am-I",
860 48 => "You-Are",
861 _ => "Unknown",
862 }
863}
864
865#[cfg(test)]
866mod tests;
867
868#[cfg(test)]
869mod property_metadata_tests;
870
871#[cfg(test)]
872mod acked_transitions_policy_tests;
873
874#[cfg(test)]
875mod truth_source_tests;
876
877#[cfg(test)]
878mod property_union_tests;
879
880#[cfg(test)]
881mod character_set_tests;
882
883#[cfg(test)]
884mod selected_device_tests;
885
886#[cfg(test)]
887mod creation_only_tests;