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