1use bacnet_objects::database::ObjectDatabase;
8use bacnet_types::enums::{ObjectType, PropertyIdentifier, Reliability};
9use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
10
11#[derive(Debug, Clone, PartialEq)]
13pub struct ReliabilityChange {
14 pub object_id: ObjectIdentifier,
16 pub old_reliability: u32,
18 pub new_reliability: u32,
20}
21
22pub struct FaultDetector {
28 pub comm_timeout: Option<std::time::Duration>,
31}
32
33impl Default for FaultDetector {
34 fn default() -> Self {
35 Self {
36 comm_timeout: Some(std::time::Duration::from_secs(60)),
37 }
38 }
39}
40
41impl FaultDetector {
42 pub fn new(comm_timeout: Option<std::time::Duration>) -> Self {
44 Self { comm_timeout }
45 }
46
47 pub fn evaluate(&self, db: &mut ObjectDatabase) -> Vec<ReliabilityChange> {
56 let analog_types = [
57 ObjectType::ANALOG_INPUT,
58 ObjectType::ANALOG_OUTPUT,
59 ObjectType::ANALOG_VALUE,
60 ];
61
62 let mut updates: Vec<(ObjectIdentifier, u32, u32)> = Vec::new();
64
65 for &obj_type in &analog_types {
66 let oids = db.find_by_type(obj_type);
67 for oid in oids {
68 if let Some(obj) = db.get(&oid) {
69 let current_reliability =
70 match obj.read_property(PropertyIdentifier::RELIABILITY, None) {
71 Ok(PropertyValue::Enumerated(v)) => v,
72 _ => 0,
73 };
74
75 let present_value =
76 match obj.read_property(PropertyIdentifier::PRESENT_VALUE, None) {
77 Ok(PropertyValue::Real(v)) => v,
78 _ => continue,
79 };
80
81 let min_pres = obj
82 .read_property(PropertyIdentifier::MIN_PRES_VALUE, None)
83 .ok()
84 .and_then(|v| match v {
85 PropertyValue::Real(f) => Some(f),
86 _ => None,
87 });
88
89 let max_pres = obj
90 .read_property(PropertyIdentifier::MAX_PRES_VALUE, None)
91 .ok()
92 .and_then(|v| match v {
93 PropertyValue::Real(f) => Some(f),
94 _ => None,
95 });
96
97 let new_reliability = if let Some(max) = max_pres {
98 if present_value > max {
99 Reliability::OVER_RANGE.to_raw()
100 } else if let Some(min) = min_pres {
101 if present_value < min {
102 Reliability::UNDER_RANGE.to_raw()
103 } else {
104 Reliability::NO_FAULT_DETECTED.to_raw()
105 }
106 } else {
107 Reliability::NO_FAULT_DETECTED.to_raw()
108 }
109 } else if let Some(min) = min_pres {
110 if present_value < min {
111 Reliability::UNDER_RANGE.to_raw()
112 } else {
113 Reliability::NO_FAULT_DETECTED.to_raw()
114 }
115 } else {
116 continue;
118 };
119
120 if new_reliability != current_reliability {
121 updates.push((oid, current_reliability, new_reliability));
122 }
123 }
124 }
125 }
126
127 let mut changes = Vec::new();
129 for (oid, old_rel, new_rel) in updates {
130 if let Some(obj) = db.get_mut(&oid) {
131 if obj
132 .write_property(
133 PropertyIdentifier::RELIABILITY,
134 None,
135 PropertyValue::Enumerated(new_rel),
136 None,
137 )
138 .is_ok()
139 {
140 changes.push(ReliabilityChange {
141 object_id: oid,
142 old_reliability: old_rel,
143 new_reliability: new_rel,
144 });
145 }
146 }
147 }
148
149 changes
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use bacnet_objects::analog::{AnalogInputObject, AnalogOutputObject, AnalogValueObject};
157
158 fn db_with_analog_input(
160 present_value: f32,
161 min_pres: Option<f32>,
162 max_pres: Option<f32>,
163 ) -> ObjectDatabase {
164 let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
165 ai.set_present_value(present_value);
166 if let Some(min) = min_pres {
167 ai.set_min_pres_value(min);
168 }
169 if let Some(max) = max_pres {
170 ai.set_max_pres_value(max);
171 }
172 let mut db = ObjectDatabase::new();
173 db.add(Box::new(ai)).unwrap();
174 db
175 }
176
177 #[test]
178 fn no_fault_when_in_range() {
179 let mut db = db_with_analog_input(50.0, Some(0.0), Some(100.0));
180 let detector = FaultDetector::default();
181 let changes = detector.evaluate(&mut db);
182 assert!(changes.is_empty(), "no change expected for in-range value");
183 }
184
185 #[test]
186 fn over_range_detected() {
187 let mut db = db_with_analog_input(150.0, Some(0.0), Some(100.0));
188 let detector = FaultDetector::default();
189 let changes = detector.evaluate(&mut db);
190 assert_eq!(changes.len(), 1);
191 assert_eq!(changes[0].new_reliability, Reliability::OVER_RANGE.to_raw());
192 assert_eq!(
193 changes[0].old_reliability,
194 Reliability::NO_FAULT_DETECTED.to_raw()
195 );
196 }
197
198 #[test]
199 fn under_range_detected() {
200 let mut db = db_with_analog_input(-10.0, Some(0.0), Some(100.0));
201 let detector = FaultDetector::default();
202 let changes = detector.evaluate(&mut db);
203 assert_eq!(changes.len(), 1);
204 assert_eq!(
205 changes[0].new_reliability,
206 Reliability::UNDER_RANGE.to_raw()
207 );
208 }
209
210 #[test]
211 fn returns_to_no_fault_after_correction() {
212 let mut db = db_with_analog_input(150.0, Some(0.0), Some(100.0));
213 let detector = FaultDetector::default();
214
215 let changes = detector.evaluate(&mut db);
217 assert_eq!(changes.len(), 1);
218 assert_eq!(changes[0].new_reliability, Reliability::OVER_RANGE.to_raw());
219
220 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
222 let obj = db.get_mut(&oid).unwrap();
223 obj.write_property(
224 PropertyIdentifier::PRESENT_VALUE,
225 None,
226 PropertyValue::Real(50.0),
227 None,
228 )
229 .unwrap_or_else(|_| {
231 obj.write_property(
232 PropertyIdentifier::OUT_OF_SERVICE,
233 None,
234 PropertyValue::Boolean(true),
235 None,
236 )
237 .unwrap();
238 obj.write_property(
239 PropertyIdentifier::PRESENT_VALUE,
240 None,
241 PropertyValue::Real(50.0),
242 None,
243 )
244 .unwrap();
245 });
246
247 let changes = detector.evaluate(&mut db);
249 assert_eq!(changes.len(), 1);
250 assert_eq!(
251 changes[0].new_reliability,
252 Reliability::NO_FAULT_DETECTED.to_raw()
253 );
254 }
255
256 #[test]
257 fn no_limits_means_no_evaluation() {
258 let mut db = db_with_analog_input(999.0, None, None);
260 let detector = FaultDetector::default();
261 let changes = detector.evaluate(&mut db);
262 assert!(changes.is_empty());
263 }
264
265 #[test]
266 fn max_only_over_range() {
267 let mut db = db_with_analog_input(200.0, None, Some(100.0));
268 let detector = FaultDetector::default();
269 let changes = detector.evaluate(&mut db);
270 assert_eq!(changes.len(), 1);
271 assert_eq!(changes[0].new_reliability, Reliability::OVER_RANGE.to_raw());
272 }
273
274 #[test]
275 fn min_only_under_range() {
276 let mut db = db_with_analog_input(-5.0, Some(0.0), None);
277 let detector = FaultDetector::default();
278 let changes = detector.evaluate(&mut db);
279 assert_eq!(changes.len(), 1);
280 assert_eq!(
281 changes[0].new_reliability,
282 Reliability::UNDER_RANGE.to_raw()
283 );
284 }
285
286 #[test]
287 fn no_change_emitted_when_already_faulted() {
288 let mut db = db_with_analog_input(150.0, Some(0.0), Some(100.0));
289 let detector = FaultDetector::default();
290
291 let changes = detector.evaluate(&mut db);
293 assert_eq!(changes.len(), 1);
294
295 let changes = detector.evaluate(&mut db);
297 assert!(changes.is_empty());
298 }
299
300 #[test]
301 fn evaluates_multiple_analog_types() {
302 let mut db = ObjectDatabase::new();
303
304 let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
305 ai.set_present_value(200.0);
306 ai.set_max_pres_value(100.0);
307 db.add(Box::new(ai)).unwrap();
308
309 let ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
310 db.add(Box::new(ao)).unwrap();
312
313 let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
314 av.set_present_value(-10.0);
315 av.set_min_pres_value(0.0);
316 db.add(Box::new(av)).unwrap();
317
318 let detector = FaultDetector::default();
319 let changes = detector.evaluate(&mut db);
320 assert_eq!(changes.len(), 2);
321 }
322}