use bacnet_types::constructed::BACnetObjectPropertyReference;
use bacnet_types::enums::{EventState, ObjectType, PropertyIdentifier, Reliability};
use bacnet_types::error::Error;
use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, StatusFlags, Time};
use std::borrow::Cow;
use std::sync::Arc;
use crate::clock::{stamp_datetime, ClockReader, UNSPECIFIED_DATETIME};
use crate::common::{self, read_common_properties};
use crate::reference::{self, ReferenceFrame};
use crate::traits::BACnetObject;
mod metadata;
#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InputReading {
pub value: i128,
pub wraps_after: Option<u64>,
}
pub struct PulseConverterObject {
oid: ObjectIdentifier,
name: String,
description: String,
decoupled_present_value: f32,
units: u32,
scale_factor: f32,
adjust_value: f32,
count: u64,
update_time: (Date, Time),
count_change_time: (Date, Time),
count_before_change: u64,
cov_increment: f32,
input_reference: Option<BACnetObjectPropertyReference>,
input_unusable: bool,
last_input: Option<i128>,
status_flags: StatusFlags,
event_state: EventState,
out_of_service: bool,
reliability: Reliability,
clock: Option<Arc<dyn ClockReader>>,
}
impl PulseConverterObject {
pub fn new(instance: u32, name: impl Into<String>, units: u32) -> Result<Self, Error> {
let oid = ObjectIdentifier::new(ObjectType::PULSE_CONVERTER, instance)?;
Ok(Self {
oid,
name: name.into(),
description: String::new(),
decoupled_present_value: 0.0,
units,
scale_factor: 1.0,
adjust_value: 0.0,
count: 0,
update_time: UNSPECIFIED_DATETIME,
count_change_time: UNSPECIFIED_DATETIME,
count_before_change: 0,
cov_increment: 0.0,
input_reference: None,
input_unusable: false,
last_input: None,
status_flags: StatusFlags::empty(),
event_state: EventState::NORMAL,
out_of_service: false,
reliability: Reliability::NO_FAULT_DETECTED,
clock: None,
})
}
pub fn set_description(&mut self, desc: impl Into<String>) {
self.description = desc.into();
}
pub fn set_input_reference(&mut self, r: BACnetObjectPropertyReference) {
self.store_input_reference(reference::set_or_unset(r));
}
fn store_input_reference(&mut self, reference: Option<BACnetObjectPropertyReference>) {
if reference.is_none() {
self.set_input_usable_internal(true);
}
if reference != self.input_reference {
self.last_input = None;
}
self.input_reference = reference;
}
fn evaluated_reliability(&self) -> Reliability {
if self.input_unusable {
Reliability::CONFIGURATION_ERROR
} else {
Reliability::NO_FAULT_DETECTED
}
}
pub fn count(&self) -> u64 {
self.count
}
pub fn add_pulses(&mut self, pulses: u64) -> Result<(), Error> {
if pulses == 0 {
return Ok(());
}
let count = self
.count
.checked_add(pulses)
.ok_or_else(common::value_out_of_range_error)?;
scaled(count, self.scale_factor)?;
self.count = count;
self.update_time = stamp_datetime(self.clock.as_deref());
Ok(())
}
fn present_value(&self) -> f32 {
if self.out_of_service {
self.decoupled_present_value
} else {
scaled(self.count, self.scale_factor).unwrap_or_default()
}
}
fn adjust(&mut self, adjust_value: f32) -> Result<(), Error> {
common::reject_non_finite(adjust_value)?;
let quotient = (f64::from(adjust_value) / f64::from(self.scale_factor)).trunc();
if !quotient.is_finite() || quotient.abs() > u64::MAX as f64 {
return Err(common::value_out_of_range_error());
}
let count = u64::try_from(i128::from(self.count) - quotient as i128)
.map_err(|_| common::value_out_of_range_error())?;
scaled(count, self.scale_factor)?;
self.adjust_value = adjust_value;
self.count_before_change = self.count;
self.count = count;
self.count_change_time = stamp_datetime(self.clock.as_deref());
Ok(())
}
}
fn scaled(count: u64, scale_factor: f32) -> Result<f32, Error> {
let value = (count as f64 * f64::from(scale_factor)) as f32;
if value.is_finite() {
Ok(value)
} else {
Err(common::value_out_of_range_error())
}
}
fn datetime_value((date, time): (Date, Time)) -> PropertyValue {
PropertyValue::List(vec![PropertyValue::Date(date), PropertyValue::Time(time)])
}
impl BACnetObject for PulseConverterObject {
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(result) = read_common_properties!(self, property, array_index) {
return result;
}
match property {
p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
ObjectType::PULSE_CONVERTER.to_raw(),
)),
p if p == PropertyIdentifier::PRESENT_VALUE => {
Ok(PropertyValue::Real(self.present_value()))
}
p if p == PropertyIdentifier::UNITS => Ok(PropertyValue::Enumerated(self.units)),
p if p == PropertyIdentifier::SCALE_FACTOR => {
Ok(PropertyValue::Real(self.scale_factor))
}
p if p == PropertyIdentifier::ADJUST_VALUE => {
Ok(PropertyValue::Real(self.adjust_value))
}
p if p == PropertyIdentifier::COUNT => Ok(PropertyValue::Unsigned(self.count)),
p if p == PropertyIdentifier::UPDATE_TIME => Ok(datetime_value(self.update_time)),
p if p == PropertyIdentifier::COUNT_CHANGE_TIME => {
Ok(datetime_value(self.count_change_time))
}
p if p == PropertyIdentifier::COUNT_BEFORE_CHANGE => {
Ok(PropertyValue::Unsigned(self.count_before_change))
}
p if p == PropertyIdentifier::COV_INCREMENT => {
Ok(PropertyValue::Real(self.cov_increment))
}
p if p == PropertyIdentifier::COV_PERIOD => Ok(PropertyValue::Unsigned(0)),
p if p == PropertyIdentifier::INPUT_REFERENCE => {
Ok(reference::object_property_reference_value(
self.input_reference.as_ref(),
ObjectType::ACCUMULATOR,
))
}
p if p == PropertyIdentifier::EVENT_STATE => {
Ok(PropertyValue::Enumerated(self.event_state.to_raw()))
}
_ => Err(common::unknown_property_error()),
}
}
fn write_property(
&mut self,
property: PropertyIdentifier,
_array_index: Option<u32>,
value: PropertyValue,
_priority: Option<u8>,
) -> Result<(), Error> {
let in_service_value = self.present_value();
let was_out_of_service = self.out_of_service;
if let Some(result) =
common::write_out_of_service(&mut self.out_of_service, property, &value)
{
if self.out_of_service && !was_out_of_service {
self.decoupled_present_value = in_service_value;
}
if was_out_of_service && !self.out_of_service {
self.reliability = self.evaluated_reliability();
}
return result;
}
if let Some(result) = common::write_description(&mut self.description, property, &value) {
return result;
}
if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
{
return result;
}
match property {
p if p == PropertyIdentifier::PRESENT_VALUE => {
if !self.out_of_service {
return Err(common::write_access_denied_error());
}
if let PropertyValue::Real(v) = value {
common::reject_non_finite(v)?;
self.decoupled_present_value = v;
Ok(())
} else {
Err(common::invalid_data_type_error())
}
}
p if p == PropertyIdentifier::SCALE_FACTOR => {
if let PropertyValue::Real(v) = value {
common::reject_non_finite(v)?;
scaled(self.count, v)?;
self.scale_factor = v;
Ok(())
} else {
Err(common::invalid_data_type_error())
}
}
p if p == PropertyIdentifier::ADJUST_VALUE => {
if let PropertyValue::Real(v) = value {
self.adjust(v)
} else {
Err(common::invalid_data_type_error())
}
}
p if p == PropertyIdentifier::RELIABILITY => {
if !self.out_of_service {
return Err(common::write_access_denied_error());
}
let PropertyValue::Enumerated(raw) = value else {
return Err(common::invalid_data_type_error());
};
let reliability = Reliability::from_raw(raw);
if !common::is_reliability_value_valid(reliability) {
return Err(common::value_out_of_range_error());
}
self.reliability = reliability;
Ok(())
}
p if p == PropertyIdentifier::INPUT_REFERENCE => {
let reference = reference::decode_reference_write(&value, ReferenceFrame::Bare)?;
self.store_input_reference(reference);
Ok(())
}
_ => Err(crate::common::unhandled_write_error(
self.property_metadata().as_ref(),
property,
_array_index,
)),
}
}
fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
metadata::for_pulse_converter_object(self)
}
fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
}
fn supports_cov(&self) -> bool {
true
}
fn cov_increment(&self) -> Option<f64> {
Some(f64::from(self.cov_increment))
}
fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
self.clock = clock;
}
fn input_reference_internal(&self) -> Option<Option<&BACnetObjectPropertyReference>> {
Some(self.input_reference.as_ref())
}
fn set_input_usable_internal(&mut self, usable: bool) -> bool {
self.input_unusable = !usable;
if self.out_of_service {
return false;
}
let reliability = self.evaluated_reliability();
std::mem::replace(&mut self.reliability, reliability) != reliability
}
fn take_input_reading_internal(&mut self, reading: Option<InputReading>) -> bool {
let Some(reading) = reading else {
self.last_input = None;
return false;
};
let Some(last) = self.last_input.replace(reading.value) else {
return false;
};
let pulses = if reading.value >= last {
reading.value - last
} else {
match reading.wraps_after.map(i128::from) {
Some(max) if last <= max => max - last + 1 + reading.value,
_ => return false,
}
};
if pulses == 0 {
return false;
}
self.add_pulses(u64::try_from(pulses).unwrap_or(u64::MAX))
.is_ok()
}
}
#[cfg(test)]
#[path = "../pulse_converter_policy_tests.rs"]
mod pulse_converter_policy_tests;
#[cfg(test)]
mod tests;