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