bacnet_objects/accumulator/
pulse_converter.rs1use crate::prelude::*;
36use alloc::borrow::Cow;
37use alloc::sync::Arc;
38use bacnet_types::constructed::BACnetObjectPropertyReference;
39use bacnet_types::enums::{
40 EngineeringUnits, EventState, ObjectType, PropertyIdentifier, Reliability,
41};
42use bacnet_types::error::Error;
43use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, StatusFlags, Time};
44
45use crate::clock::{ClockReader, UNSPECIFIED_DATETIME, stamp_datetime};
46use crate::common::{self, read_common_properties};
47use crate::reference::{self, ReferenceFrame};
48use crate::traits::BACnetObject;
49
50mod metadata;
51
52#[doc(hidden)]
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub struct InputReading {
58 pub value: i128,
60 pub wraps_after: Option<u64>,
64}
65
66pub struct PulseConverterObject {
68 oid: ObjectIdentifier,
69 name: String,
70 description: String,
71 decoupled_present_value: f32,
74 units: EngineeringUnits,
75 scale_factor: f32,
76 adjust_value: f32,
77 count: u64,
79 update_time: (Date, Time),
80 count_change_time: (Date, Time),
81 count_before_change: u64,
82 cov_increment: f32,
83 input_reference: Option<BACnetObjectPropertyReference>,
84 input_unusable: bool,
87 last_input: Option<i128>,
90 status_flags: StatusFlags,
91 event_state: EventState,
92 out_of_service: bool,
93 reliability: Reliability,
94 clock: Option<Arc<dyn ClockReader>>,
96}
97
98impl PulseConverterObject {
99 pub fn new(
101 instance: u32,
102 name: impl Into<String>,
103 units: EngineeringUnits,
104 ) -> Result<Self, Error> {
105 let oid = ObjectIdentifier::new(ObjectType::PULSE_CONVERTER, instance)?;
106 Ok(Self {
107 oid,
108 name: name.into(),
109 description: String::new(),
110 decoupled_present_value: 0.0,
111 units,
112 scale_factor: 1.0,
113 adjust_value: 0.0,
114 count: 0,
115 update_time: UNSPECIFIED_DATETIME,
116 count_change_time: UNSPECIFIED_DATETIME,
117 count_before_change: 0,
118 cov_increment: 0.0,
119 input_reference: None,
120 input_unusable: false,
121 last_input: None,
122 status_flags: StatusFlags::empty(),
123 event_state: EventState::NORMAL,
124 out_of_service: false,
125 reliability: Reliability::NO_FAULT_DETECTED,
126 clock: None,
127 })
128 }
129
130 pub fn set_description(&mut self, desc: impl Into<String>) {
132 self.description = desc.into();
133 }
134
135 pub fn set_input_reference(&mut self, r: BACnetObjectPropertyReference) {
143 self.store_input_reference(reference::set_or_unset(r));
144 }
145
146 fn store_input_reference(&mut self, reference: Option<BACnetObjectPropertyReference>) {
150 if reference.is_none() {
151 self.set_input_usable_internal(true);
152 }
153 if reference != self.input_reference {
155 self.last_input = None;
156 }
157 self.input_reference = reference;
158 }
159
160 fn evaluated_reliability(&self) -> Reliability {
162 if self.input_unusable {
163 Reliability::CONFIGURATION_ERROR
164 } else {
165 Reliability::NO_FAULT_DETECTED
166 }
167 }
168
169 pub fn count(&self) -> u64 {
171 self.count
172 }
173
174 pub fn add_pulses(&mut self, pulses: u64) -> Result<(), Error> {
186 if pulses == 0 {
187 return Ok(());
188 }
189 let count = self
190 .count
191 .checked_add(pulses)
192 .ok_or_else(common::value_out_of_range_error)?;
193 scaled(count, self.scale_factor)?;
194 self.count = count;
195 self.update_time = stamp_datetime(self.clock.as_deref());
196 Ok(())
197 }
198
199 fn present_value(&self) -> f32 {
202 if self.out_of_service {
203 self.decoupled_present_value
204 } else {
205 scaled(self.count, self.scale_factor).unwrap_or_default()
207 }
208 }
209
210 fn adjust(&mut self, adjust_value: f32) -> Result<(), Error> {
219 common::reject_non_finite(adjust_value)?;
220 let quotient = crate::float::trunc(f64::from(adjust_value) / f64::from(self.scale_factor));
221 if !quotient.is_finite() || quotient.abs() > u64::MAX as f64 {
223 return Err(common::value_out_of_range_error());
224 }
225 let count = u64::try_from(i128::from(self.count) - quotient as i128)
226 .map_err(|_| common::value_out_of_range_error())?;
227 scaled(count, self.scale_factor)?;
228 self.adjust_value = adjust_value;
229 self.count_before_change = self.count;
230 self.count = count;
231 self.count_change_time = stamp_datetime(self.clock.as_deref());
232 Ok(())
233 }
234}
235
236fn scaled(count: u64, scale_factor: f32) -> Result<f32, Error> {
239 let value = (count as f64 * f64::from(scale_factor)) as f32;
240 if value.is_finite() {
241 Ok(value)
242 } else {
243 Err(common::value_out_of_range_error())
244 }
245}
246
247fn datetime_value((date, time): (Date, Time)) -> PropertyValue {
248 PropertyValue::List(vec![PropertyValue::Date(date), PropertyValue::Time(time)])
249}
250
251impl BACnetObject for PulseConverterObject {
252 fn object_identifier(&self) -> ObjectIdentifier {
253 self.oid
254 }
255
256 fn object_name(&self) -> &str {
257 &self.name
258 }
259
260 fn read_property(
261 &self,
262 property: PropertyIdentifier,
263 array_index: Option<u32>,
264 ) -> Result<PropertyValue, Error> {
265 if let Some(result) = read_common_properties!(self, property, array_index) {
266 return result;
267 }
268 match property {
269 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
270 ObjectType::PULSE_CONVERTER.to_raw(),
271 )),
272 p if p == PropertyIdentifier::PRESENT_VALUE => {
273 Ok(PropertyValue::Real(self.present_value()))
274 }
275 p if p == PropertyIdentifier::UNITS => {
276 Ok(PropertyValue::Enumerated(self.units.to_raw()))
277 }
278 p if p == PropertyIdentifier::SCALE_FACTOR => {
279 Ok(PropertyValue::Real(self.scale_factor))
280 }
281 p if p == PropertyIdentifier::ADJUST_VALUE => {
282 Ok(PropertyValue::Real(self.adjust_value))
283 }
284 p if p == PropertyIdentifier::COUNT => Ok(PropertyValue::Unsigned(self.count)),
285 p if p == PropertyIdentifier::UPDATE_TIME => Ok(datetime_value(self.update_time)),
286 p if p == PropertyIdentifier::COUNT_CHANGE_TIME => {
287 Ok(datetime_value(self.count_change_time))
288 }
289 p if p == PropertyIdentifier::COUNT_BEFORE_CHANGE => {
290 Ok(PropertyValue::Unsigned(self.count_before_change))
291 }
292 p if p == PropertyIdentifier::COV_INCREMENT => {
293 Ok(PropertyValue::Real(self.cov_increment))
294 }
295 p if p == PropertyIdentifier::COV_PERIOD => Ok(PropertyValue::Unsigned(0)),
298 p if p == PropertyIdentifier::INPUT_REFERENCE => {
302 Ok(reference::object_property_reference_value(
303 self.input_reference.as_ref(),
304 ObjectType::ACCUMULATOR,
305 ))
306 }
307 p if p == PropertyIdentifier::EVENT_STATE => {
308 Ok(PropertyValue::Enumerated(self.event_state.to_raw()))
309 }
310 _ => Err(common::unknown_property_error()),
311 }
312 }
313
314 fn write_property(
315 &mut self,
316 property: PropertyIdentifier,
317 _array_index: Option<u32>,
318 value: PropertyValue,
319 _priority: Option<u8>,
320 ) -> Result<(), Error> {
321 let in_service_value = self.present_value();
322 let was_out_of_service = self.out_of_service;
323 if let Some(result) =
324 common::write_out_of_service(&mut self.out_of_service, property, &value)
325 {
326 if self.out_of_service && !was_out_of_service {
329 self.decoupled_present_value = in_service_value;
330 }
331 if was_out_of_service && !self.out_of_service {
334 self.reliability = self.evaluated_reliability();
335 }
336 return result;
337 }
338 if let Some(result) = common::write_description(&mut self.description, property, &value) {
339 return result;
340 }
341 if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
342 {
343 return result;
344 }
345 match property {
346 p if p == PropertyIdentifier::PRESENT_VALUE => {
347 if !self.out_of_service {
348 return Err(common::write_access_denied_error());
349 }
350 if let PropertyValue::Real(v) = value {
351 common::reject_non_finite(v)?;
352 self.decoupled_present_value = v;
353 Ok(())
354 } else {
355 Err(common::invalid_data_type_error())
356 }
357 }
358 p if p == PropertyIdentifier::SCALE_FACTOR => {
359 if let PropertyValue::Real(v) = value {
360 common::reject_non_finite(v)?;
361 scaled(self.count, v)?;
362 self.scale_factor = v;
363 Ok(())
364 } else {
365 Err(common::invalid_data_type_error())
366 }
367 }
368 p if p == PropertyIdentifier::ADJUST_VALUE => {
369 if let PropertyValue::Real(v) = value {
370 self.adjust(v)
371 } else {
372 Err(common::invalid_data_type_error())
373 }
374 }
375 p if p == PropertyIdentifier::RELIABILITY => {
379 if !self.out_of_service {
380 return Err(common::write_access_denied_error());
381 }
382 let PropertyValue::Enumerated(raw) = value else {
383 return Err(common::invalid_data_type_error());
384 };
385 let reliability = Reliability::from_raw(raw);
386 if !common::is_reliability_value_valid(reliability) {
387 return Err(common::value_out_of_range_error());
388 }
389 self.reliability = reliability;
390 Ok(())
391 }
392 p if p == PropertyIdentifier::INPUT_REFERENCE => {
395 let reference = reference::decode_reference_write(&value, ReferenceFrame::Bare)?;
396 self.store_input_reference(reference);
397 Ok(())
398 }
399 _ => Err(crate::common::unhandled_write_error(
400 self.property_metadata().as_ref(),
401 property,
402 _array_index,
403 )),
404 }
405 }
406
407 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
408 metadata::for_pulse_converter_object(self)
409 }
410
411 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
412 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
413 }
414
415 fn supports_cov(&self) -> bool {
416 true
417 }
418
419 fn cov_increment(&self) -> Option<f64> {
420 Some(f64::from(self.cov_increment))
421 }
422
423 fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
424 self.clock = clock;
425 }
426
427 fn input_reference_internal(&self) -> Option<Option<&BACnetObjectPropertyReference>> {
428 Some(self.input_reference.as_ref())
429 }
430
431 fn set_input_usable_internal(&mut self, usable: bool) -> bool {
432 self.input_unusable = !usable;
433 if self.out_of_service {
434 return false;
435 }
436 let reliability = self.evaluated_reliability();
437 core::mem::replace(&mut self.reliability, reliability) != reliability
438 }
439
440 fn take_input_reading_internal(&mut self, reading: Option<InputReading>) -> bool {
441 let Some(reading) = reading else {
442 self.last_input = None;
443 return false;
444 };
445 let Some(last) = self.last_input.replace(reading.value) else {
446 return false;
447 };
448 let pulses = if reading.value >= last {
449 reading.value - last
450 } else {
451 match reading.wraps_after.map(i128::from) {
452 Some(max) if last <= max => max - last + 1 + reading.value,
455 _ => return false,
456 }
457 };
458 if pulses == 0 {
459 return false;
460 }
461 self.add_pulses(u64::try_from(pulses).unwrap_or(u64::MAX))
462 .is_ok()
463 }
464}
465
466#[cfg(test)]
467#[path = "../pulse_converter_policy_tests.rs"]
468mod pulse_converter_policy_tests;
469
470#[cfg(test)]
471mod tests;