use std::borrow::Cow;
use std::collections::VecDeque;
use std::sync::Arc;
use bacnet_types::bitstring::EventTransitionBits;
use bacnet_types::constructed::{BACnetDeviceObjectPropertyReference, BACnetLogMultipleRecord};
use bacnet_types::enums::{
ErrorClass, ErrorCode, LoggingType, NotifyType, ObjectType, PropertyIdentifier, Reliability,
};
use bacnet_types::error::Error;
use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, StatusFlags, Time};
use super::acquisition::{Acquisition, Rules};
use super::multiple_metadata;
use super::references::{self, MAX_LOG_DEVICE_OBJECT_PROPERTIES};
use crate::clock::ClockReader;
use crate::common::{self, read_property_list_property};
use crate::device_reference;
use crate::log_buffer::{
log_buffer_read_denied, LogBufferRecords, LogRecordBuffer, LogRecordIdentity,
};
use crate::log_lifecycle::LogLifecycle;
use crate::log_reporting::{impl_buffer_ready_reporting, BufferReadyReporting};
use crate::log_window::LogWindow;
use crate::traits::BACnetObject;
pub struct TrendLogMultipleObject {
oid: ObjectIdentifier,
name: String,
description: String,
log_enable: bool,
stop_when_full: bool,
buffer_size: u32,
log_buffer: LogRecordBuffer<BACnetLogMultipleRecord>,
log_device_object_property: Vec<BACnetDeviceObjectPropertyReference>,
acquisition: Acquisition,
window: LogWindow,
reporting: BufferReadyReporting,
reliability: Reliability,
clock: Option<Arc<dyn ClockReader>>,
}
impl TrendLogMultipleObject {
pub fn new(instance: u32, name: impl Into<String>, buffer_size: u32) -> Result<Self, Error> {
let oid = ObjectIdentifier::new(ObjectType::TREND_LOG_MULTIPLE, instance)?;
Ok(Self {
oid,
name: name.into(),
description: String::new(),
log_enable: true,
stop_when_full: false,
buffer_size,
log_buffer: LogRecordBuffer::new(buffer_size),
log_device_object_property: Vec::new(),
acquisition: Acquisition::new(Rules::TrendLogMultiple),
window: LogWindow::default(),
reporting: BufferReadyReporting::default(),
reliability: Reliability::NO_FAULT_DETECTED,
clock: None,
})
}
pub fn add_record(&mut self, record: BACnetLogMultipleRecord) -> Result<(), Error> {
self.lifecycle().try_add_ordinary(record)?;
self.acquisition.acquired();
Ok(())
}
pub fn add_property_reference(
&mut self,
reference: BACnetDeviceObjectPropertyReference,
) -> Result<(), Error> {
device_reference::check_device_member(reference.device_identifier)?;
if self.log_device_object_property.len() >= MAX_LOG_DEVICE_OBJECT_PROPERTIES {
return Err(references::no_space_error());
}
self.log_device_object_property.push(reference);
Ok(())
}
fn write_log_device_object_property(
&mut self,
array_index: Option<u32>,
value: PropertyValue,
) -> Result<(), Error> {
let mut candidate = self.log_device_object_property.clone();
match array_index {
None => {
let written = device_reference::decode_references(&value)?;
if written.len() > MAX_LOG_DEVICE_OBJECT_PROPERTIES {
return Err(references::no_space_error());
}
for reference in &written {
references::check_written(reference)?;
}
candidate = written;
}
Some(0) => {
let PropertyValue::Unsigned(size) = value else {
return Err(common::invalid_data_type_error());
};
let size = usize::try_from(size)
.ok()
.filter(|size| *size <= MAX_LOG_DEVICE_OBJECT_PROPERTIES)
.ok_or_else(references::no_space_error)?;
candidate.resize_with(size, references::empty_element);
}
Some(index) => {
let slot = usize::try_from(index - 1)
.ok()
.and_then(|slot| candidate.get_mut(slot))
.ok_or_else(common::invalid_array_index_error)?;
let reference = device_reference::decode_reference(&value)?;
references::check_written(&reference)?;
*slot = reference;
}
}
if candidate != self.log_device_object_property {
self.lifecycle().purge()?;
self.log_device_object_property = candidate;
}
Ok(())
}
pub fn records(&self) -> &VecDeque<BACnetLogMultipleRecord> {
self.log_buffer.records()
}
pub fn clear(&mut self) {
self.log_buffer.clear();
}
pub fn set_description(&mut self, desc: impl Into<String>) {
self.description = desc.into();
}
pub fn set_logging_type(&mut self, logging_type: LoggingType) -> Result<(), Error> {
self.acquisition.set_logging_type(logging_type)
}
pub fn set_log_interval(&mut self, hundredths: u32) -> Result<(), Error> {
self.acquisition.set_log_interval(hundredths)
}
pub fn set_start_time(&mut self, date: Date, time: Time) -> Result<(), Error> {
self.lifecycle()
.configure_window(PropertyIdentifier::START_TIME, (date, time))
}
pub fn set_stop_time(&mut self, date: Date, time: Time) -> Result<(), Error> {
self.lifecycle()
.configure_window(PropertyIdentifier::STOP_TIME, (date, time))
}
pub fn set_align_intervals(&mut self, align: bool) {
self.acquisition.set_align_intervals(align);
}
pub fn set_interval_offset(&mut self, hundredths: u32) {
self.acquisition.set_interval_offset(hundredths);
}
pub fn trigger(&mut self) -> Result<(), Error> {
self.acquisition.trigger()
}
pub(super) fn logging_type(&self) -> LoggingType {
self.acquisition.logging_type()
}
pub fn set_notification_threshold(&mut self, threshold: u32) {
self.reporting.set_notification_threshold(threshold);
}
pub fn set_notification_class(&mut self, class: u32) {
self.reporting.set_notification_class(class);
}
pub fn set_event_enable(&mut self, enable: EventTransitionBits) {
self.reporting.set_event_enable(enable);
}
pub fn set_notify_type(&mut self, notify_type: NotifyType) {
self.reporting.set_notify_type(notify_type);
}
fn lifecycle(&mut self) -> LogLifecycle<'_, BACnetLogMultipleRecord> {
LogLifecycle::new(
&mut self.log_buffer,
&mut self.log_enable,
&mut self.stop_when_full,
self.clock.as_ref(),
&mut self.window,
&mut self.reporting,
)
}
}
impl BACnetObject for TrendLogMultipleObject {
fn object_identifier(&self) -> ObjectIdentifier {
self.oid
}
fn object_name(&self) -> &str {
&self.name
}
fn read_property(
&self,
property: PropertyIdentifier,
array_index: Option<u32>,
) -> Result<PropertyValue, Error> {
if let Some(value) = self
.acquisition
.read(property)
.or_else(|| self.window.read(property))
{
return Ok(value);
}
let total = self.log_buffer.total_record_count();
if let Some(result) = self.reporting.read(property, array_index, total) {
return result;
}
match property {
p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
Ok(PropertyValue::ObjectIdentifier(self.oid))
}
p if p == PropertyIdentifier::OBJECT_NAME => {
Ok(PropertyValue::CharacterString(self.name.clone()))
}
p if p == PropertyIdentifier::DESCRIPTION => {
Ok(PropertyValue::CharacterString(self.description.clone()))
}
p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
ObjectType::TREND_LOG_MULTIPLE.to_raw(),
)),
p if p == PropertyIdentifier::LOG_ENABLE => Ok(PropertyValue::Boolean(self.log_enable)),
p if p == PropertyIdentifier::STOP_WHEN_FULL => {
Ok(PropertyValue::Boolean(self.stop_when_full))
}
p if p == PropertyIdentifier::BUFFER_SIZE => {
Ok(PropertyValue::Unsigned(self.buffer_size as u64))
}
p if p == PropertyIdentifier::RECORD_COUNT => {
Ok(PropertyValue::Unsigned(self.records().len() as u64))
}
p if p == PropertyIdentifier::TOTAL_RECORD_COUNT => {
Ok(PropertyValue::Unsigned(u64::from(total)))
}
p if p == PropertyIdentifier::STATUS_FLAGS => Ok(common::compute_status_flags(
StatusFlags::empty(),
self.reliability,
false,
self.reporting.event_state(),
)),
p if p == PropertyIdentifier::RELIABILITY => {
Ok(PropertyValue::Enumerated(self.reliability.to_raw()))
}
p if p == PropertyIdentifier::LOG_BUFFER => Err(log_buffer_read_denied()),
p if p == PropertyIdentifier::LOG_DEVICE_OBJECT_PROPERTY => common::read_array(
device_reference::reference_elements(&self.log_device_object_property),
array_index,
),
p if p == PropertyIdentifier::PROPERTY_LIST => {
read_property_list_property(&self.property_list(), array_index)
}
_ => Err(Error::Protocol {
class: ErrorClass::PROPERTY.to_raw() as u32,
code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
}),
}
}
fn write_property(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: PropertyValue,
_priority: Option<u8>,
) -> Result<(), Error> {
if property == PropertyIdentifier::LOG_ENABLE {
if let PropertyValue::Boolean(v) = value {
return self.lifecycle().write_enable(v);
}
return Err(Error::Protocol {
class: ErrorClass::PROPERTY.to_raw() as u32,
code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
});
}
if let Some(result) = self.acquisition.write(property, &value) {
return result;
}
if let Some(result) = self.window.write(property, &value) {
result?;
self.lifecycle().refresh_window();
return Ok(());
}
if property == PropertyIdentifier::STOP_WHEN_FULL {
if let PropertyValue::Boolean(v) = value {
return self.lifecycle().write_stop_when_full(v);
}
return Err(Error::Protocol {
class: ErrorClass::PROPERTY.to_raw() as u32,
code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
});
}
if property == PropertyIdentifier::RECORD_COUNT {
if let PropertyValue::Unsigned(0) = value {
return self.lifecycle().purge();
}
return Err(Error::Protocol {
class: ErrorClass::PROPERTY.to_raw() as u32,
code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
});
}
if property == PropertyIdentifier::LOG_DEVICE_OBJECT_PROPERTY {
return self.write_log_device_object_property(array_index, value);
}
let total = self.log_buffer.total_record_count();
if let Some(result) = self.reporting.write(property, array_index, &value, total) {
return result;
}
if let Some(result) = common::write_description(&mut self.description, property, &value) {
return result;
}
Err(crate::common::unhandled_write_error(
self.property_metadata().as_ref(),
property,
array_index,
))
}
fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
multiple_metadata::for_object(self)
}
fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
}
fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
self.clock = clock;
}
fn log_record_identities_internal(&self) -> Option<Vec<LogRecordIdentity>> {
Some(self.log_buffer.identities())
}
fn log_buffer_internal(&self) -> Option<&dyn LogBufferRecords> {
Some(&self.log_buffer)
}
fn add_trend_multiple_record(&mut self, record: BACnetLogMultipleRecord) -> Result<(), Error> {
self.add_record(record)
}
fn refresh_log_window_internal(&mut self) -> bool {
self.lifecycle().refresh_window()
}
impl_buffer_ready_reporting!(reporting, log_buffer);
}