1use std::borrow::Cow;
4use std::collections::VecDeque;
5
6use bacnet_types::constructed::{BACnetLogRecord, LogDatum};
7use bacnet_types::enums::{ObjectType, PropertyIdentifier};
8use bacnet_types::error::Error;
9use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
10
11use crate::common::{self, read_common_properties};
12use crate::traits::BACnetObject;
13
14pub struct EventLogObject {
20 oid: ObjectIdentifier,
21 name: String,
22 description: String,
23 log_enable: bool,
24 log_interval: u32,
25 stop_when_full: bool,
26 buffer_size: u32,
27 buffer: VecDeque<BACnetLogRecord>,
28 total_record_count: u64,
29 status_flags: StatusFlags,
30 event_state: u32,
31 out_of_service: bool,
32 reliability: u32,
33}
34
35impl EventLogObject {
36 pub fn new(instance: u32, name: impl Into<String>, buffer_size: u32) -> Result<Self, Error> {
38 let oid = ObjectIdentifier::new(ObjectType::EVENT_LOG, instance)?;
39 Ok(Self {
40 oid,
41 name: name.into(),
42 description: String::new(),
43 log_enable: true,
44 log_interval: 0,
45 stop_when_full: false,
46 buffer_size,
47 buffer: VecDeque::new(),
48 total_record_count: 0,
49 status_flags: StatusFlags::empty(),
50 event_state: 0,
51 out_of_service: false,
52 reliability: 0,
53 })
54 }
55
56 pub fn add_record(&mut self, record: BACnetLogRecord) {
58 if !self.log_enable {
59 return;
60 }
61 if self.buffer.len() >= self.buffer_size as usize {
62 if self.stop_when_full {
63 return;
64 }
65 self.buffer.pop_front();
66 }
67 self.buffer.push_back(record);
68 self.total_record_count += 1;
69 }
70
71 pub fn records(&self) -> &VecDeque<BACnetLogRecord> {
73 &self.buffer
74 }
75
76 pub fn clear(&mut self) {
78 self.buffer.clear();
79 }
80
81 pub fn set_description(&mut self, desc: impl Into<String>) {
83 self.description = desc.into();
84 }
85
86 fn encode_log_buffer(&self) -> PropertyValue {
87 let records = self
88 .buffer
89 .iter()
90 .map(|record| {
91 let datum_value = match &record.log_datum {
92 LogDatum::LogStatus(v) => PropertyValue::Unsigned(*v as u64),
93 LogDatum::BooleanValue(v) => PropertyValue::Boolean(*v),
94 LogDatum::RealValue(v) => PropertyValue::Real(*v),
95 LogDatum::EnumValue(v) => PropertyValue::Enumerated(*v),
96 LogDatum::UnsignedValue(v) => PropertyValue::Unsigned(*v),
97 LogDatum::SignedValue(v) => PropertyValue::Signed(*v as i32),
98 LogDatum::BitstringValue { unused_bits, data } => PropertyValue::BitString {
99 unused_bits: *unused_bits,
100 data: data.clone(),
101 },
102 LogDatum::NullValue => PropertyValue::Null,
103 LogDatum::Failure {
104 error_class,
105 error_code,
106 } => PropertyValue::List(vec![
107 PropertyValue::Unsigned(*error_class as u64),
108 PropertyValue::Unsigned(*error_code as u64),
109 ]),
110 LogDatum::TimeChange(v) => PropertyValue::Real(*v),
111 LogDatum::AnyValue(bytes) => PropertyValue::OctetString(bytes.clone()),
112 };
113 PropertyValue::List(vec![
114 PropertyValue::Date(record.date),
115 PropertyValue::Time(record.time),
116 datum_value,
117 ])
118 })
119 .collect();
120 PropertyValue::List(records)
121 }
122}
123
124impl BACnetObject for EventLogObject {
125 fn object_identifier(&self) -> ObjectIdentifier {
126 self.oid
127 }
128
129 fn object_name(&self) -> &str {
130 &self.name
131 }
132
133 fn read_property(
134 &self,
135 property: PropertyIdentifier,
136 array_index: Option<u32>,
137 ) -> Result<PropertyValue, Error> {
138 if let Some(result) = read_common_properties!(self, property, array_index) {
139 return result;
140 }
141 match property {
142 p if p == PropertyIdentifier::OBJECT_TYPE => {
143 Ok(PropertyValue::Enumerated(ObjectType::EVENT_LOG.to_raw()))
144 }
145 p if p == PropertyIdentifier::LOG_ENABLE => Ok(PropertyValue::Boolean(self.log_enable)),
146 p if p == PropertyIdentifier::LOG_INTERVAL => {
147 Ok(PropertyValue::Unsigned(self.log_interval as u64))
148 }
149 p if p == PropertyIdentifier::STOP_WHEN_FULL => {
150 Ok(PropertyValue::Boolean(self.stop_when_full))
151 }
152 p if p == PropertyIdentifier::BUFFER_SIZE => {
153 Ok(PropertyValue::Unsigned(self.buffer_size as u64))
154 }
155 p if p == PropertyIdentifier::LOG_BUFFER => Ok(self.encode_log_buffer()),
156 p if p == PropertyIdentifier::RECORD_COUNT => {
157 Ok(PropertyValue::Unsigned(self.buffer.len() as u64))
158 }
159 p if p == PropertyIdentifier::TOTAL_RECORD_COUNT => {
160 Ok(PropertyValue::Unsigned(self.total_record_count))
161 }
162 p if p == PropertyIdentifier::EVENT_STATE => {
163 Ok(PropertyValue::Enumerated(self.event_state))
164 }
165 _ => Err(common::unknown_property_error()),
166 }
167 }
168
169 fn write_property(
170 &mut self,
171 property: PropertyIdentifier,
172 _array_index: Option<u32>,
173 value: PropertyValue,
174 _priority: Option<u8>,
175 ) -> Result<(), Error> {
176 if property == PropertyIdentifier::LOG_ENABLE {
177 if let PropertyValue::Boolean(v) = value {
178 self.log_enable = v;
179 return Ok(());
180 }
181 return Err(common::invalid_data_type_error());
182 }
183 if property == PropertyIdentifier::LOG_INTERVAL {
184 if let PropertyValue::Unsigned(v) = value {
185 self.log_interval = common::u64_to_u32(v)?;
186 return Ok(());
187 }
188 return Err(common::invalid_data_type_error());
189 }
190 if property == PropertyIdentifier::STOP_WHEN_FULL {
191 if let PropertyValue::Boolean(v) = value {
192 self.stop_when_full = v;
193 return Ok(());
194 }
195 return Err(common::invalid_data_type_error());
196 }
197 if property == PropertyIdentifier::RECORD_COUNT {
198 if let PropertyValue::Unsigned(0) = value {
200 self.buffer.clear();
201 return Ok(());
202 }
203 return Err(common::invalid_data_type_error());
204 }
205 if let Some(result) =
206 common::write_out_of_service(&mut self.out_of_service, property, &value)
207 {
208 return result;
209 }
210 if let Some(result) = common::write_description(&mut self.description, property, &value) {
211 return result;
212 }
213 Err(common::write_access_denied_error())
214 }
215
216 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
217 static PROPS: &[PropertyIdentifier] = &[
218 PropertyIdentifier::OBJECT_IDENTIFIER,
219 PropertyIdentifier::OBJECT_NAME,
220 PropertyIdentifier::DESCRIPTION,
221 PropertyIdentifier::OBJECT_TYPE,
222 PropertyIdentifier::LOG_ENABLE,
223 PropertyIdentifier::LOG_INTERVAL,
224 PropertyIdentifier::STOP_WHEN_FULL,
225 PropertyIdentifier::BUFFER_SIZE,
226 PropertyIdentifier::LOG_BUFFER,
227 PropertyIdentifier::RECORD_COUNT,
228 PropertyIdentifier::TOTAL_RECORD_COUNT,
229 PropertyIdentifier::STATUS_FLAGS,
230 PropertyIdentifier::EVENT_STATE,
231 PropertyIdentifier::OUT_OF_SERVICE,
232 PropertyIdentifier::RELIABILITY,
233 ];
234 Cow::Borrowed(PROPS)
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use bacnet_types::constructed::LogDatum;
242 use bacnet_types::primitives::{Date, Time};
243
244 fn make_date() -> Date {
245 Date {
246 year: 124,
247 month: 3,
248 day: 15,
249 day_of_week: 5,
250 }
251 }
252
253 fn make_time(hour: u8) -> Time {
254 Time {
255 hour,
256 minute: 0,
257 second: 0,
258 hundredths: 0,
259 }
260 }
261
262 fn make_record(hour: u8, value: f32) -> BACnetLogRecord {
263 BACnetLogRecord {
264 date: make_date(),
265 time: make_time(hour),
266 log_datum: LogDatum::RealValue(value),
267 status_flags: None,
268 }
269 }
270
271 #[test]
272 fn create_event_log() {
273 let el = EventLogObject::new(1, "EL-1", 100).unwrap();
274 assert_eq!(el.object_identifier().object_type(), ObjectType::EVENT_LOG);
275 assert_eq!(el.object_identifier().instance_number(), 1);
276 assert_eq!(el.object_name(), "EL-1");
277 }
278
279 #[test]
280 fn read_object_type() {
281 let el = EventLogObject::new(1, "EL-1", 100).unwrap();
282 let val = el
283 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
284 .unwrap();
285 assert_eq!(
286 val,
287 PropertyValue::Enumerated(ObjectType::EVENT_LOG.to_raw())
288 );
289 }
290
291 #[test]
292 fn add_records_and_read_count() {
293 let mut el = EventLogObject::new(1, "EL-1", 100).unwrap();
294 el.add_record(make_record(10, 72.5));
295 el.add_record(make_record(11, 73.0));
296 assert_eq!(el.records().len(), 2);
297 let val = el
298 .read_property(PropertyIdentifier::RECORD_COUNT, None)
299 .unwrap();
300 assert_eq!(val, PropertyValue::Unsigned(2));
301 let val = el
302 .read_property(PropertyIdentifier::TOTAL_RECORD_COUNT, None)
303 .unwrap();
304 assert_eq!(val, PropertyValue::Unsigned(2));
305 }
306
307 #[test]
308 fn read_log_buffer() {
309 let mut el = EventLogObject::new(1, "EL-1", 100).unwrap();
310 el.add_record(make_record(10, 72.5));
311 el.add_record(make_record(11, 73.0));
312 let val = el
313 .read_property(PropertyIdentifier::LOG_BUFFER, None)
314 .unwrap();
315 if let PropertyValue::List(records) = val {
316 assert_eq!(records.len(), 2);
317 if let PropertyValue::List(fields) = &records[0] {
318 assert_eq!(fields.len(), 3);
319 assert_eq!(fields[2], PropertyValue::Real(72.5));
320 } else {
321 panic!("Expected List for log record");
322 }
323 if let PropertyValue::List(fields) = &records[1] {
324 assert_eq!(fields[2], PropertyValue::Real(73.0));
325 } else {
326 panic!("Expected List for log record");
327 }
328 } else {
329 panic!("Expected List for LOG_BUFFER");
330 }
331 }
332
333 #[test]
334 fn read_log_buffer_empty() {
335 let el = EventLogObject::new(1, "EL-1", 100).unwrap();
336 let val = el
337 .read_property(PropertyIdentifier::LOG_BUFFER, None)
338 .unwrap();
339 assert_eq!(val, PropertyValue::List(vec![]));
340 }
341
342 #[test]
343 fn ring_buffer_wraps() {
344 let mut el = EventLogObject::new(1, "EL-1", 3).unwrap();
345 for i in 0..5u8 {
346 el.add_record(BACnetLogRecord {
347 date: make_date(),
348 time: make_time(i),
349 log_datum: LogDatum::UnsignedValue(i as u64),
350 status_flags: None,
351 });
352 }
353 assert_eq!(el.records().len(), 3);
354 assert_eq!(el.records()[0].time.hour, 2);
356 let val = el
357 .read_property(PropertyIdentifier::TOTAL_RECORD_COUNT, None)
358 .unwrap();
359 assert_eq!(val, PropertyValue::Unsigned(5));
360 }
361
362 #[test]
363 fn stop_when_full() {
364 let mut el = EventLogObject::new(1, "EL-1", 2).unwrap();
365 el.write_property(
366 PropertyIdentifier::STOP_WHEN_FULL,
367 None,
368 PropertyValue::Boolean(true),
369 None,
370 )
371 .unwrap();
372 for i in 0..5u8 {
373 el.add_record(make_record(i, i as f32));
374 }
375 assert_eq!(el.records().len(), 2);
376 assert_eq!(el.total_record_count, 2); }
378
379 #[test]
380 fn disable_logging() {
381 let mut el = EventLogObject::new(1, "EL-1", 100).unwrap();
382 el.write_property(
383 PropertyIdentifier::LOG_ENABLE,
384 None,
385 PropertyValue::Boolean(false),
386 None,
387 )
388 .unwrap();
389 el.add_record(make_record(10, 72.5));
390 assert_eq!(el.records().len(), 0);
391 }
392
393 #[test]
394 fn clear_buffer_via_record_count() {
395 let mut el = EventLogObject::new(1, "EL-1", 100).unwrap();
396 el.add_record(make_record(10, 72.5));
397 assert_eq!(el.records().len(), 1);
398 el.write_property(
399 PropertyIdentifier::RECORD_COUNT,
400 None,
401 PropertyValue::Unsigned(0),
402 None,
403 )
404 .unwrap();
405 assert_eq!(el.records().len(), 0);
406 }
407
408 #[test]
409 fn read_event_state_default() {
410 let el = EventLogObject::new(1, "EL-1", 100).unwrap();
411 let val = el
412 .read_property(PropertyIdentifier::EVENT_STATE, None)
413 .unwrap();
414 assert_eq!(val, PropertyValue::Enumerated(0)); }
416
417 #[test]
418 fn property_list_complete() {
419 let el = EventLogObject::new(1, "EL-1", 100).unwrap();
420 let props = el.property_list();
421 assert!(props.contains(&PropertyIdentifier::LOG_ENABLE));
422 assert!(props.contains(&PropertyIdentifier::LOG_INTERVAL));
423 assert!(props.contains(&PropertyIdentifier::STOP_WHEN_FULL));
424 assert!(props.contains(&PropertyIdentifier::BUFFER_SIZE));
425 assert!(props.contains(&PropertyIdentifier::LOG_BUFFER));
426 assert!(props.contains(&PropertyIdentifier::RECORD_COUNT));
427 assert!(props.contains(&PropertyIdentifier::TOTAL_RECORD_COUNT));
428 assert!(props.contains(&PropertyIdentifier::STATUS_FLAGS));
429 assert!(props.contains(&PropertyIdentifier::EVENT_STATE));
430 assert!(props.contains(&PropertyIdentifier::OUT_OF_SERVICE));
431 assert!(props.contains(&PropertyIdentifier::RELIABILITY));
432 }
433
434 #[test]
435 fn write_log_interval() {
436 let mut el = EventLogObject::new(1, "EL-1", 100).unwrap();
437 el.write_property(
438 PropertyIdentifier::LOG_INTERVAL,
439 None,
440 PropertyValue::Unsigned(60),
441 None,
442 )
443 .unwrap();
444 let val = el
445 .read_property(PropertyIdentifier::LOG_INTERVAL, None)
446 .unwrap();
447 assert_eq!(val, PropertyValue::Unsigned(60));
448 }
449
450 #[test]
451 fn write_unknown_property_denied() {
452 let mut el = EventLogObject::new(1, "EL-1", 100).unwrap();
453 let result = el.write_property(
454 PropertyIdentifier::PRESENT_VALUE,
455 None,
456 PropertyValue::Real(1.0),
457 None,
458 );
459 assert!(result.is_err());
460 }
461
462 #[test]
463 fn log_buffer_various_datum_types() {
464 let mut el = EventLogObject::new(1, "EL-1", 100).unwrap();
465 let date = make_date();
466 let time = make_time(8);
467
468 el.add_record(BACnetLogRecord {
469 date,
470 time,
471 log_datum: LogDatum::BooleanValue(true),
472 status_flags: None,
473 });
474 el.add_record(BACnetLogRecord {
475 date,
476 time,
477 log_datum: LogDatum::EnumValue(42),
478 status_flags: Some(0b0100),
479 });
480 el.add_record(BACnetLogRecord {
481 date,
482 time,
483 log_datum: LogDatum::NullValue,
484 status_flags: None,
485 });
486
487 let val = el
488 .read_property(PropertyIdentifier::LOG_BUFFER, None)
489 .unwrap();
490 if let PropertyValue::List(records) = val {
491 assert_eq!(records.len(), 3);
492 if let PropertyValue::List(fields) = &records[0] {
493 assert_eq!(fields[2], PropertyValue::Boolean(true));
494 } else {
495 panic!("Expected List");
496 }
497 if let PropertyValue::List(fields) = &records[1] {
498 assert_eq!(fields[2], PropertyValue::Enumerated(42));
499 } else {
500 panic!("Expected List");
501 }
502 if let PropertyValue::List(fields) = &records[2] {
503 assert_eq!(fields[2], PropertyValue::Null);
504 } else {
505 panic!("Expected List");
506 }
507 } else {
508 panic!("Expected List for LOG_BUFFER");
509 }
510 }
511}