bacnet_server/pics/
render.rs1use super::device_text::{markdown_cell, text_line};
4use super::{ObjectTypeSupport, Pics, PropertySupport};
5
6const 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.";
7
8const CREATION_ONLY_LABEL: &str = "Whole value set only by CreateObject";
12
13fn access_text(row: &PropertySupport) -> String {
18 match row.written_through {
19 Some(through) => format!(
20 "{} (resized through {through}: a whole write or its size at index 0)",
21 row.access
22 ),
23 None => row.access.to_string(),
24 }
25}
26
27fn creation_only_names(support: &ObjectTypeSupport) -> Option<String> {
30 let names: Vec<_> = support
31 .creation_only_properties
32 .iter()
33 .map(ToString::to_string)
34 .collect();
35 (!names.is_empty()).then(|| names.join(", "))
36}
37
38impl Pics {
39 pub fn generate_text(&self) -> String {
41 let mut out = String::with_capacity(4096);
42
43 out.push_str("=== BACnet Protocol Implementation Conformance Statement (PICS) ===\n");
44 out.push_str(" Per ASHRAE 135-2020 Annex A\n\n");
45
46 out.push_str("--- Vendor Information ---\n");
47 out.push_str(&format!(
48 "Vendor ID: {}\n",
49 self.vendor_info.vendor_id
50 ));
51 out.push_str(&format!(
52 "Vendor Name: {}\n",
53 text_line(&self.vendor_info.vendor_name)
54 ));
55 out.push_str(&format!(
56 "Model Name: {}\n",
57 text_line(&self.vendor_info.model_name)
58 ));
59 out.push_str(&format!(
60 "Firmware Revision: {}\n",
61 text_line(&self.vendor_info.firmware_revision)
62 ));
63 out.push_str(&format!(
64 "Application Software Version: {}\n",
65 text_line(&self.vendor_info.application_software_version)
66 ));
67 out.push_str(&format!(
68 "Protocol Version: {}\n",
69 self.vendor_info.protocol_version
70 ));
71 out.push_str(&format!(
72 "Protocol Revision: {}\n\n",
73 self.vendor_info.protocol_revision
74 ));
75
76 out.push_str("--- BACnet Device Profile ---\n");
77 out.push_str(&format!("Profile: {}\n\n", self.device_profile));
78
79 out.push_str("--- Supported Object Types ---\n");
80 out.push_str(PROPERTY_CAPABILITIES_EXPLANATION);
81 out.push('\n');
82 for ot in &self.supported_object_types {
83 out.push_str(&format!(
84 "\n Object Type: {} (createable={}, deleteable={})\n",
85 ot.object_type, ot.createable, ot.deleteable
86 ));
87 if let Some(names) = creation_only_names(ot) {
88 out.push_str(&format!(" {CREATION_ONLY_LABEL}: {names}\n"));
89 }
90 out.push_str(" Properties:\n");
91 for prop in &ot.supported_properties {
92 out.push_str(&format!(
93 " {:<40} {}\n",
94 prop.property_id.to_string(),
95 access_text(prop)
96 ));
97 }
98 }
99 out.push('\n');
100
101 out.push_str("--- Supported Services ---\n");
102 out.push_str(&format!(
103 " {:<45} {:>9} {:>9}\n",
104 "Service", "Initiator", "Executor"
105 ));
106 out.push_str(&format!(" {:-<45} {:-<9} {:-<9}\n", "", "", ""));
107 for svc in &self.supported_services {
108 let init = if svc.initiator { "Yes" } else { "No" };
109 let exec = if svc.executor { "Yes" } else { "No" };
110 out.push_str(&format!(
111 " {:<45} {:>9} {:>9}\n",
112 svc.service_name, init, exec
113 ));
114 }
115 out.push('\n');
116
117 out.push_str("--- Data Link Layer Support ---\n");
118 for dl in &self.data_link_layers {
119 out.push_str(&format!(" {dl}\n"));
120 }
121 out.push('\n');
122
123 out.push_str("--- Network Layer Options ---\n");
124 out.push_str(&format!(
125 " Router: {}\n",
126 self.network_layer.router
127 ));
128 out.push_str(&format!(" BBMD: {}\n", self.network_layer.bbmd));
129 out.push_str(&format!(
130 " Foreign Device: {}\n\n",
131 self.network_layer.foreign_device
132 ));
133
134 out.push_str("--- Character Sets Supported ---\n");
135 for cs in &self.character_sets {
136 out.push_str(&format!(" {cs}\n"));
137 }
138 out.push('\n');
139
140 if !self.special_functionality.is_empty() {
141 out.push_str("--- Special Functionality ---\n");
142 for sf in &self.special_functionality {
143 out.push_str(&format!(" {sf}\n"));
144 }
145 out.push('\n');
146 }
147
148 out
149 }
150
151 pub fn generate_markdown(&self) -> String {
153 let mut out = String::with_capacity(4096);
154
155 out.push_str("# BACnet Protocol Implementation Conformance Statement (PICS)\n\n");
156 out.push_str("*Per ASHRAE 135-2020 Annex A*\n\n");
157
158 out.push_str("## Vendor Information\n\n");
159 out.push_str("| Field | Value |\n");
160 out.push_str("|-------|-------|\n");
161 out.push_str(&format!("| Vendor ID | {} |\n", self.vendor_info.vendor_id));
162 out.push_str(&format!(
163 "| Vendor Name | {} |\n",
164 markdown_cell(&self.vendor_info.vendor_name)
165 ));
166 out.push_str(&format!(
167 "| Model Name | {} |\n",
168 markdown_cell(&self.vendor_info.model_name)
169 ));
170 out.push_str(&format!(
171 "| Firmware Revision | {} |\n",
172 markdown_cell(&self.vendor_info.firmware_revision)
173 ));
174 out.push_str(&format!(
175 "| Application Software Version | {} |\n",
176 markdown_cell(&self.vendor_info.application_software_version)
177 ));
178 out.push_str(&format!(
179 "| Protocol Version | {} |\n",
180 self.vendor_info.protocol_version
181 ));
182 out.push_str(&format!(
183 "| Protocol Revision | {} |\n\n",
184 self.vendor_info.protocol_revision
185 ));
186
187 out.push_str("## BACnet Device Profile\n\n");
188 out.push_str(&format!("**{}**\n\n", self.device_profile));
189
190 out.push_str("## Supported Object Types\n\n");
191 out.push_str(PROPERTY_CAPABILITIES_EXPLANATION);
192 out.push_str("\n\n");
193 for ot in &self.supported_object_types {
194 out.push_str(&format!(
195 "### {}\n\n- Createable: {}\n- Deleteable: {}\n",
196 ot.object_type, ot.createable, ot.deleteable
197 ));
198 if let Some(names) = creation_only_names(ot) {
199 out.push_str(&format!("- {CREATION_ONLY_LABEL}: {names}\n"));
200 }
201 out.push('\n');
202 out.push_str("| Property | Access |\n");
203 out.push_str("|----------|--------|\n");
204 for prop in &ot.supported_properties {
205 out.push_str(&format!(
206 "| {} | {} |\n",
207 prop.property_id,
208 access_text(prop)
209 ));
210 }
211 out.push('\n');
212 }
213
214 out.push_str("## Supported Services\n\n");
215 out.push_str("| Service | Initiator | Executor |\n");
216 out.push_str("|---------|-----------|----------|\n");
217 for svc in &self.supported_services {
218 let init = if svc.initiator { "Yes" } else { "No" };
219 let exec = if svc.executor { "Yes" } else { "No" };
220 out.push_str(&format!("| {} | {} | {} |\n", svc.service_name, init, exec));
221 }
222 out.push('\n');
223
224 out.push_str("## Data Link Layer Support\n\n");
225 for dl in &self.data_link_layers {
226 out.push_str(&format!("- {dl}\n"));
227 }
228 out.push('\n');
229
230 out.push_str("## Network Layer Options\n\n");
231 out.push_str("| Feature | Supported |\n");
232 out.push_str("|---------|-----------|\n");
233 out.push_str(&format!("| Router | {} |\n", self.network_layer.router));
234 out.push_str(&format!("| BBMD | {} |\n", self.network_layer.bbmd));
235 out.push_str(&format!(
236 "| Foreign Device | {} |\n\n",
237 self.network_layer.foreign_device
238 ));
239
240 out.push_str("## Character Sets Supported\n\n");
241 for cs in &self.character_sets {
242 out.push_str(&format!("- {cs}\n"));
243 }
244 out.push('\n');
245
246 if !self.special_functionality.is_empty() {
247 out.push_str("## Special Functionality\n\n");
248 for sf in &self.special_functionality {
249 out.push_str(&format!("- {sf}\n"));
250 }
251 out.push('\n');
252 }
253
254 out
255 }
256}