1use bacnet_types::constructed::BACnetObjectPropertyReference;
7use bacnet_types::enums::{ObjectType, PropertyIdentifier};
8use bacnet_types::error::Error;
9use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
10use std::borrow::Cow;
11
12use crate::common::{self, read_common_properties};
13use crate::traits::BACnetObject;
14
15pub struct AveragingObject {
20 oid: ObjectIdentifier,
21 name: String,
22 description: String,
23 present_value: f32,
24 minimum_value: f32,
25 maximum_value: f32,
26 average_value: f32,
27 attempted_samples: u32,
28 valid_samples: u32,
29 object_property_reference: Option<BACnetObjectPropertyReference>,
30 status_flags: StatusFlags,
31 out_of_service: bool,
32 reliability: u32,
33}
34
35impl AveragingObject {
36 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
37 let oid = ObjectIdentifier::new(ObjectType::AVERAGING, instance)?;
38 Ok(Self {
39 oid,
40 name: name.into(),
41 description: String::new(),
42 present_value: 0.0,
43 minimum_value: f32::MAX,
44 maximum_value: f32::MIN,
45 average_value: 0.0,
46 attempted_samples: 0,
47 valid_samples: 0,
48 object_property_reference: None,
49 status_flags: StatusFlags::empty(),
50 out_of_service: false,
51 reliability: 0,
52 })
53 }
54
55 pub fn add_sample(&mut self, value: f32) {
57 self.attempted_samples += 1;
58 self.valid_samples += 1;
59
60 if value < self.minimum_value {
61 self.minimum_value = value;
62 }
63 if value > self.maximum_value {
64 self.maximum_value = value;
65 }
66
67 self.average_value += (value - self.average_value) / self.valid_samples as f32;
69 self.present_value = self.average_value;
70 }
71
72 pub fn set_object_property_reference(
74 &mut self,
75 reference: Option<BACnetObjectPropertyReference>,
76 ) {
77 self.object_property_reference = reference;
78 }
79
80 pub fn set_description(&mut self, desc: impl Into<String>) {
82 self.description = desc.into();
83 }
84}
85
86impl BACnetObject for AveragingObject {
87 fn object_identifier(&self) -> ObjectIdentifier {
88 self.oid
89 }
90
91 fn object_name(&self) -> &str {
92 &self.name
93 }
94
95 fn read_property(
96 &self,
97 property: PropertyIdentifier,
98 array_index: Option<u32>,
99 ) -> Result<PropertyValue, Error> {
100 if let Some(result) = read_common_properties!(self, property, array_index) {
101 return result;
102 }
103 match property {
104 p if p == PropertyIdentifier::OBJECT_TYPE => {
105 Ok(PropertyValue::Enumerated(ObjectType::AVERAGING.to_raw()))
106 }
107 p if p == PropertyIdentifier::PRESENT_VALUE => {
108 Ok(PropertyValue::Real(self.present_value))
109 }
110 p if p == PropertyIdentifier::MINIMUM_VALUE => {
111 if self.valid_samples == 0 {
112 Ok(PropertyValue::Real(0.0))
113 } else {
114 Ok(PropertyValue::Real(self.minimum_value))
115 }
116 }
117 p if p == PropertyIdentifier::MAXIMUM_VALUE => {
118 if self.valid_samples == 0 {
119 Ok(PropertyValue::Real(0.0))
120 } else {
121 Ok(PropertyValue::Real(self.maximum_value))
122 }
123 }
124 p if p == PropertyIdentifier::AVERAGE_VALUE => {
125 Ok(PropertyValue::Real(self.average_value))
126 }
127 p if p == PropertyIdentifier::ATTEMPTED_SAMPLES => {
128 Ok(PropertyValue::Unsigned(self.attempted_samples as u64))
129 }
130 p if p == PropertyIdentifier::VALID_SAMPLES => {
131 Ok(PropertyValue::Unsigned(self.valid_samples as u64))
132 }
133 p if p == PropertyIdentifier::OBJECT_PROPERTY_REFERENCE => {
134 match &self.object_property_reference {
135 None => Ok(PropertyValue::Null),
136 Some(r) => {
137 let mut fields = vec![
138 PropertyValue::ObjectIdentifier(r.object_identifier),
139 PropertyValue::Unsigned(r.property_identifier as u64),
140 ];
141 if let Some(idx) = r.property_array_index {
142 fields.push(PropertyValue::Unsigned(idx as u64));
143 }
144 Ok(PropertyValue::List(fields))
145 }
146 }
147 }
148 p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(0)),
149 _ => Err(common::unknown_property_error()),
150 }
151 }
152
153 fn write_property(
154 &mut self,
155 property: PropertyIdentifier,
156 _array_index: Option<u32>,
157 value: PropertyValue,
158 _priority: Option<u8>,
159 ) -> Result<(), Error> {
160 if let Some(result) = common::write_description(&mut self.description, property, &value) {
161 return result;
162 }
163 if let Some(result) =
164 common::write_out_of_service(&mut self.out_of_service, property, &value)
165 {
166 return result;
167 }
168 if property == PropertyIdentifier::OBJECT_PROPERTY_REFERENCE {
169 match value {
170 PropertyValue::Null => {
171 self.object_property_reference = None;
172 return Ok(());
173 }
174 PropertyValue::List(ref items) if items.len() >= 2 => {
175 if let (PropertyValue::ObjectIdentifier(oid), PropertyValue::Unsigned(prop)) =
176 (&items[0], &items[1])
177 {
178 let array_index = if items.len() > 2 {
179 if let PropertyValue::Unsigned(idx) = &items[2] {
180 Some(*idx as u32)
181 } else {
182 None
183 }
184 } else {
185 None
186 };
187 self.object_property_reference = Some(if let Some(idx) = array_index {
188 BACnetObjectPropertyReference::new_indexed(*oid, *prop as u32, idx)
189 } else {
190 BACnetObjectPropertyReference::new(*oid, *prop as u32)
191 });
192 return Ok(());
193 }
194 return Err(common::invalid_data_type_error());
195 }
196 _ => return Err(common::invalid_data_type_error()),
197 }
198 }
199 Err(common::write_access_denied_error())
200 }
201
202 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
203 static PROPS: &[PropertyIdentifier] = &[
204 PropertyIdentifier::OBJECT_IDENTIFIER,
205 PropertyIdentifier::OBJECT_NAME,
206 PropertyIdentifier::DESCRIPTION,
207 PropertyIdentifier::OBJECT_TYPE,
208 PropertyIdentifier::PRESENT_VALUE,
209 PropertyIdentifier::MINIMUM_VALUE,
210 PropertyIdentifier::MAXIMUM_VALUE,
211 PropertyIdentifier::AVERAGE_VALUE,
212 PropertyIdentifier::ATTEMPTED_SAMPLES,
213 PropertyIdentifier::VALID_SAMPLES,
214 PropertyIdentifier::OBJECT_PROPERTY_REFERENCE,
215 PropertyIdentifier::STATUS_FLAGS,
216 PropertyIdentifier::OUT_OF_SERVICE,
217 PropertyIdentifier::RELIABILITY,
218 PropertyIdentifier::EVENT_STATE,
219 ];
220 Cow::Borrowed(PROPS)
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227 use bacnet_types::enums::ObjectType;
228
229 #[test]
230 fn averaging_create() {
231 let avg = AveragingObject::new(1, "AVG-1").unwrap();
232 assert_eq!(
233 avg.read_property(PropertyIdentifier::OBJECT_NAME, None)
234 .unwrap(),
235 PropertyValue::CharacterString("AVG-1".into())
236 );
237 assert_eq!(
238 avg.read_property(PropertyIdentifier::OBJECT_TYPE, None)
239 .unwrap(),
240 PropertyValue::Enumerated(ObjectType::AVERAGING.to_raw())
241 );
242 assert_eq!(
243 avg.read_property(PropertyIdentifier::PRESENT_VALUE, None)
244 .unwrap(),
245 PropertyValue::Real(0.0)
246 );
247 }
248
249 #[test]
250 fn averaging_add_samples() {
251 let mut avg = AveragingObject::new(1, "AVG-1").unwrap();
252 avg.add_sample(10.0);
253 avg.add_sample(20.0);
254 avg.add_sample(30.0);
255
256 assert_eq!(
257 avg.read_property(PropertyIdentifier::ATTEMPTED_SAMPLES, None)
258 .unwrap(),
259 PropertyValue::Unsigned(3)
260 );
261 assert_eq!(
262 avg.read_property(PropertyIdentifier::VALID_SAMPLES, None)
263 .unwrap(),
264 PropertyValue::Unsigned(3)
265 );
266 }
267
268 #[test]
269 fn averaging_min_max() {
270 let mut avg = AveragingObject::new(1, "AVG-1").unwrap();
271 avg.add_sample(15.0);
272 avg.add_sample(5.0);
273 avg.add_sample(25.0);
274
275 assert_eq!(
276 avg.read_property(PropertyIdentifier::MINIMUM_VALUE, None)
277 .unwrap(),
278 PropertyValue::Real(5.0)
279 );
280 assert_eq!(
281 avg.read_property(PropertyIdentifier::MAXIMUM_VALUE, None)
282 .unwrap(),
283 PropertyValue::Real(25.0)
284 );
285 }
286
287 #[test]
288 fn averaging_average_value() {
289 let mut avg = AveragingObject::new(1, "AVG-1").unwrap();
290 avg.add_sample(10.0);
291 avg.add_sample(20.0);
292 avg.add_sample(30.0);
293
294 let val = avg
295 .read_property(PropertyIdentifier::AVERAGE_VALUE, None)
296 .unwrap();
297 if let PropertyValue::Real(v) = val {
298 assert!((v - 20.0).abs() < 0.001);
299 } else {
300 panic!("Expected Real");
301 }
302
303 let pv = avg
305 .read_property(PropertyIdentifier::PRESENT_VALUE, None)
306 .unwrap();
307 assert_eq!(pv, val);
308 }
309
310 #[test]
311 fn averaging_no_samples_defaults() {
312 let avg = AveragingObject::new(1, "AVG-1").unwrap();
313 assert_eq!(
315 avg.read_property(PropertyIdentifier::MINIMUM_VALUE, None)
316 .unwrap(),
317 PropertyValue::Real(0.0)
318 );
319 assert_eq!(
320 avg.read_property(PropertyIdentifier::MAXIMUM_VALUE, None)
321 .unwrap(),
322 PropertyValue::Real(0.0)
323 );
324 assert_eq!(
325 avg.read_property(PropertyIdentifier::AVERAGE_VALUE, None)
326 .unwrap(),
327 PropertyValue::Real(0.0)
328 );
329 }
330
331 #[test]
332 fn averaging_property_list() {
333 let avg = AveragingObject::new(1, "AVG-1").unwrap();
334 let props = avg.property_list();
335 assert!(props.contains(&PropertyIdentifier::PRESENT_VALUE));
336 assert!(props.contains(&PropertyIdentifier::MINIMUM_VALUE));
337 assert!(props.contains(&PropertyIdentifier::MAXIMUM_VALUE));
338 assert!(props.contains(&PropertyIdentifier::AVERAGE_VALUE));
339 assert!(props.contains(&PropertyIdentifier::ATTEMPTED_SAMPLES));
340 assert!(props.contains(&PropertyIdentifier::VALID_SAMPLES));
341 assert!(props.contains(&PropertyIdentifier::OBJECT_PROPERTY_REFERENCE));
342 assert!(props.contains(&PropertyIdentifier::STATUS_FLAGS));
343 assert!(props.contains(&PropertyIdentifier::OUT_OF_SERVICE));
344 assert!(props.contains(&PropertyIdentifier::RELIABILITY));
345 }
346
347 #[test]
348 fn averaging_object_property_reference_default_null() {
349 let avg = AveragingObject::new(1, "AVG-1").unwrap();
350 assert_eq!(
351 avg.read_property(PropertyIdentifier::OBJECT_PROPERTY_REFERENCE, None)
352 .unwrap(),
353 PropertyValue::Null
354 );
355 }
356
357 #[test]
358 fn averaging_set_object_property_reference() {
359 let mut avg = AveragingObject::new(1, "AVG-1").unwrap();
360 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 5).unwrap();
361 let pv_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
362 avg.set_object_property_reference(Some(BACnetObjectPropertyReference::new(oid, pv_raw)));
363
364 let val = avg
365 .read_property(PropertyIdentifier::OBJECT_PROPERTY_REFERENCE, None)
366 .unwrap();
367 assert_eq!(
368 val,
369 PropertyValue::List(vec![
370 PropertyValue::ObjectIdentifier(oid),
371 PropertyValue::Unsigned(pv_raw as u64),
372 ])
373 );
374 }
375
376 #[test]
377 fn averaging_write_object_property_reference() {
378 let mut avg = AveragingObject::new(1, "AVG-1").unwrap();
379 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 3).unwrap();
380 let pv_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
381
382 avg.write_property(
383 PropertyIdentifier::OBJECT_PROPERTY_REFERENCE,
384 None,
385 PropertyValue::List(vec![
386 PropertyValue::ObjectIdentifier(oid),
387 PropertyValue::Unsigned(pv_raw as u64),
388 ]),
389 None,
390 )
391 .unwrap();
392
393 assert_eq!(
394 avg.read_property(PropertyIdentifier::OBJECT_PROPERTY_REFERENCE, None)
395 .unwrap(),
396 PropertyValue::List(vec![
397 PropertyValue::ObjectIdentifier(oid),
398 PropertyValue::Unsigned(pv_raw as u64),
399 ])
400 );
401 }
402
403 #[test]
404 fn averaging_write_null_clears_reference() {
405 let mut avg = AveragingObject::new(1, "AVG-1").unwrap();
406 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
407 avg.set_object_property_reference(Some(BACnetObjectPropertyReference::new(
408 oid,
409 PropertyIdentifier::PRESENT_VALUE.to_raw(),
410 )));
411
412 avg.write_property(
413 PropertyIdentifier::OBJECT_PROPERTY_REFERENCE,
414 None,
415 PropertyValue::Null,
416 None,
417 )
418 .unwrap();
419
420 assert_eq!(
421 avg.read_property(PropertyIdentifier::OBJECT_PROPERTY_REFERENCE, None)
422 .unwrap(),
423 PropertyValue::Null
424 );
425 }
426
427 #[test]
428 fn averaging_write_present_value_denied() {
429 let mut avg = AveragingObject::new(1, "AVG-1").unwrap();
430 let result = avg.write_property(
431 PropertyIdentifier::PRESENT_VALUE,
432 None,
433 PropertyValue::Real(42.0),
434 None,
435 );
436 assert!(result.is_err());
437 }
438
439 #[test]
440 fn averaging_description_read_write() {
441 let mut avg = AveragingObject::new(1, "AVG-1").unwrap();
442 assert_eq!(
443 avg.read_property(PropertyIdentifier::DESCRIPTION, None)
444 .unwrap(),
445 PropertyValue::CharacterString(String::new())
446 );
447 avg.write_property(
448 PropertyIdentifier::DESCRIPTION,
449 None,
450 PropertyValue::CharacterString("Zone temperature averaging".into()),
451 None,
452 )
453 .unwrap();
454 assert_eq!(
455 avg.read_property(PropertyIdentifier::DESCRIPTION, None)
456 .unwrap(),
457 PropertyValue::CharacterString("Zone temperature averaging".into())
458 );
459 }
460
461 #[test]
462 fn averaging_single_sample() {
463 let mut avg = AveragingObject::new(1, "AVG-1").unwrap();
464 avg.add_sample(42.0);
465
466 assert_eq!(
467 avg.read_property(PropertyIdentifier::MINIMUM_VALUE, None)
468 .unwrap(),
469 PropertyValue::Real(42.0)
470 );
471 assert_eq!(
472 avg.read_property(PropertyIdentifier::MAXIMUM_VALUE, None)
473 .unwrap(),
474 PropertyValue::Real(42.0)
475 );
476 assert_eq!(
477 avg.read_property(PropertyIdentifier::AVERAGE_VALUE, None)
478 .unwrap(),
479 PropertyValue::Real(42.0)
480 );
481 assert_eq!(
482 avg.read_property(PropertyIdentifier::PRESENT_VALUE, None)
483 .unwrap(),
484 PropertyValue::Real(42.0)
485 );
486 }
487}