use bacnet_types::constructed::BACnetDeviceObjectReference;
use bacnet_types::enums::{ObjectType, PropertyIdentifier};
use bacnet_types::error::Error;
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
use crate::common;
const METER_TYPES: [ObjectType; 7] = [
ObjectType::ACCUMULATOR,
ObjectType::PULSE_CONVERTER,
ObjectType::ANALOG_INPUT,
ObjectType::ANALOG_VALUE,
ObjectType::LARGE_ANALOG_VALUE,
ObjectType::INTEGER_VALUE,
ObjectType::POSITIVE_INTEGER_VALUE,
];
const PROPRIETARY_TYPES: std::ops::RangeInclusive<u32> = 128..=1023;
fn is_meter_type(object_type: ObjectType) -> bool {
METER_TYPES.contains(&object_type) || PROPRIETARY_TYPES.contains(&object_type.to_raw())
}
#[derive(Debug, Clone, PartialEq)]
pub(super) struct EnergyMeter {
reading: f32,
reference: BACnetDeviceObjectReference,
}
impl EnergyMeter {
pub(super) fn new() -> Result<Self, Error> {
Ok(Self {
reading: 0.0,
reference: BACnetDeviceObjectReference {
device_identifier: None,
object_identifier: ObjectIdentifier::new(
ObjectType::ACCUMULATOR,
ObjectIdentifier::MAX_INSTANCE,
)?,
},
})
}
pub(super) fn reference(&self) -> &BACnetDeviceObjectReference {
&self.reference
}
fn is_referenced(&self) -> bool {
self.reference.object_identifier.instance_number() != ObjectIdentifier::MAX_INSTANCE
}
pub(super) fn read(&self, property: PropertyIdentifier) -> Option<PropertyValue> {
match property {
PropertyIdentifier::ENERGY_METER => Some(PropertyValue::Real(self.reading)),
PropertyIdentifier::ENERGY_METER_REF => {
Some(crate::device_reference::reference_value(&self.reference))
}
_ => None,
}
}
pub(super) fn write(
&mut self,
property: PropertyIdentifier,
value: &PropertyValue,
) -> Option<Result<(), Error>> {
if property != PropertyIdentifier::ENERGY_METER {
return None;
}
let &PropertyValue::Real(reading) = value else {
return Some(Err(common::invalid_data_type_error()));
};
Some(self.store_reading(reading))
}
fn store_reading(&mut self, reading: f32) -> Result<(), Error> {
common::reject_non_finite(reading)?;
if !self.is_referenced() {
self.reading = reading;
} else if reading != 0.0 {
return Err(common::value_out_of_range_error());
}
Ok(())
}
pub(super) fn set_reference(
&mut self,
reference: BACnetDeviceObjectReference,
) -> Result<(), Error> {
crate::device_reference::check_device_member(reference.device_identifier)?;
if !is_meter_type(reference.object_identifier.object_type()) {
return Err(common::value_out_of_range_error());
}
self.reference = reference;
if self.is_referenced() {
self.reading = 0.0;
}
Ok(())
}
}
macro_rules! energy_meter_accessors {
($object:literal) => {
pub fn energy_meter_ref(&self) -> &BACnetDeviceObjectReference {
self.energy_meter.reference()
}
#[doc = concat!("used by this ", $object, ".")]
pub fn set_energy_meter_ref(
&mut self,
reference: BACnetDeviceObjectReference,
) -> Result<(), Error> {
self.energy_meter.set_reference(reference)
}
};
}
pub(super) use energy_meter_accessors;