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