1#[cfg(feature = "std")]
2use std::fmt;
3
4use super::data_information::DataInformationError;
5
6#[derive(Clone, Copy, Debug, PartialEq)]
7struct VifInfo {
8 labels: &'static [ValueLabel],
9 units: &'static [Unit],
10 scale: isize,
11 offset: isize,
12}
13impl VifInfo {
14 const EMPTY: Self = Self {
15 labels: &[],
16 units: &[],
17 scale: 0,
18 offset: 0,
19 };
20}
21macro_rules! labels {
22 ($($label:expr),+ $(,)?) => { VifInfo { labels: &[$($label),+], ..VifInfo::EMPTY } };
23}
24macro_rules! units {
25 ($($unit:expr),+ $(,)?) => { VifInfo { units: &[$($unit),+], ..VifInfo::EMPTY } };
26}
27
28const MAX_VIFE_RECORDS: usize = 10;
29
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31#[derive(Debug, PartialEq, Copy, Clone)]
32#[cfg_attr(feature = "defmt", derive(defmt::Format))]
33pub struct Unit {
34 pub name: UnitName,
35 pub exponent: i32,
36}
37macro_rules! unit {
38 ($name:ident) => {
39 Unit {
40 name: UnitName::$name,
41 exponent: 1,
42 }
43 };
44 ($name:ident ^ $exponent:literal) => {
45 Unit {
46 name: UnitName::$name,
47 exponent: $exponent,
48 }
49 };
50}
51
52impl<'a> TryFrom<&'a [u8]> for ValueInformationBlock<'a> {
53 type Error = DataInformationError;
54
55 fn try_from(data: &'a [u8]) -> Result<Self, DataInformationError> {
56 let vif =
57 ValueInformationField::from(*data.first().ok_or(DataInformationError::DataTooShort)?);
58 let mut offset = 1;
59 let mut value_information_extension = None;
60 let mut plaintext_vife = None;
61
62 #[cfg(feature = "plaintext-before-extension")]
63 if vif.value_information_contains_ascii() {
64 let plaintext = PlainTextValueInformationExtension::new(
65 data.get(offset..)
66 .ok_or(DataInformationError::DataTooShort)?,
67 )?;
68 offset += plaintext.ascii_len() + 1;
69 plaintext_vife = Some(plaintext);
70 }
71
72 if vif.has_extension() {
73 let extensions = ValueInformationFieldExtensions::new(
76 data.get(offset..)
77 .ok_or(DataInformationError::DataTooShort)?,
78 )?;
79 #[cfg(not(feature = "plaintext-before-extension"))]
80 {
81 offset += extensions.len();
82 }
83 value_information_extension = Some(extensions);
84 }
85
86 #[cfg(not(feature = "plaintext-before-extension"))]
87 if vif.value_information_contains_ascii() {
88 plaintext_vife = Some(PlainTextValueInformationExtension::new(
89 data.get(offset..)
90 .ok_or(DataInformationError::DataTooShort)?,
91 )?);
92 }
93
94 Ok(Self {
95 value_information: vif,
96 value_information_extension,
97 plaintext_vife,
98 })
99 }
100}
101
102#[cfg_attr(feature = "serde", derive(serde::Serialize))]
103#[derive(Debug, PartialEq, Clone)]
104pub struct ValueInformationBlock<'a> {
105 pub value_information: ValueInformationField,
106 pub value_information_extension: Option<ValueInformationFieldExtensions<'a>>,
107 pub plaintext_vife: Option<PlainTextValueInformationExtension<'a>>,
108}
109
110#[cfg(feature = "defmt")]
111impl<'a> defmt::Format for ValueInformationBlock<'a> {
112 fn format(&self, f: defmt::Formatter) {
113 defmt::write!(
114 f,
115 "ValueInformationBlock{{ value_information: {:?}",
116 self.value_information
117 );
118 if let Some(ext) = &self.value_information_extension {
119 defmt::write!(f, ", value_information_extension: [");
120 ext.iter().for_each(|x| defmt::write!(f, "{},", x));
121 defmt::write!(f, "]");
122 }
123 if let Some(text) = &self.plaintext_vife {
124 defmt::write!(f, ", plaintext_vife: {}", text.as_ascii_str());
125 }
126 defmt::write!(f, " }}");
127 }
128}
129#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
130#[derive(Debug, PartialEq, Clone)]
131#[cfg_attr(feature = "defmt", derive(defmt::Format))]
132pub struct ValueInformationField {
133 pub data: u8,
134}
135
136impl ValueInformationField {
137 const fn value_information_contains_ascii(&self) -> bool {
138 self.data == 0x7C || self.data == 0xFC
139 }
140}
141
142#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
143#[derive(Clone, Debug, PartialEq)]
144#[cfg_attr(feature = "defmt", derive(defmt::Format))]
145pub struct ValueInformationFieldExtensions<'a>(&'a [u8]);
146
147#[cfg(feature = "serde")]
148impl serde::Serialize for ValueInformationFieldExtensions<'_> {
149 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
150 where
151 S: serde::Serializer,
152 {
153 serializer.collect_seq(self.iter())
154 }
155}
156
157impl<'a> ValueInformationFieldExtensions<'a> {
158 fn new(data: &'a [u8]) -> Result<Self, DataInformationError> {
159 let Some(last_index) = data
160 .iter()
161 .take(MAX_VIFE_RECORDS + 1)
162 .position(|byte| byte & 0x80 == 0)
163 else {
164 return Err(if data.len() > MAX_VIFE_RECORDS {
165 DataInformationError::InvalidValueInformation
166 } else {
167 DataInformationError::DataTooShort
168 });
169 };
170
171 let length = last_index + 1;
172 if length > MAX_VIFE_RECORDS {
173 return Err(DataInformationError::InvalidValueInformation);
174 }
175
176 Ok(Self(
177 data.get(..length)
178 .ok_or(DataInformationError::DataTooShort)?,
179 ))
180 }
181}
182
183impl Iterator for ValueInformationFieldExtensions<'_> {
184 type Item = ValueInformationFieldExtension;
185 fn next(&mut self) -> Option<Self::Item> {
186 let (head, tail) = self.0.split_first()?;
187 self.0 = tail;
188 Some(ValueInformationFieldExtension { data: *head })
189 }
190 fn size_hint(&self) -> (usize, Option<usize>) {
191 (self.0.len(), Some(self.0.len()))
192 }
193}
194
195impl ExactSizeIterator for ValueInformationFieldExtensions<'_> {}
196impl DoubleEndedIterator for ValueInformationFieldExtensions<'_> {
197 fn next_back(&mut self) -> Option<Self::Item> {
198 let (end, start) = self.0.split_last()?;
199 self.0 = start;
200 Some(ValueInformationFieldExtension { data: *end })
201 }
202}
203
204impl<'a> ValueInformationFieldExtensions<'a> {
205 pub fn iter(
206 &self,
207 ) -> impl DoubleEndedIterator<Item = ValueInformationFieldExtension> + ExactSizeIterator + '_
208 {
209 self.0
210 .iter()
211 .copied()
212 .map(|data| ValueInformationFieldExtension { data })
213 }
214}
215
216#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
217#[derive(Clone, Debug, PartialEq)]
218#[cfg_attr(feature = "defmt", derive(defmt::Format))]
219pub struct PlainTextValueInformationExtension<'a>(&'a [u8]);
220
221#[cfg(feature = "serde")]
222impl serde::Serialize for PlainTextValueInformationExtension<'_> {
223 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
224 where
225 S: serde::Serializer,
226 {
227 let plaintext = self.as_ascii_str().ok_or_else(|| {
228 <S::Error as serde::ser::Error>::custom("invalid plaintext VIFE encoding")
229 })?;
230
231 serializer.collect_seq(plaintext.chars())
232 }
233}
234
235impl<'a> PlainTextValueInformationExtension<'a> {
236 fn new(data: &'a [u8]) -> Result<Self, DataInformationError> {
237 let ascii_len = usize::from(*data.first().ok_or(DataInformationError::DataTooShort)?);
238
239 if ascii_len > 9 {
240 return Err(DataInformationError::InvalidValueInformation);
241 }
242
243 let encoded = data
244 .get(..ascii_len + 1)
245 .ok_or(DataInformationError::DataTooShort)?;
246
247 if !encoded[1..].is_ascii() {
248 return Err(DataInformationError::InvalidValueInformation);
249 }
250
251 Ok(Self(encoded))
252 }
253
254 pub const fn ascii_len(&self) -> usize {
255 if let Some(x) = self.0.first() {
256 *x as usize
257 } else {
258 0
259 }
260 }
261
262 pub fn as_ascii_str(&self) -> Option<&str> {
263 core::str::from_utf8(self.0.get(1..)?).ok()
264 }
265}
266
267#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
268#[derive(Debug, PartialEq, Clone)]
269#[cfg_attr(feature = "defmt", derive(defmt::Format))]
270pub struct ValueInformationFieldExtension {
271 pub data: u8,
272}
273
274impl From<&ValueInformationField> for ValueInformationCoding {
275 fn from(value_information: &ValueInformationField) -> Self {
276 match value_information.data {
277 0x00..=0x7B | 0x80..=0xFA => Self::Primary,
278 0x7C | 0xFC => Self::PlainText,
279 0xFD => Self::MainVIFExtension,
280 0xFB => Self::AlternateVIFExtension,
281 0x7E => Self::ManufacturerSpecific,
282 0xFE => Self::ManufacturerSpecific,
283 0x7F => Self::ManufacturerSpecific,
284 0xFF => Self::ManufacturerSpecific,
285 _ => unreachable!("Invalid value information: {:X}", value_information.data),
286 }
287 }
288}
289
290impl ValueInformationField {
291 const fn has_extension(&self) -> bool {
292 self.data & 0x80 != 0
293 }
294}
295
296#[derive(Debug, Clone, Copy, PartialEq)]
297#[cfg_attr(feature = "defmt", derive(defmt::Format))]
298#[non_exhaustive]
299pub enum ValueInformationCoding {
300 Primary,
301 PlainText,
302 MainVIFExtension,
303 AlternateVIFExtension,
304 ManufacturerSpecific,
305}
306
307impl<'a> ValueInformationBlock<'a> {
308 pub fn new(
309 value_information: ValueInformationField,
310 value_information_extension: Option<ValueInformationFieldExtensions<'a>>,
311 plaintext_vife: Option<PlainTextValueInformationExtension<'a>>,
312 ) -> Self {
313 Self {
314 value_information,
315 value_information_extension,
316 plaintext_vife,
317 }
318 }
319
320 #[must_use]
321 pub fn get_size(&self) -> usize {
322 let mut size = 1;
323 if let Some(vife) = &self.value_information_extension {
324 size += vife.0.len();
325 }
326 if let Some(plaintext_vife) = &self.plaintext_vife {
327 size += plaintext_vife.ascii_len() + 1;
329 }
330 size
331 }
332}
333
334fn head_vif_info(
335 vif: ValueInformationField,
336 first_vife: Option<u8>,
337 second_vife_data: Option<u8>,
338) -> Result<VifInfo, DataInformationError> {
339 Ok(match ValueInformationCoding::from(&vif) {
340 ValueInformationCoding::Primary => match vif.data & 0x7F {
341 0x00..=0x07 => VifInfo {
342 labels: &[ValueLabel::Energy],
343 units: &[unit!(Watt), unit!(Hour)],
344 scale: (vif.data & 0b111) as isize - 3,
345 ..VifInfo::EMPTY
346 },
347 0x08..=0x0F => VifInfo {
348 labels: &[ValueLabel::Energy],
349 units: &[unit!(Joul)],
350 scale: (vif.data & 0b111) as isize,
351 ..VifInfo::EMPTY
352 },
353 0x10..=0x17 => VifInfo {
354 labels: &[ValueLabel::Volume],
355 units: &[unit!(Meter ^ 3)],
356 scale: (vif.data & 0b111) as isize - 6,
357 ..VifInfo::EMPTY
358 },
359 0x18..=0x1F => VifInfo {
360 labels: &[ValueLabel::Mass],
361 units: &[unit!(Kilogram)],
362 scale: (vif.data & 0b111) as isize - 3,
363 ..VifInfo::EMPTY
364 },
365 0x20..=0x23 => {
366 return Ok(VifInfo {
367 labels: &[ValueLabel::OnTime],
368 units: match vif.data & 3 {
369 0 => &[unit!(Second)],
370 1 => &[unit!(Minute)],
371 2 => &[unit!(Hour)],
372 _ => &[unit!(Day)],
373 },
374 ..VifInfo::EMPTY
375 });
376 }
377 0x24..=0x27 => {
378 return Ok(VifInfo {
379 labels: &[ValueLabel::OperatingTime],
380 units: match vif.data & 3 {
381 0 => &[unit!(Second)],
382 1 => &[unit!(Minute)],
383 2 => &[unit!(Hour)],
384 _ => &[unit!(Day)],
385 },
386 ..VifInfo::EMPTY
387 });
388 }
389 0x28..=0x2F => VifInfo {
390 labels: &[ValueLabel::Power],
391 units: &[unit!(Watt)],
392 scale: (vif.data & 0b111) as isize - 3,
393 ..VifInfo::EMPTY
394 },
395 0x30..=0x37 => VifInfo {
396 labels: &[ValueLabel::Power],
397 units: &[unit!(Joul), unit!(Hour ^ -1)],
398 scale: (vif.data & 0b111) as isize,
399 ..VifInfo::EMPTY
400 },
401 0x38..=0x3F => VifInfo {
402 labels: &[ValueLabel::VolumeFlow],
403 units: &[unit!(Meter ^ 3), unit!(Hour ^ -1)],
404 scale: (vif.data & 0b111) as isize - 6,
405 ..VifInfo::EMPTY
406 },
407 0x40..=0x47 => VifInfo {
408 labels: &[ValueLabel::VolumeFlow],
409 units: &[unit!(Meter ^ 3), unit!(Minute ^ -1)],
410 scale: (vif.data & 0b111) as isize - 7,
411 ..VifInfo::EMPTY
412 },
413 0x48..=0x4F => VifInfo {
414 labels: &[ValueLabel::VolumeFlow],
415 units: &[unit!(Meter ^ 3), unit!(Second ^ -1)],
416 scale: (vif.data & 0b111) as isize - 9,
417 ..VifInfo::EMPTY
418 },
419 0x50..=0x57 => VifInfo {
420 labels: &[ValueLabel::MassFlow],
421 units: &[unit!(Kilogram), unit!(Hour ^ -1)],
422 scale: (vif.data & 0b111) as isize - 3,
423 ..VifInfo::EMPTY
424 },
425 0x58..=0x5B => VifInfo {
426 labels: &[ValueLabel::FlowTemperature],
427 units: &[unit!(Celsius)],
428 scale: (vif.data & 0b11) as isize - 3,
429 ..VifInfo::EMPTY
430 },
431 0x5C..=0x5F => VifInfo {
432 labels: &[ValueLabel::ReturnTemperature],
433 units: &[unit!(Celsius)],
434 scale: (vif.data & 0b11) as isize - 3,
435 ..VifInfo::EMPTY
436 },
437 0x60..=0x63 => VifInfo {
438 labels: &[ValueLabel::TemperatureDifference],
439 units: &[unit!(Kelvin)],
440 scale: (vif.data & 0b11) as isize - 3,
441 ..VifInfo::EMPTY
442 },
443 0x64..=0x67 => VifInfo {
444 labels: &[ValueLabel::ExternalTemperature],
445 units: &[unit!(Celsius)],
446 scale: (vif.data & 0b11) as isize - 3,
447 ..VifInfo::EMPTY
448 },
449 0x68..=0x6B => VifInfo {
450 labels: &[ValueLabel::Pressure],
451 units: &[unit!(Bar)],
452 scale: (vif.data & 0b11) as isize - 3,
453 ..VifInfo::EMPTY
454 },
455 0x6C => labels!(ValueLabel::Date),
456 0x6D => labels!(ValueLabel::DateTime),
457 0x6E => labels!(ValueLabel::DimensionlessHCA),
458 0x70..=0x73 => labels!(ValueLabel::AveragingDuration),
459 0x74..=0x77 => labels!(ValueLabel::ActualityDuration),
460 0x78 => labels!(ValueLabel::FabricationNumber),
461 0x79 => labels!(ValueLabel::EnhancedIdentification),
462 0x7A => labels!(ValueLabel::Address),
463 0x7B => VifInfo::EMPTY,
464
465 _ => {
466 return Err(DataInformationError::Unimplemented {
467 feature: "Primary value information unit codes (partial)",
468 })
469 }
470 },
471 ValueInformationCoding::MainVIFExtension => {
472 let Some(first_vife_data) = first_vife else {
473 return Ok(VifInfo::EMPTY);
474 };
475 match first_vife_data & 0x7F {
476 0x00..=0x03 => VifInfo {
477 labels: &[ValueLabel::Credit],
478 units: &[unit!(LocalMoneyCurrency)],
479 scale: (first_vife_data & 0b11) as isize - 3,
480 ..VifInfo::EMPTY
481 },
482 0x04..=0x07 => VifInfo {
483 labels: &[ValueLabel::Debit],
484 units: &[unit!(LocalMoneyCurrency)],
485 scale: (first_vife_data & 0b11) as isize - 3,
486 ..VifInfo::EMPTY
487 },
488 0x08 => labels!(ValueLabel::UniqueMessageIdentificationOrAccessNumber),
489 0x09 => labels!(ValueLabel::DeviceType),
490 0x0A => labels!(ValueLabel::Manufacturer),
491 0x0B => labels!(ValueLabel::ParameterSetIdentification),
492 0x0C => labels!(ValueLabel::ModelOrVersion),
493 0x0D => labels!(ValueLabel::HardwareVersion),
494 0x0E => labels!(ValueLabel::MetrologyFirmwareVersion),
495 0x0F => labels!(ValueLabel::OtherSoftwareVersion),
496 0x10 => labels!(ValueLabel::CustomerLocation),
497 0x11 => labels!(ValueLabel::Customer),
498 0x12 => labels!(ValueLabel::AccessCodeUser),
499 0x13 => labels!(ValueLabel::AccessCodeOperator),
500 0x14 => labels!(ValueLabel::AccessCodeSystemOperator),
501 0x15 => labels!(ValueLabel::AccessCodeDeveloper),
502 0x16 => labels!(ValueLabel::Password),
503 0x17 => labels!(ValueLabel::ErrorFlags),
504 0x18 => labels!(ValueLabel::ErrorMask),
505 0x19 => labels!(ValueLabel::SecurityKey),
506 0x1A => VifInfo {
507 labels: &[ValueLabel::DigitalOutput, ValueLabel::Binary],
508 ..VifInfo::EMPTY
509 },
510 0x1B => VifInfo {
511 labels: &[ValueLabel::DigitalInput, ValueLabel::Binary],
512 ..VifInfo::EMPTY
513 },
514 0x1C => VifInfo {
515 labels: &[ValueLabel::BaudRate],
516 units: &[unit!(Symbol), unit!(Second ^ -1)],
517 ..VifInfo::EMPTY
518 },
519 0x1D => VifInfo {
520 labels: &[ValueLabel::ResponseDelayTime],
521 units: &[unit!(BitTime)],
522 ..VifInfo::EMPTY
523 },
524 0x1E => labels!(ValueLabel::Retry),
525 0x1F => labels!(ValueLabel::RemoteControl),
526 0x20 => labels!(ValueLabel::FirstStorageForCycleStorage),
527 0x21 => labels!(ValueLabel::LastStorageForCycleStorage),
528 0x22 => labels!(ValueLabel::SizeOfStorageBlock),
529 0x23 => labels!(ValueLabel::DescriptionOfTariffAndSubunit),
530 0x24 => VifInfo {
531 labels: &[ValueLabel::StorageInterval],
532 units: &[unit!(Second)],
533 ..VifInfo::EMPTY
534 },
535 0x25 => VifInfo {
536 labels: &[ValueLabel::StorageInterval],
537 units: &[unit!(Minute)],
538 ..VifInfo::EMPTY
539 },
540 0x26 => VifInfo {
541 labels: &[ValueLabel::StorageInterval],
542 units: &[unit!(Hour)],
543 ..VifInfo::EMPTY
544 },
545 0x27 => VifInfo {
546 labels: &[ValueLabel::StorageInterval],
547 units: &[unit!(Day)],
548 ..VifInfo::EMPTY
549 },
550 0x28 => VifInfo {
551 labels: &[ValueLabel::StorageInterval],
552 units: &[unit!(Month)],
553 ..VifInfo::EMPTY
554 },
555 0x29 => VifInfo {
556 labels: &[ValueLabel::StorageInterval],
557 units: &[unit!(Year)],
558 ..VifInfo::EMPTY
559 },
560 0x30 => labels!(ValueLabel::DimensionlessHCA),
561 0x31 => labels!(ValueLabel::DataContainerForWmbusProtocol),
562 0x32 => VifInfo {
563 labels: &[ValueLabel::PeriodOfNormalDataTransmission],
564 units: &[unit!(Second)],
565 ..VifInfo::EMPTY
566 },
567 0x33 => VifInfo {
568 labels: &[ValueLabel::PeriodOfNormalDataTransmission],
569 units: &[unit!(Meter)],
570 ..VifInfo::EMPTY
571 },
572 0x34 => VifInfo {
573 labels: &[ValueLabel::PeriodOfNormalDataTransmission],
574 units: &[unit!(Hour)],
575 ..VifInfo::EMPTY
576 },
577 0x35 => VifInfo {
578 labels: &[ValueLabel::PeriodOfNormalDataTransmission],
579 units: &[unit!(Day)],
580 ..VifInfo::EMPTY
581 },
582 0x3A => labels!(ValueLabel::Dimensionless),
583 0x40..=0x4F => VifInfo {
584 labels: &[ValueLabel::Voltage],
585 units: &[unit!(Volt)],
586 scale: (first_vife_data & 0b1111) as isize - 9,
587 ..VifInfo::EMPTY
588 },
589 0x50..=0x5F => VifInfo {
590 labels: &[ValueLabel::Current],
591 units: &[unit!(Ampere)],
592 scale: (first_vife_data & 0b1111) as isize - 12,
593 ..VifInfo::EMPTY
594 },
595 0x60 => labels!(ValueLabel::ResetCounter),
596 0x61 => labels!(ValueLabel::CumulationCounter),
597 0x62 => labels!(ValueLabel::ControlSignal),
598 0x63 => labels!(ValueLabel::DayOfWeek),
599 0x64 => labels!(ValueLabel::WeekNumber),
600 0x65 => labels!(ValueLabel::TimePointOfChangeOfTariff),
601 0x66 => labels!(ValueLabel::StateOfParameterActivation),
602 0x67 => labels!(ValueLabel::SpecialSupplierInformation),
603 0x68 => VifInfo {
604 labels: &[ValueLabel::DurationSinceLastCumulation],
605 units: &[unit!(Hour)],
606 ..VifInfo::EMPTY
607 },
608 0x69 => VifInfo {
609 labels: &[ValueLabel::DurationSinceLastCumulation],
610 units: &[unit!(Day)],
611 ..VifInfo::EMPTY
612 },
613 0x6A => VifInfo {
614 labels: &[ValueLabel::DurationSinceLastCumulation],
615 units: &[unit!(Month)],
616 ..VifInfo::EMPTY
617 },
618 0x6B => VifInfo {
619 labels: &[ValueLabel::DurationSinceLastCumulation],
620 units: &[unit!(Year)],
621 ..VifInfo::EMPTY
622 },
623 0x6C => VifInfo {
624 labels: &[ValueLabel::OperatingTimeBattery],
625 units: &[unit!(Hour)],
626 ..VifInfo::EMPTY
627 },
628 0x6D => VifInfo {
629 labels: &[ValueLabel::OperatingTimeBattery],
630 units: &[unit!(Day)],
631 ..VifInfo::EMPTY
632 },
633 0x6E => VifInfo {
634 labels: &[ValueLabel::OperatingTimeBattery],
635 units: &[unit!(Month)],
636 ..VifInfo::EMPTY
637 },
638 0x6F => VifInfo {
639 labels: &[ValueLabel::OperatingTimeBattery],
640 units: &[unit!(Hour)],
641 ..VifInfo::EMPTY
642 },
643 0x70 => VifInfo {
644 labels: &[ValueLabel::DateAndTimeOfBatteryChange],
645 units: &[unit!(Second)],
646 ..VifInfo::EMPTY
647 },
648 0x71 => VifInfo {
649 labels: &[ValueLabel::RFPowerLevel],
650 units: &[unit!(DecibelMilliWatt)],
651 ..VifInfo::EMPTY
652 },
653 0x72 => labels!(ValueLabel::DaylightSavingBeginningEndingDeviation),
654 0x73 => labels!(ValueLabel::ListeningWindowManagementData),
655 0x74 => labels!(ValueLabel::RemainingBatteryLifeTime),
656 0x75 => labels!(ValueLabel::NumberOfTimesTheMeterWasStopped),
657 0x76 => VifInfo {
658 labels: &[ValueLabel::DataContainerForManufacturerSpecificProtocol],
659 ..VifInfo::EMPTY
660 },
661 0x7D => match second_vife_data.map(|s| s & 0x7F) {
662 Some(0x00) => labels!(ValueLabel::CurrentlySelectedApplication),
663 Some(0x02) => VifInfo {
664 labels: &[ValueLabel::RemainingBatteryLifeTime],
665 units: &[unit!(Month)],
666 ..VifInfo::EMPTY
667 },
668 Some(0x03) => VifInfo {
669 labels: &[ValueLabel::RemainingBatteryLifeTime],
670 units: &[unit!(Year)],
671 ..VifInfo::EMPTY
672 },
673 Some(0x3E) => VifInfo {
674 labels: &[ValueLabel::MoistureLevel],
675 units: &[unit!(Percent)],
676 ..VifInfo::EMPTY
677 },
678 _ => labels!(ValueLabel::Reserved),
679 },
680 _ => labels!(ValueLabel::Reserved),
681 }
682 }
683 ValueInformationCoding::AlternateVIFExtension => {
684 use UnitName::*;
685 use ValueLabel::*;
686 macro_rules! populate {
687 ($name:ident / h, $exp:expr, dec: $d:literal, $label:expr) => {
688 VifInfo {
689 units: &[
690 Unit {
691 name: $name,
692 exponent: $exp,
693 },
694 Unit {
695 name: Hour,
696 exponent: -1,
697 },
698 ],
699 labels: &[$label],
700 scale: $d,
701 offset: 0,
702 }
703 };
704 ($name:ident / min, $exp:expr, dec: $d:literal, $label:expr) => {
705 VifInfo {
706 units: &[
707 Unit {
708 name: $name,
709 exponent: $exp,
710 },
711 Unit {
712 name: Minute,
713 exponent: -1,
714 },
715 ],
716 labels: &[$label],
717 scale: $d,
718 offset: 0,
719 }
720 };
721 ($name:ident * h, $exp:expr, dec: $d:literal, $label:expr) => {
722 VifInfo {
723 units: &[
724 Unit {
725 name: $name,
726 exponent: $exp,
727 },
728 Unit {
729 name: Hour,
730 exponent: 1,
731 },
732 ],
733 labels: &[$label],
734 scale: $d,
735 offset: 0,
736 }
737 };
738 ($name:ident , $exp:expr, dec: $d:literal, $label:expr) => {
739 VifInfo {
740 units: &[Unit {
741 name: $name,
742 exponent: $exp,
743 }],
744 labels: &[$label],
745 scale: $d,
746 offset: 0,
747 }
748 };
749 }
750
751 let Some(first_vife_data) = first_vife else {
752 return Ok(VifInfo::EMPTY);
753 };
754 match first_vife_data & 0x7F {
755 0b0 => populate!(Watt / h, 3, dec: 5, Energy),
756 0b000_0001 => populate!(Watt / h, 3, dec: 6, Energy),
757 0b000_0010 => populate!(ReactiveWatt * h, 1, dec: 3, ReactiveEnergy),
758 0b000_0011 => populate!(ReactiveWatt * h, 1, dec: 4, ReactiveEnergy),
759 0b000_0100 => populate!(ApparentWatt * h, 1, dec: 3, ApparentEnergy),
760 0b000_0101 => populate!(ApparentWatt * h, 1, dec: 4, ApparentEnergy),
761 0b000_0110 => VifInfo {
762 labels: &[CoefficientOfPerformance],
763 scale: -1,
764 ..VifInfo::EMPTY
765 },
766 0b000_1000 => populate!(Joul, 1, dec: 8, Energy),
767 0b000_1001 => populate!(Joul, 1, dec: 9, Energy),
768 0b000_1100 => populate!(Calorie, 1, dec: 5, Energy),
769 0b000_1101 => populate!(Calorie, 1, dec: 6, Energy),
770 0b000_1110 => populate!(Calorie, 1, dec: 7, Energy),
771 0b000_1111 => populate!(Calorie, 1, dec: 8, Energy),
772 0b001_0000 => populate!(Meter, 3, dec: 2, Volume),
773 0b001_0001 => populate!(Meter, 3, dec: 3, Volume),
774 0b001_0100 => populate!(ReactiveWatt, 1, dec: 0, ReactivePower),
775 0b001_0101 => populate!(ReactiveWatt, 1, dec: 1, ReactivePower),
776 0b001_0110 => populate!(ReactiveWatt, 1, dec: 2, ReactivePower),
777 0b001_0111 => populate!(ReactiveWatt, 1, dec: 3, ReactivePower),
778 0b001_1000 => populate!(Tonne, 1, dec: 2, Mass),
779 0b001_1001 => populate!(Tonne, 1, dec: 3, Mass),
780 0b001_1010 => populate!(Percent, 1, dec: -1, RelativeHumidity),
781 0b001_1011 => populate!(Percent, 1, dec: 0, RelativeHumidity),
782 0b010_0000 => populate!(Feet, 3, dec: 0, Volume),
783 0b010_0001 => populate!(Feet, 3, dec: -1, Volume),
784 0b010_0011 => populate!(Degree, 1, dec: -1, PhaseItoU),
785 0b010_1000 => populate!(Watt, 1, dec: 5, Power),
786 0b010_1001 => populate!(Watt, 1, dec: 6, Power),
787 0b010_1010 => populate!(Degree, 1, dec: -1, PhaseUtoU),
788 0b010_1011 => populate!(Degree, 1, dec: -1, PhaseUtoI),
789 0b010_1100 => populate!(Hertz, 1, dec: -3, Frequency),
790 0b010_1101 => populate!(Hertz, 1, dec: -2, Frequency),
791 0b010_1110 => populate!(Hertz, 1, dec: -1, Frequency),
792 0b010_1111 => populate!(Hertz, 1, dec: 0, Frequency),
793 0b011_0000 => populate!(Joul / h, 1, dec: 8, Power),
794 0b011_0001 => populate!(Joul / h, 1, dec: 9, Power),
795 0b011_0100 => populate!(ApparentWatt, 1, dec: 0, ApparentPower),
796 0b011_0101 => populate!(ApparentWatt, 1, dec: 1, ApparentPower),
797 0b011_0110 => populate!(ApparentWatt, 1, dec: 2, ApparentPower),
798 0b011_0111 => populate!(ApparentWatt, 1, dec: 3, ApparentPower),
799 0b101_1000 => populate!(Fahrenheit, 1, dec: -3, FlowTemperature),
800 0b101_1001 => populate!(Fahrenheit, 1, dec: -2, FlowTemperature),
801 0b101_1010 => populate!(Fahrenheit, 1, dec: -1, FlowTemperature),
802 0b101_1011 => populate!(Fahrenheit, 1, dec: 0, FlowTemperature),
803 0b101_1100 => populate!(Fahrenheit, 1, dec: -3, ReturnTemperature),
804 0b101_1101 => populate!(Fahrenheit, 1, dec: -2, ReturnTemperature),
805 0b101_1110 => populate!(Fahrenheit, 1, dec: -1, ReturnTemperature),
806 0b101_1111 => populate!(Fahrenheit, 1, dec: 0, ReturnTemperature),
807 0b110_0000 => populate!(Fahrenheit, 1, dec: -3, TemperatureDifference),
808 0b110_0001 => populate!(Fahrenheit, 1, dec: -2, TemperatureDifference),
809 0b110_0010 => populate!(Fahrenheit, 1, dec: -1, TemperatureDifference),
810 0b110_0011 => populate!(Fahrenheit, 1, dec: 0, TemperatureDifference),
811 0b110_0100 => populate!(Fahrenheit, 1, dec: -3, ExternalTemperature),
812 0b110_0101 => populate!(Fahrenheit, 1, dec: -2, ExternalTemperature),
813 0b110_0110 => populate!(Fahrenheit, 1, dec: -1, ExternalTemperature),
814 0b110_0111 => populate!(Fahrenheit, 1, dec: 0, ExternalTemperature),
815 0b111_0000 => populate!(Fahrenheit, 1, dec: -3, ColdWarmTemperatureLimit),
816 0b111_0001 => populate!(Fahrenheit, 1, dec: -2, ColdWarmTemperatureLimit),
817 0b111_0010 => populate!(Fahrenheit, 1, dec: -1, ColdWarmTemperatureLimit),
818 0b111_0011 => populate!(Fahrenheit, 1, dec: 0, ColdWarmTemperatureLimit),
819 0b111_0100 => populate!(Celsius, 1, dec: -3, ColdWarmTemperatureLimit),
820 0b111_0101 => populate!(Celsius, 1, dec: -2, ColdWarmTemperatureLimit),
821 0b111_0110 => populate!(Celsius, 1, dec: -1, ColdWarmTemperatureLimit),
822 0b111_0111 => populate!(Celsius, 1, dec: 0, ColdWarmTemperatureLimit),
823 0b111_1000 => populate!(Watt, 1, dec: -3, CumulativeMaximumOfActivePower),
824 0b111_1001 => populate!(Watt, 1, dec: -2, CumulativeMaximumOfActivePower),
825 0b111_1010 => populate!(Watt, 1, dec: -1, CumulativeMaximumOfActivePower),
826 0b111_1011 => populate!(Watt, 1, dec: 0, CumulativeMaximumOfActivePower),
827 0b111_1100 => populate!(Watt, 1, dec: 1, CumulativeMaximumOfActivePower),
828 0b111_1101 => populate!(Watt, 1, dec: 2, CumulativeMaximumOfActivePower),
829 0b111_1110 => populate!(Watt, 1, dec: 3, CumulativeMaximumOfActivePower),
830 0b111_1111 => populate!(Watt, 1, dec: 4, CumulativeMaximumOfActivePower),
831 0b110_1000 => populate!(HCAUnit, 1,dec: 0, ResultingRatingFactor),
832 0b110_1001 => populate!(HCAUnit, 1,dec: 0, ThermalOutputRatingFactor),
833 0b110_1010 => {
834 populate!(HCAUnit, 1,dec: 0, ThermalCouplingRatingFactorOverall)
835 }
836 0b110_1011 => populate!(HCAUnit, 1,dec: 0, ThermalCouplingRatingRoomSide),
837 0b110_1100 => {
838 populate!(HCAUnit, 1,dec: 0, ThermalCouplingRatingFactorHeatingSide)
839 }
840 0b110_1101 => populate!(HCAUnit, 1,dec: 0, LowTemperatureRatingFactor),
841 0b110_1110 => populate!(HCAUnit, 1,dec: 0, DisplayOutputScalingFactor),
842
843 _ => labels!(ValueLabel::Reserved),
844 }
845 }
846 ValueInformationCoding::PlainText => labels!(ValueLabel::PlainText),
847 ValueInformationCoding::ManufacturerSpecific => labels!(ValueLabel::ManufacturerSpecific),
848 })
849}
850fn orthogonal_vife_info(data: u8, combinable_ext: bool) -> VifInfo {
851 if combinable_ext {
852 match data & 0x7F {
853 0x00 => labels!(ValueLabel::Reserved),
854 0x01 => labels!(ValueLabel::AtPhaseL1),
855 0x02 => labels!(ValueLabel::AtPhaseL2),
856 0x03 => labels!(ValueLabel::AtPhaseL3),
857 0x04 => labels!(ValueLabel::AtNeutral),
858 0x05 => labels!(ValueLabel::BetweenPhasesL1L2),
859 0x06 => labels!(ValueLabel::BetweenPhasesL2L3),
860 0x07 => labels!(ValueLabel::BetweenPhasesL3L1),
861 0x08 => labels!(ValueLabel::AtQuadrant1),
862 0x09 => labels!(ValueLabel::AtQuadrant2),
863 0x0A => labels!(ValueLabel::AtQuadrant3),
864 0x0B => labels!(ValueLabel::AtQuadrant4),
865 0x0C => labels!(ValueLabel::DeltaBetweenImportAndExport),
866 0x0D => labels!(ValueLabel::AlternativeNonMetricUnits),
867 0x0E => labels!(ValueLabel::SecondarySensorMeasurement),
868 0x0F => labels!(ValueLabel::HigherResolutionRegister),
869 0x10 => {
870 labels!(ValueLabel::AccumulationOfAbsoluteValueBothPositiveAndNegativeContribution)
871 }
872 0x11 => labels!(ValueLabel::DataPresentedWithTypeC),
873 0x12 => labels!(ValueLabel::DataPresentedWithTypeD),
874 0x13 => labels!(ValueLabel::EndDate),
875 0x14 => labels!(ValueLabel::DirectionFromCommunicationPartnerToMeter),
876 0x15 => labels!(ValueLabel::DirectionFromMeterToCommunicationPartner),
877 _ => labels!(ValueLabel::Reserved),
878 }
879 } else {
880 match data & 0x7F {
881 0x00..=0x0F => labels!(ValueLabel::ReservedForObjectActions),
882 0x10..=0x11 => labels!(ValueLabel::Reserved),
883 0x12 => labels!(ValueLabel::Averaged),
884 0x13 => labels!(ValueLabel::InverseCompactProfile),
885 0x14 => labels!(ValueLabel::RelativeDeviation),
886 0x15..=0x1C => labels!(ValueLabel::RecordErrorCodes),
887 0x1D => labels!(ValueLabel::StandardConformDataContent),
888 0x1E => labels!(ValueLabel::CompactProfileWithRegisterNumbers),
889 0x1F => labels!(ValueLabel::CompactProfile),
890 0x20 => units!(unit!(Second ^ -1)),
891 0x21 => units!(unit!(Minute ^ -1)),
892 0x22 => units!(unit!(Hour ^ -1)),
893 0x23 => units!(unit!(Day ^ -1)),
894 0x24 => units!(unit!(Week ^ -1)),
895 0x25 => units!(unit!(Month ^ -1)),
896 0x26 => units!(unit!(Year ^ -1)),
897 0x27 => units!(unit!(Revolution ^ -1)),
898 0x28 => VifInfo {
899 units: &[unit!(Increment), unit!(InputPulseOnChannel0 ^ -1)],
900 ..VifInfo::EMPTY
901 },
902 0x29 => VifInfo {
903 units: &[unit!(Increment), unit!(InputPulseOnChannel1 ^ -1)],
904 ..VifInfo::EMPTY
905 },
906 0x2A => VifInfo {
907 units: &[unit!(Increment), unit!(OutputPulseOnChannel0 ^ -1)],
908 ..VifInfo::EMPTY
909 },
910 0x2B => VifInfo {
911 units: &[unit!(Increment), unit!(OutputPulseOnChannel1 ^ -1)],
912 ..VifInfo::EMPTY
913 },
914 0x2C => units!(unit!(Liter)),
915 0x2D => units!(unit!(Meter ^ -3)),
916 0x2E => units!(unit!(Kilogram ^ -1)),
917 0x2F => units!(unit!(Kelvin ^ -1)),
918 0x30 => VifInfo {
919 units: &[unit!(Watt ^ -1), unit!(Hour ^ -1)],
920 scale: -(3),
921 ..VifInfo::EMPTY
922 },
923 0x31 => VifInfo {
924 units: &[unit!(Joul ^ -1)],
925 scale: -9,
926 ..VifInfo::EMPTY
927 },
928 0x32 => VifInfo {
929 units: &[unit!(Watt ^ -1)],
930 scale: -3,
931 ..VifInfo::EMPTY
932 },
933 0x33 => VifInfo {
934 units: &[unit!(Kelvin ^ -1), unit!(Liter ^ -1)],
935 ..VifInfo::EMPTY
936 },
937 0x34 => units!(unit!(Volt ^ -1)),
938 0x35 => units!(unit!(Ampere ^ -1)),
939 0x36 => units!(unit!(Second ^ 1)),
940 0x37 => VifInfo {
941 units: &[unit!(Second ^ 1), unit!(Volt ^ -1)],
942 ..VifInfo::EMPTY
943 },
944 0x38 => VifInfo {
945 units: &[unit!(Second ^ 1), unit!(Ampere ^ -1)],
946 ..VifInfo::EMPTY
947 },
948 0x39 => labels!(ValueLabel::StartDateOf),
949 0x3A => labels!(ValueLabel::VifContainsUncorrectedUnitOrValue),
950 0x3B => labels!(ValueLabel::AccumulationOnlyIfValueIsPositive),
951 0x3C => labels!(ValueLabel::AccumulationOnlyIfValueIsNegative),
952 0x3D => labels!(ValueLabel::NonMetricUnits),
953 0x3E => labels!(ValueLabel::ValueAtBaseConditions),
954 0x3F => labels!(ValueLabel::ObisDeclaration),
955 0x40 => labels!(ValueLabel::LowerLimitValue),
957 0x48 => labels!(ValueLabel::UpperLimitValue),
958 0x41 => labels!(ValueLabel::NumberOfExceedsOfLowerLimitValue),
960 0x49 => labels!(ValueLabel::NumberOfExceedsOfUpperLimitValue),
961 0x42 => labels!(ValueLabel::DateOfBeginFirstLowerLimitExceed),
967 0x43 => labels!(ValueLabel::DateOfEndFirstLowerLimitExceed),
968 0x46 => labels!(ValueLabel::DateOfBeginLastLowerLimitExceed),
969 0x47 => labels!(ValueLabel::DateOfEndLastLowerLimitExceed),
970 0x4A => labels!(ValueLabel::DateOfBeginFirstUpperLimitExceed),
971 0x4B => labels!(ValueLabel::DateOfEndFirstUpperLimitExceed),
972 0x4E => labels!(ValueLabel::DateOfBeginLastUpperLimitExceed),
973 0x4F => labels!(ValueLabel::DateOfEndLastUpperLimitExceed),
974 0x50 => VifInfo {
975 labels: &[ValueLabel::DurationOfFirstLowerLimitExceed],
976 units: &[unit!(Second)],
977 ..VifInfo::EMPTY
978 },
979 0x51 => VifInfo {
980 labels: &[ValueLabel::DurationOfFirstLowerLimitExceed],
981 units: &[unit!(Minute)],
982 ..VifInfo::EMPTY
983 },
984 0x52 => VifInfo {
985 labels: &[ValueLabel::DurationOfFirstLowerLimitExceed],
986 units: &[unit!(Hour)],
987 ..VifInfo::EMPTY
988 },
989 0x53 => VifInfo {
990 labels: &[ValueLabel::DurationOfFirstLowerLimitExceed],
991 units: &[unit!(Day)],
992 ..VifInfo::EMPTY
993 },
994 0x54 => VifInfo {
995 labels: &[ValueLabel::DurationOfLastLowerLimitExceed],
996 units: &[unit!(Second)],
997 ..VifInfo::EMPTY
998 },
999 0x55 => VifInfo {
1000 labels: &[ValueLabel::DurationOfLastLowerLimitExceed],
1001 units: &[unit!(Minute)],
1002 ..VifInfo::EMPTY
1003 },
1004 0x56 => VifInfo {
1005 labels: &[ValueLabel::DurationOfLastLowerLimitExceed],
1006 units: &[unit!(Hour)],
1007 ..VifInfo::EMPTY
1008 },
1009 0x57 => VifInfo {
1010 labels: &[ValueLabel::DurationOfLastLowerLimitExceed],
1011 units: &[unit!(Day)],
1012 ..VifInfo::EMPTY
1013 },
1014 0x58 => VifInfo {
1015 labels: &[ValueLabel::DurationOfFirstUpperLimitExceed],
1016 units: &[unit!(Second)],
1017 ..VifInfo::EMPTY
1018 },
1019 0x59 => VifInfo {
1020 labels: &[ValueLabel::DurationOfFirstUpperLimitExceed],
1021 units: &[unit!(Minute)],
1022 ..VifInfo::EMPTY
1023 },
1024 0x5A => VifInfo {
1025 labels: &[ValueLabel::DurationOfFirstUpperLimitExceed],
1026 units: &[unit!(Hour)],
1027 ..VifInfo::EMPTY
1028 },
1029 0x5B => VifInfo {
1030 labels: &[ValueLabel::DurationOfFirstUpperLimitExceed],
1031 units: &[unit!(Day)],
1032 ..VifInfo::EMPTY
1033 },
1034 0x5C => VifInfo {
1035 labels: &[ValueLabel::DurationOfLastUpperLimitExceed],
1036 units: &[unit!(Second)],
1037 ..VifInfo::EMPTY
1038 },
1039 0x5D => VifInfo {
1040 labels: &[ValueLabel::DurationOfLastUpperLimitExceed],
1041 units: &[unit!(Minute)],
1042 ..VifInfo::EMPTY
1043 },
1044 0x5E => VifInfo {
1045 labels: &[ValueLabel::DurationOfLastUpperLimitExceed],
1046 units: &[unit!(Hour)],
1047 ..VifInfo::EMPTY
1048 },
1049 0x5F => VifInfo {
1050 labels: &[ValueLabel::DurationOfLastUpperLimitExceed],
1051 units: &[unit!(Day)],
1052 ..VifInfo::EMPTY
1053 },
1054 0x60 => VifInfo {
1055 labels: &[ValueLabel::DurationOfFirst],
1056 units: &[unit!(Second)],
1057 ..VifInfo::EMPTY
1058 },
1059 0x61 => VifInfo {
1060 labels: &[ValueLabel::DurationOfFirst],
1061 units: &[unit!(Minute)],
1062 ..VifInfo::EMPTY
1063 },
1064 0x62 => VifInfo {
1065 labels: &[ValueLabel::DurationOfFirst],
1066 units: &[unit!(Hour)],
1067 ..VifInfo::EMPTY
1068 },
1069 0x63 => VifInfo {
1070 labels: &[ValueLabel::DurationOfFirst],
1071 units: &[unit!(Day)],
1072 ..VifInfo::EMPTY
1073 },
1074 0x64 => VifInfo {
1075 labels: &[ValueLabel::DurationOfLast],
1076 units: &[unit!(Second)],
1077 ..VifInfo::EMPTY
1078 },
1079 0x65 => VifInfo {
1080 labels: &[ValueLabel::DurationOfLast],
1081 units: &[unit!(Minute)],
1082 ..VifInfo::EMPTY
1083 },
1084 0x66 => VifInfo {
1085 labels: &[ValueLabel::DurationOfLast],
1086 units: &[unit!(Hour)],
1087 ..VifInfo::EMPTY
1088 },
1089 0x67 => VifInfo {
1090 labels: &[ValueLabel::DurationOfLast],
1091 units: &[unit!(Day)],
1092 ..VifInfo::EMPTY
1093 },
1094 0x68 => labels!(ValueLabel::ValueDuringLowerValueExceed),
1095 0x6C => labels!(ValueLabel::ValueDuringUpperValueExceed),
1096 0x69 => labels!(ValueLabel::LeakageValues),
1097 0x6D => labels!(ValueLabel::OverflowValues),
1098 0x6A => labels!(ValueLabel::DateOfBeginFirst),
1099 0x6B => labels!(ValueLabel::DateOfBeginLast),
1100 0x6E => labels!(ValueLabel::DateOfEndLast),
1101 0x6F => labels!(ValueLabel::DateOfEndFirst),
1102 0x70..=0x77 => VifInfo {
1103 scale: (data & 0b111) as isize - 6,
1104 ..VifInfo::EMPTY
1105 },
1106 0x78..=0x7B => VifInfo {
1107 offset: (data & 0b11) as isize - 3,
1108 ..VifInfo::EMPTY
1109 },
1110 0x7D => VifInfo {
1111 scale: 3,
1112 ..VifInfo::EMPTY
1113 },
1114 0x7E => labels!(ValueLabel::FutureValue),
1115 0x7F => labels!(ValueLabel::NextVIFEAndDataOfThisBlockAreManufacturerSpecific),
1116 _ => labels!(ValueLabel::Reserved),
1117 }
1118 }
1119}
1120
1121#[derive(Debug, Clone, Copy, PartialEq)]
1122#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1123#[non_exhaustive]
1124pub enum ValueInformationError {
1125 InvalidValueInformation,
1126 DataTooShort,
1127}
1128
1129impl From<u8> for ValueInformationField {
1130 fn from(data: u8) -> Self {
1131 Self { data }
1132 }
1133}
1134fn orthogonal_chain(
1141 coding: ValueInformationCoding,
1142 ext: Option<ValueInformationFieldExtensions<'_>>,
1143) -> ValueInformationFieldExtensions<'_> {
1144 let mut chain = ext.unwrap_or(ValueInformationFieldExtensions(&[]));
1145 match coding {
1146 ValueInformationCoding::MainVIFExtension
1147 | ValueInformationCoding::AlternateVIFExtension => {
1148 chain.next();
1149 }
1150 ValueInformationCoding::ManufacturerSpecific => return ValueInformationFieldExtensions(&[]),
1151 _ => {}
1152 }
1153 chain
1154}
1155
1156#[derive(Clone)]
1157struct OrthogonalVifes<'a> {
1158 vife: ValueInformationFieldExtensions<'a>,
1159 combinable_ext: bool,
1160}
1161impl Iterator for OrthogonalVifes<'_> {
1162 type Item = VifInfo;
1163 fn next(&mut self) -> Option<Self::Item> {
1164 loop {
1165 let v = self.vife.next()?;
1166 if v.data == 0xFC {
1169 self.combinable_ext = true;
1170 continue;
1171 }
1172 let ext = core::mem::replace(&mut self.combinable_ext, false);
1173 if !ext && v.data & 0x7F == 0x7F {
1174 self.vife = ValueInformationFieldExtensions(&[]);
1177 }
1178 return Some(orthogonal_vife_info(v.data, ext));
1179 }
1180 }
1181}
1182
1183#[inline(never)]
1190fn non_metric_units(
1191 coding: ValueInformationCoding,
1192 vif: u8,
1193 first_vife: Option<u8>,
1194 orthogonal: &ValueInformationFieldExtensions<'_>,
1195) -> Option<(&'static [Unit], isize)> {
1196 let units: Option<(&'static [Unit], isize)> = match coding {
1197 ValueInformationCoding::Primary => match vif & 0x7F {
1198 0x00..=0x07 => Some((&[unit!(BritishThermalUnit)], 3)),
1200 0x10..=0x17 => Some((&[unit!(AmericanGallon)], 3)),
1202 0x28..=0x2F => Some((&[unit!(BritishThermalUnit), unit!(Second ^ -1)], -3)),
1204 0x40..=0x47 => Some((&[unit!(AmericanGallon), unit!(Minute ^ -1)], 3)),
1206 0x58..=0x67 => Some((&[unit!(Fahrenheit)], 0)),
1208 _ => None,
1209 },
1210 ValueInformationCoding::AlternateVIFExtension => match first_vife? & 0x7F {
1212 0x74..=0x77 => Some((&[unit!(Fahrenheit)], 0)),
1213 _ => None,
1214 },
1215 _ => None,
1216 };
1217 let units = units?;
1218 let mut combinable_ext = false;
1221 for &byte in orthogonal.0 {
1222 if byte == 0xFC {
1223 combinable_ext = true;
1224 continue;
1225 }
1226 if !core::mem::replace(&mut combinable_ext, false) {
1227 match byte & 0x7F {
1228 0x3D => return Some(units),
1229 0x7F => return None,
1230 _ => {}
1231 }
1232 }
1233 }
1234 None
1235}
1236
1237#[derive(Clone)]
1242pub struct ValueInformation<'a> {
1243 head_labels: &'static [ValueLabel],
1244 head_units: &'static [Unit],
1245 orthogonal: ValueInformationFieldExtensions<'a>,
1246 pub decimal_scale_exponent: isize,
1247 pub decimal_offset_exponent: isize,
1248}
1249impl<'a> ValueInformation<'a> {
1250 #[must_use]
1252 pub fn labels(&self) -> ValueLabels<'a> {
1253 ValueLabels {
1254 current: self.head_labels,
1255 rest: OrthogonalVifes {
1256 vife: self.orthogonal.clone(),
1257 combinable_ext: false,
1258 },
1259 }
1260 }
1261 #[must_use]
1263 pub fn units(&self) -> Units<'a> {
1264 Units {
1265 current: self.head_units,
1266 rest: OrthogonalVifes {
1267 vife: self.orthogonal.clone(),
1268 combinable_ext: false,
1269 },
1270 }
1271 }
1272 #[must_use]
1273 pub fn has_label(&self, label: ValueLabel) -> bool {
1274 self.labels().any(|item| item == label)
1275 }
1276 #[must_use]
1277 pub fn first_unit(&self) -> Option<Unit> {
1278 self.units().next()
1279 }
1280}
1281impl<'a> TryFrom<&ValueInformationBlock<'a>> for ValueInformation<'a> {
1282 type Error = DataInformationError;
1283 fn try_from(block: &ValueInformationBlock<'a>) -> Result<Self, Self::Error> {
1284 let coding = ValueInformationCoding::from(&block.value_information);
1285 let ext = block.value_information_extension.clone();
1286 let bytes = ext.as_ref().map_or(&[][..], |ext| ext.0);
1289 let first = bytes.first().copied();
1290 let second = bytes.get(1).copied();
1291 if matches!(
1294 coding,
1295 ValueInformationCoding::MainVIFExtension
1296 | ValueInformationCoding::AlternateVIFExtension
1297 ) && ext.is_some()
1298 && first.is_none()
1299 {
1300 return Err(DataInformationError::DataTooShort);
1301 }
1302 let head = head_vif_info(block.value_information.clone(), first, second)?;
1303 let orthogonal = OrthogonalVifes {
1304 vife: orthogonal_chain(coding, ext),
1305 combinable_ext: false,
1306 };
1307 if orthogonal.vife.0.is_empty() {
1308 return Ok(Self {
1309 head_labels: head.labels,
1310 head_units: head.units,
1311 orthogonal: orthogonal.vife,
1312 decimal_scale_exponent: head.scale,
1313 decimal_offset_exponent: head.offset,
1314 });
1315 }
1316 let (scale, offset) = orthogonal
1317 .clone()
1318 .fold((head.scale, head.offset), |(s, o), v| {
1319 (s + v.scale, o + v.offset)
1320 });
1321 let (head_units, scale) = match non_metric_units(
1322 coding,
1323 block.value_information.data,
1324 first,
1325 &orthogonal.vife,
1326 ) {
1327 Some((units, delta)) => (units, scale + delta),
1328 None => (head.units, scale),
1329 };
1330 Ok(Self {
1331 head_labels: head.labels,
1332 head_units,
1333 orthogonal: orthogonal.vife,
1334 decimal_scale_exponent: scale,
1335 decimal_offset_exponent: offset,
1336 })
1337 }
1338}
1339impl PartialEq for ValueInformation<'_> {
1340 fn eq(&self, other: &Self) -> bool {
1341 self.decimal_scale_exponent == other.decimal_scale_exponent
1342 && self.decimal_offset_exponent == other.decimal_offset_exponent
1343 && self.labels().eq(other.labels())
1344 && self.units().eq(other.units())
1345 }
1346}
1347impl core::fmt::Debug for ValueInformation<'_> {
1348 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1349 f.debug_struct("ValueInformation")
1350 .field("decimal_offset_exponent", &self.decimal_offset_exponent)
1351 .field("labels", &self.labels())
1352 .field("decimal_scale_exponent", &self.decimal_scale_exponent)
1353 .field("units", &self.units())
1354 .finish()
1355 }
1356}
1357#[cfg(feature = "serde")]
1358impl serde::Serialize for ValueInformation<'_> {
1359 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1360 use serde::ser::SerializeStruct;
1361 let mut state = serializer.serialize_struct("ValueInformation", 4)?;
1362 state.serialize_field("decimal_offset_exponent", &self.decimal_offset_exponent)?;
1363 state.serialize_field("labels", &self.labels())?;
1364 state.serialize_field("decimal_scale_exponent", &self.decimal_scale_exponent)?;
1365 state.serialize_field("units", &self.units())?;
1366 state.end()
1367 }
1368}
1369
1370#[cfg(feature = "serde")]
1373fn serialize_counted_sequence<I, S>(items: I, serializer: S) -> Result<S::Ok, S::Error>
1374where
1375 I: Iterator + Clone,
1376 I::Item: serde::Serialize,
1377 S: serde::Serializer,
1378{
1379 use serde::ser::SerializeSeq;
1380 let mut sequence = serializer.serialize_seq(Some(items.clone().count()))?;
1381 for item in items {
1382 sequence.serialize_element(&item)?;
1383 }
1384 sequence.end()
1385}
1386
1387#[derive(Clone)]
1389pub struct ValueLabels<'a> {
1390 current: &'static [ValueLabel],
1391 rest: OrthogonalVifes<'a>,
1392}
1393impl Iterator for ValueLabels<'_> {
1394 type Item = ValueLabel;
1395 fn next(&mut self) -> Option<Self::Item> {
1396 loop {
1397 if let Some((first, tail)) = self.current.split_first() {
1398 self.current = tail;
1399 return Some(*first);
1400 }
1401 self.current = self.rest.next()?.labels;
1402 }
1403 }
1404}
1405impl core::fmt::Debug for ValueLabels<'_> {
1406 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1407 f.debug_list().entries(self.clone()).finish()
1408 }
1409}
1410#[cfg(feature = "serde")]
1411impl serde::Serialize for ValueLabels<'_> {
1412 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1413 serialize_counted_sequence(self.clone(), serializer)
1414 }
1415}
1416
1417#[derive(Clone)]
1419pub struct Units<'a> {
1420 current: &'static [Unit],
1421 rest: OrthogonalVifes<'a>,
1422}
1423impl Iterator for Units<'_> {
1424 type Item = Unit;
1425 fn next(&mut self) -> Option<Self::Item> {
1426 loop {
1427 if let Some((first, tail)) = self.current.split_first() {
1428 self.current = tail;
1429 return Some(*first);
1430 }
1431 self.current = self.rest.next()?.units;
1432 }
1433 }
1434}
1435impl core::fmt::Debug for Units<'_> {
1436 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1437 f.debug_list().entries(self.clone()).finish()
1438 }
1439}
1440#[cfg(feature = "serde")]
1441impl serde::Serialize for Units<'_> {
1442 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1443 serialize_counted_sequence(self.clone(), serializer)
1444 }
1445}
1446#[cfg(feature = "defmt")]
1447impl defmt::Format for ValueInformation<'_> {
1448 fn format(&self, f: defmt::Formatter) {
1449 defmt::write!(
1450 f,
1451 "ValueInformation{{ decimal_offset_exponent: {}, decimal_scale_exponent: {}",
1452 self.decimal_offset_exponent,
1453 self.decimal_scale_exponent
1454 );
1455 let mut labels = self.labels().peekable();
1456 if labels.peek().is_some() {
1457 defmt::write!(f, ", labels: [");
1458 for (i, label) in labels.enumerate() {
1459 if i != 0 {
1460 defmt::write!(f, ", ");
1461 }
1462 defmt::write!(f, "{:?}", label);
1463 }
1464 defmt::write!(f, "]");
1465 }
1466 let mut units = self.units().peekable();
1467 if units.peek().is_some() {
1468 defmt::write!(f, ", units: [");
1469 for (i, unit) in units.enumerate() {
1470 if i != 0 {
1471 defmt::write!(f, ", ");
1472 }
1473 defmt::write!(f, "{:?}", unit);
1474 }
1475 defmt::write!(f, "]");
1476 }
1477 defmt::write!(f, " }}");
1478 }
1479}
1480
1481#[cfg(feature = "std")]
1482impl fmt::Display for ValueInformation<'_> {
1483 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1484 if self.decimal_offset_exponent != 0 {
1485 write!(f, "+{})", self.decimal_offset_exponent)?;
1486 } else {
1487 write!(f, ")")?;
1488 }
1489 if self.decimal_scale_exponent != 0 {
1490 write!(f, "e{}", self.decimal_scale_exponent)?;
1491 }
1492 let mut units = self.units().peekable();
1493 if units.peek().is_some() {
1494 write!(f, "[")?;
1495 for unit in units {
1496 write!(f, "{}", unit)?;
1497 }
1498 write!(f, "]")?;
1499 }
1500 let mut labels = self.labels().peekable();
1501 if labels.peek().is_some() {
1502 write!(f, "(")?;
1503 for (i, label) in labels.enumerate() {
1504 if i != 0 {
1505 write!(f, ", ")?;
1506 }
1507 write!(f, "{:?}", label)?;
1508 }
1509
1510 return write!(f, ")");
1511 }
1512 Ok(())
1513 }
1514}
1515#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1516#[derive(Debug, Clone, Copy, PartialEq)]
1517#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1518#[non_exhaustive]
1519pub enum ValueLabel {
1520 Instantaneous,
1521 ReservedForObjectActions,
1522 Reserved,
1523 Averaged,
1524 Integral,
1525 Parameter,
1526 InverseCompactProfile,
1527 RelativeDeviation,
1528 RecordErrorCodes,
1529 StandardConformDataContent,
1530 CompactProfileWithRegisterNumbers,
1531 CompactProfile,
1532 ActualityDuration,
1533 AveragingDuration,
1534 Date,
1535 Time,
1536 DateTime,
1537 DateTimeWithSeconds,
1538 FabricationNumber,
1539 EnhancedIdentification,
1540 Address,
1541 PlainText,
1542 RevolutionOrMeasurement,
1543 IncrementPerInputPulseOnChannelP,
1544 IncrementPerOutputPulseOnChannelP,
1545 HourMinuteSecond,
1546 DayMonthYear,
1547 StartDateOf,
1548 VifContainsUncorrectedUnitOrValue,
1549 AccumulationOnlyIfValueIsPositive,
1550 AccumulationOnlyIfValueIsNegative,
1551 NonMetricUnits,
1552 AlternativeNonMetricUnits,
1553 ValueAtBaseConditions,
1554 ObisDeclaration,
1555 UpperLimitValue,
1556 LowerLimitValue,
1557 NumberOfExceedsOfUpperLimitValue,
1558 NumberOfExceedsOfLowerLimitValue,
1559 DateOfBeginFirstLowerLimitExceed,
1560 DateOfBeginFirstUpperLimitExceed,
1561 DateOfBeginLastLowerLimitExceed,
1562 DateOfBeginLastUpperLimitExceed,
1563 DateOfEndLastLowerLimitExceed,
1564 DateOfEndLastUpperLimitExceed,
1565 DateOfEndFirstLowerLimitExceed,
1566 DateOfEndFirstUpperLimitExceed,
1567 DurationOfFirstLowerLimitExceed,
1568 DurationOfFirstUpperLimitExceed,
1569 DurationOfLastLowerLimitExceed,
1570 DurationOfLastUpperLimitExceed,
1571 DurationOfFirst,
1572 DurationOfLast,
1573 ValueDuringLowerValueExceed,
1574 ValueDuringUpperValueExceed,
1575 LeakageValues,
1576 OverflowValues,
1577 DateOfBeginLast,
1578 DateOfBeginFirst,
1579 DateOfEndLast,
1580 DateOfEndFirst,
1581 ExtensionOfCombinableOrthogonalVIFE,
1582 FutureValue,
1583 NextVIFEAndDataOfThisBlockAreManufacturerSpecific,
1584 Credit,
1585 Debit,
1586 UniqueMessageIdentificationOrAccessNumber,
1587 DeviceType,
1588 Manufacturer,
1589 ParameterSetIdentification,
1590 ModelOrVersion,
1591 HardwareVersion,
1592 MetrologyFirmwareVersion,
1593 OtherSoftwareVersion,
1594 CustomerLocation,
1595 Customer,
1596 AccessCodeUser,
1597 AccessCodeOperator,
1598 AccessCodeSystemOperator,
1599 AccessCodeDeveloper,
1600 Password,
1601 ErrorFlags,
1602 ErrorMask,
1603 SecurityKey,
1604 DigitalInput,
1605 DigitalOutput,
1606 Binary,
1607 BaudRate,
1608 ResponseDelayTime,
1609 Retry,
1610 RemoteControl,
1611 FirstStorageForCycleStorage,
1612 LastStorageForCycleStorage,
1613 SizeOfStorageBlock,
1614 DescriptionOfTariffAndSubunit,
1615 StorageInterval,
1616 Dimensionless,
1617 DimensionlessHCA,
1618 DataContainerForWmbusProtocol,
1619 PeriodOfNormalDataTransmission,
1620 ResetCounter,
1621 CumulationCounter,
1622 ControlSignal,
1623 DayOfWeek,
1624 WeekNumber,
1625 TimePointOfChangeOfTariff,
1626 StateOfParameterActivation,
1627 SpecialSupplierInformation,
1628 DurationSinceLastCumulation,
1629 OperatingTimeBattery,
1630 DateAndTimeOfBatteryChange,
1631 RFPowerLevel,
1632 DaylightSavingBeginningEndingDeviation,
1633 ListeningWindowManagementData,
1634 RemainingBatteryLifeTime,
1635 NumberOfTimesTheMeterWasStopped,
1636 DataContainerForManufacturerSpecificProtocol,
1637 CurrentlySelectedApplication,
1638 Energy,
1639 ReactiveEnergy,
1640 ApparentEnergy,
1641 CoefficientOfPerformance,
1642 ReactivePower,
1643 Frequency,
1644 ApparentPower,
1645 AtPhaseL1,
1646 AtPhaseL2,
1647 AtPhaseL3,
1648 AtNeutral,
1649 BetweenPhasesL1L2,
1650 BetweenPhasesL2L3,
1651 BetweenPhasesL3L1,
1652 AtQuadrant1,
1653 AtQuadrant2,
1654 AtQuadrant3,
1655 AtQuadrant4,
1656 DeltaBetweenImportAndExport,
1657 AccumulationOfAbsoluteValueBothPositiveAndNegativeContribution,
1658 SecondarySensorMeasurement,
1659 HigherResolutionRegister,
1660 DataPresentedWithTypeC,
1661 DataPresentedWithTypeD,
1662 EndDate,
1663 DirectionFromCommunicationPartnerToMeter,
1664 DirectionFromMeterToCommunicationPartner,
1665 RelativeHumidity,
1666 MoistureLevel,
1667 PhaseUtoU,
1668 PhaseUtoI,
1669 PhaseItoU,
1670 ColdWarmTemperatureLimit,
1671 CumulativeMaximumOfActivePower,
1672 ResultingRatingFactor,
1673 ThermalOutputRatingFactor,
1674 ThermalCouplingRatingFactorOverall,
1675 ThermalCouplingRatingRoomSide,
1676 ThermalCouplingRatingFactorHeatingSide,
1677 LowTemperatureRatingFactor,
1678 DisplayOutputScalingFactor,
1679 ManufacturerSpecific,
1680 OnTime,
1681 OperatingTime,
1682 Volume,
1683 Mass,
1684 Power,
1685 VolumeFlow,
1686 MassFlow,
1687 Pressure,
1688 Voltage,
1689 Current,
1690 FlowTemperature,
1691 ReturnTemperature,
1692 TemperatureDifference,
1693 ExternalTemperature,
1694}
1695
1696#[cfg(feature = "std")]
1697impl fmt::Display for Unit {
1698 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1699 let superscripts = ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
1700 let invalid_superscript = '⁻';
1701 match self.exponent {
1702 1 => write!(f, "{}", self.name),
1703 0..=9 => write!(
1704 f,
1705 "{}{}",
1706 self.name,
1707 superscripts
1708 .get(self.exponent as usize)
1709 .unwrap_or(&invalid_superscript)
1710 ),
1711 10..=19 => write!(
1712 f,
1713 "{}{}{}",
1714 self.name,
1715 superscripts.get(1).unwrap_or(&invalid_superscript),
1716 superscripts
1717 .get(self.exponent as usize - 10)
1718 .unwrap_or(&invalid_superscript)
1719 ),
1720 x if (-9..0).contains(&x) => {
1721 write!(
1722 f,
1723 "{}⁻{}",
1724 self.name,
1725 superscripts
1726 .get((-x) as usize)
1727 .unwrap_or(&invalid_superscript)
1728 )
1729 }
1730 x if (-19..0).contains(&x) => write!(
1731 f,
1732 "{}⁻{}{}",
1733 self.name,
1734 superscripts.get(1).unwrap_or(&invalid_superscript),
1735 superscripts
1736 .get((-x) as usize - 10)
1737 .unwrap_or(&invalid_superscript)
1738 ),
1739 x => write!(f, "{}^{}", self.name, x),
1740 }
1741 }
1742}
1743#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1744#[derive(Debug, Clone, Copy, PartialEq)]
1745#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1746#[non_exhaustive]
1747pub enum UnitName {
1748 Watt,
1749 ReactiveWatt,
1750 ApparentWatt,
1751 Joul,
1752 Kilogram,
1753 Tonne,
1754 Meter,
1755 Feet,
1756 Celsius,
1757 Kelvin,
1758 Bar,
1759 HCA,
1760 Reserved,
1761 WithoutUnits,
1762 Second,
1763 Minute,
1764 Hour,
1765 Day,
1766 Week,
1767 Month,
1768 Year,
1769 Revolution,
1770 Increment,
1771 InputPulseOnChannel0,
1772 OutputPulseOnChannel0,
1773 InputPulseOnChannel1,
1774 OutputPulseOnChannel1,
1775 Liter,
1776 Volt,
1777 Ampere,
1778 LocalMoneyCurrency,
1779 Symbol,
1780 BitTime,
1781 DecibelMilliWatt,
1782 Percent,
1783 Degree,
1784 Hertz,
1785 HCAUnit,
1786 Fahrenheit,
1787 AmericanGallon,
1788 Calorie,
1789 BritishThermalUnit,
1790}
1791
1792#[cfg(feature = "std")]
1793impl fmt::Display for UnitName {
1794 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1795 match self {
1796 UnitName::Watt => write!(f, "W"),
1797 UnitName::ReactiveWatt => write!(f, "W (reactive)"),
1798 UnitName::ApparentWatt => write!(f, "W (apparent)"),
1799 UnitName::Joul => write!(f, "J"),
1800 UnitName::Kilogram => write!(f, "Kg"),
1801 UnitName::Tonne => write!(f, "t"),
1802 UnitName::Meter => write!(f, "m"),
1803 UnitName::Feet => write!(f, "ft"),
1804 UnitName::Celsius => write!(f, "°C"),
1805 UnitName::Kelvin => write!(f, "°K"),
1806 UnitName::Bar => write!(f, "Bar"),
1807 UnitName::HCA => write!(f, "HCA"),
1808 UnitName::Reserved => write!(f, "Reserved"),
1809 UnitName::WithoutUnits => write!(f, "-"),
1810 UnitName::Second => write!(f, "s"),
1811 UnitName::Minute => write!(f, "min"),
1812 UnitName::Hour => write!(f, "h"),
1813 UnitName::Day => write!(f, "day"),
1814 UnitName::Week => write!(f, "week"),
1815 UnitName::Month => write!(f, "month"),
1816 UnitName::Year => write!(f, "year"),
1817 UnitName::Revolution => write!(f, "revolution"),
1818 UnitName::Increment => write!(f, "increment"),
1819 UnitName::InputPulseOnChannel0 => write!(f, "InputPulseOnChannel0"),
1820 UnitName::OutputPulseOnChannel0 => write!(f, "OutputPulseOnChannel0"),
1821 UnitName::InputPulseOnChannel1 => write!(f, "InputPulseOnChannel1"),
1822 UnitName::OutputPulseOnChannel1 => write!(f, "OutputPulseOnChannel1"),
1823 UnitName::Liter => write!(f, "l"),
1824 UnitName::Volt => write!(f, "V"),
1825 UnitName::Ampere => write!(f, "A"),
1826 UnitName::LocalMoneyCurrency => write!(f, "$ (local)"),
1827 UnitName::Symbol => write!(f, "Symbol"),
1828 UnitName::BitTime => write!(f, "BitTime"),
1829 UnitName::DecibelMilliWatt => write!(f, "dBmW"),
1830 UnitName::Percent => write!(f, "%"),
1831 UnitName::Degree => write!(f, "°"),
1832 UnitName::Hertz => write!(f, "Hz"),
1833 UnitName::HCAUnit => write!(f, "HCAUnit"),
1834 UnitName::Fahrenheit => write!(f, "°F"),
1835 UnitName::AmericanGallon => write!(f, "UsGal"),
1836 UnitName::BritishThermalUnit => write!(f, "BTU"),
1837 UnitName::Calorie => write!(f, "cal"),
1838 }
1839 }
1840}
1841
1842#[cfg(test)]
1843mod tests {
1844 extern crate std;
1845 use std::{vec, vec::Vec};
1846
1847 fn assert_information(
1848 actual: super::ValueInformation<'_>,
1849 offset: isize,
1850 scale: isize,
1851 labels: &[super::ValueLabel],
1852 units: &[super::Unit],
1853 ) {
1854 assert_eq!(actual.decimal_offset_exponent, offset);
1855 assert_eq!(actual.decimal_scale_exponent, scale);
1856 assert!(actual.labels().eq(labels.iter().copied()));
1857 assert!(actual.units().eq(units.iter().copied()));
1858 }
1859
1860 #[test]
1861 fn value_information_peeks_remaining_extension_bytes_without_consuming_them() {
1862 use super::{
1863 ValueInformation, ValueInformationBlock, ValueInformationFieldExtensions, ValueLabel,
1864 };
1865 let bytes = [0x80, 0xfd, 0x3e];
1866 let mut extensions = ValueInformationFieldExtensions::new(&bytes).unwrap();
1867 assert_eq!(extensions.next().unwrap().data, 0x80);
1868 let block = ValueInformationBlock::new(0xfd.into(), Some(extensions), None);
1869 let info = ValueInformation::try_from(&block).unwrap();
1870 assert!(info.has_label(ValueLabel::MoistureLevel));
1871 assert_eq!(
1872 info,
1873 ValueInformation::try_from(
1874 &ValueInformationBlock::try_from([0xfd, 0xfd, 0x3e].as_slice()).unwrap()
1875 )
1876 .unwrap()
1877 );
1878 assert_eq!(block.value_information_extension.as_ref().unwrap().len(), 2);
1879 assert_eq!(ValueInformation::try_from(&block).unwrap(), info);
1880 }
1881
1882 #[test]
1883 fn test_single_byte_primary_value_information_parsing() {
1884 use crate::value_information::UnitName;
1885 use crate::value_information::{
1886 Unit, ValueInformation, ValueInformationBlock, ValueInformationField, ValueLabel,
1887 };
1888
1889 let data = [0x13];
1891 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1892 assert_eq!(
1893 result,
1894 ValueInformationBlock {
1895 value_information: ValueInformationField::from(0x13),
1896 value_information_extension: None,
1897 plaintext_vife: None
1898 }
1899 );
1900 assert_eq!(result.get_size(), 1);
1901 assert_information(
1902 ValueInformation::try_from(&result).unwrap(),
1903 0,
1904 -3,
1905 &[ValueLabel::Volume],
1906 &[unit!(Meter ^ 3)],
1907 );
1908
1909 let data = [0x14];
1911 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1912 assert_eq!(
1913 result,
1914 ValueInformationBlock {
1915 value_information: ValueInformationField::from(0x14),
1916 value_information_extension: None,
1917 plaintext_vife: None
1918 }
1919 );
1920 assert_eq!(result.get_size(), 1);
1921 assert_information(
1922 ValueInformation::try_from(&result).unwrap(),
1923 0,
1924 -2,
1925 &[ValueLabel::Volume],
1926 &[unit!(Meter ^ 3)],
1927 );
1928
1929 let data = [0x15];
1931 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1932 assert_eq!(
1933 result,
1934 ValueInformationBlock {
1935 value_information: ValueInformationField::from(0x15),
1936 value_information_extension: None,
1937 plaintext_vife: None
1938 }
1939 );
1940 assert_eq!(result.get_size(), 1);
1941 assert_information(
1942 ValueInformation::try_from(&result).unwrap(),
1943 0,
1944 -1,
1945 &[ValueLabel::Volume],
1946 &[unit!(Meter ^ 3)],
1947 );
1948
1949 let data = [0x16];
1951 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1952 assert_eq!(
1953 result,
1954 ValueInformationBlock {
1955 value_information: ValueInformationField::from(0x16),
1956 value_information_extension: None,
1957 plaintext_vife: None
1958 },
1959 );
1960 assert_eq!(result.get_size(), 1);
1961 }
1962
1963 #[test]
1964 fn test_multibyte_primary_value_information() {
1965 use crate::value_information::UnitName;
1966 use crate::value_information::{
1967 Unit, ValueInformation, ValueInformationBlock, ValueInformationField, ValueLabel,
1968 };
1969
1970 let data = [0x96, 0x12];
1976 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1977 assert_eq!(result.get_size(), 2);
1978 assert_eq!(result.value_information, ValueInformationField::from(0x96));
1979 assert!(ValueInformation::try_from(&result)
1980 .unwrap()
1981 .labels()
1982 .eq([ValueLabel::Volume, ValueLabel::Averaged]));
1983
1984 let data = [0x96, 0x92, 0x20];
1990 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1991 assert_eq!(result.get_size(), 3);
1992 assert_eq!(result.value_information, ValueInformationField::from(0x96));
1993 assert_information(
1994 ValueInformation::try_from(&result).unwrap(),
1995 0,
1996 0,
1997 &[ValueLabel::Volume, ValueLabel::Averaged],
1998 &[unit!(Meter ^ 3), unit!(Second ^ -1)],
1999 );
2000
2001 let data = [0x96, 0x92, 0xA0, 0x2D];
2008 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
2009 assert_eq!(result.get_size(), 4);
2010 assert_eq!(result.value_information, ValueInformationField::from(0x96));
2011 assert_information(
2012 ValueInformation::try_from(&result).unwrap(),
2013 0,
2014 0,
2015 &[ValueLabel::Volume, ValueLabel::Averaged],
2016 &[unit!(Meter ^ 3), unit!(Second ^ -1), unit!(Meter ^ -3)],
2017 );
2018 }
2019
2020 #[cfg(not(feature = "plaintext-before-extension"))]
2021 #[test]
2022 fn test_plain_text_vif_norm_conform() {
2023 use crate::value_information::{ValueInformation, ValueLabel};
2024
2025 use crate::value_information::ValueInformationBlock;
2026 let data = [0xFC, 0x74, 0x03, 0x48, 0x52, 0x25];
2037 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
2038 assert_eq!(result.get_size(), 6);
2039 assert_eq!(result.value_information.data, 0xFC);
2040 assert_information(
2041 ValueInformation::try_from(&result).unwrap(),
2042 0,
2043 -2,
2044 &[ValueLabel::PlainText],
2045 &[],
2046 );
2047
2048 }
2058
2059 #[test]
2060 fn test_short_vif_with_vife() {
2061 use crate::value_information::ValueInformationBlock;
2062 let data = [253, 27];
2063 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
2064 assert_eq!(result.get_size(), 2);
2065 }
2066
2067 #[test]
2068 fn test_vif_fd_voltage_and_ampere() {
2069 use crate::value_information::UnitName;
2070 use crate::value_information::{ValueInformation, ValueInformationBlock};
2071
2072 let vi = ValueInformation::try_from(
2074 &ValueInformationBlock::try_from([0xFD, 0x48].as_slice()).unwrap(),
2075 )
2076 .unwrap();
2077 assert_eq!(vi.first_unit().unwrap().name, UnitName::Volt);
2078 assert_eq!(vi.decimal_scale_exponent, -1);
2079
2080 let vi = ValueInformation::try_from(
2082 &ValueInformationBlock::try_from([0xFD, 0x59].as_slice()).unwrap(),
2083 )
2084 .unwrap();
2085 assert_eq!(vi.first_unit().unwrap().name, UnitName::Ampere);
2086 assert_eq!(vi.decimal_scale_exponent, -3);
2087 }
2088
2089 #[test]
2090 fn test_vif_fb_added_codes_and_reserved_fallback() {
2091 use crate::value_information::UnitName;
2092 use crate::value_information::{ValueInformation, ValueInformationBlock, ValueLabel};
2093
2094 let vi = ValueInformation::try_from(
2096 &ValueInformationBlock::try_from([0xFB, 0x20].as_slice()).unwrap(),
2097 )
2098 .unwrap();
2099 assert_eq!(vi.first_unit().unwrap().name, UnitName::Feet);
2100 assert_eq!(vi.first_unit().unwrap().exponent, 3);
2101 assert_eq!(vi.decimal_scale_exponent, 0);
2102 assert!(vi.has_label(ValueLabel::Volume));
2103
2104 let vi = ValueInformation::try_from(
2106 &ValueInformationBlock::try_from([0xFB, 0x23].as_slice()).unwrap(),
2107 )
2108 .unwrap();
2109 assert_eq!(vi.first_unit().unwrap().name, UnitName::Degree);
2110 assert_eq!(vi.decimal_scale_exponent, -1);
2111 assert!(vi.has_label(ValueLabel::PhaseItoU));
2112
2113 let vi = ValueInformation::try_from(
2115 &ValueInformationBlock::try_from([0xFB, 0x70].as_slice()).unwrap(),
2116 )
2117 .unwrap();
2118 assert_eq!(vi.first_unit().unwrap().name, UnitName::Fahrenheit);
2119 assert_eq!(vi.decimal_scale_exponent, -3);
2120
2121 let vi = ValueInformation::try_from(
2123 &ValueInformationBlock::try_from([0xFB, 0x22].as_slice()).unwrap(),
2124 )
2125 .unwrap();
2126 assert!(vi.has_label(ValueLabel::Reserved));
2127 }
2128
2129 #[test]
2130 fn test_primary_vif_on_time_and_operating_time_labels() {
2131 use crate::value_information::{
2132 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2133 };
2134
2135 let vi = ValueInformation::try_from(
2137 &ValueInformationBlock::try_from([0x21].as_slice()).unwrap(),
2138 )
2139 .unwrap();
2140 assert!(vi.has_label(ValueLabel::OnTime));
2141 assert_eq!(vi.first_unit().unwrap().name, UnitName::Minute);
2142
2143 let vi = ValueInformation::try_from(
2145 &ValueInformationBlock::try_from([0x27].as_slice()).unwrap(),
2146 )
2147 .unwrap();
2148 assert!(vi.has_label(ValueLabel::OperatingTime));
2149 assert_eq!(vi.first_unit().unwrap().name, UnitName::Day);
2150 }
2151
2152 #[test]
2153 fn test_fb_cumulative_maximum_of_active_power() {
2154 use crate::value_information::{
2155 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2156 };
2157
2158 let vi = ValueInformation::try_from(
2160 &ValueInformationBlock::try_from([0xFB, 0x78].as_slice()).unwrap(),
2161 )
2162 .unwrap();
2163 assert!(vi.has_label(ValueLabel::CumulativeMaximumOfActivePower));
2164 assert_eq!(vi.first_unit().unwrap().name, UnitName::Watt);
2165 assert_eq!(vi.decimal_scale_exponent, -3);
2166 }
2167
2168 #[test]
2169 fn test_fb_humidity_with_combinatorial_scale() {
2170 use crate::value_information::{
2171 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2172 };
2173
2174 let vi = ValueInformation::try_from(
2177 &ValueInformationBlock::try_from([0xFB, 0x9B, 0x74].as_slice()).unwrap(),
2178 )
2179 .unwrap();
2180
2181 assert!(vi.has_label(ValueLabel::RelativeHumidity));
2182 assert_eq!(vi.first_unit().unwrap().name, UnitName::Percent);
2183 assert_eq!(vi.decimal_scale_exponent, -2);
2184 }
2185
2186 #[test]
2187 fn test_fd_ampere_with_phase_combinatorial() {
2188 use crate::value_information::{
2189 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2190 };
2191
2192 let vi = ValueInformation::try_from(
2196 &ValueInformationBlock::try_from([0xFD, 0xD9, 0xFC, 0x01].as_slice()).unwrap(),
2197 )
2198 .unwrap();
2199
2200 assert_eq!(vi.first_unit().unwrap().name, UnitName::Ampere);
2201 assert_eq!(vi.decimal_scale_exponent, -3);
2202 assert!(vi.has_label(ValueLabel::AtPhaseL1));
2203 }
2204
2205 #[test]
2206 fn test_primary_vif_combinatorial_not_skipped() {
2207 use crate::value_information::{
2208 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2209 };
2210
2211 let vi = ValueInformation::try_from(
2215 &ValueInformationBlock::try_from([0xE5, 0x74].as_slice()).unwrap(),
2216 )
2217 .unwrap();
2218
2219 assert!(vi.has_label(ValueLabel::ExternalTemperature));
2220 assert_eq!(vi.first_unit().unwrap().name, UnitName::Celsius);
2221 assert_eq!(vi.decimal_scale_exponent, -4);
2222 }
2223
2224 #[test]
2225 fn test_fd_moisture_level() {
2226 use crate::value_information::{
2227 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2228 };
2229
2230 let vi = ValueInformation::try_from(
2233 &ValueInformationBlock::try_from([0xFD, 0xFD, 0x3E].as_slice()).unwrap(),
2234 )
2235 .unwrap();
2236
2237 assert!(vi.has_label(ValueLabel::MoistureLevel));
2238 assert_eq!(vi.first_unit().unwrap().name, UnitName::Percent);
2239 assert_eq!(vi.decimal_scale_exponent, 0);
2240 }
2241
2242 #[test]
2243 fn test_non_metric_vife_substitutes_units() {
2244 use crate::value_information::{
2245 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2246 };
2247
2248 let decode = |bytes: &[u8]| {
2249 let vi = ValueInformation::try_from(&ValueInformationBlock::try_from(bytes).unwrap())
2250 .unwrap();
2251 assert!(vi.has_label(ValueLabel::NonMetricUnits));
2252 let units: Vec<_> = vi.units().map(|u| (u.name, u.exponent)).collect();
2253 (units, vi.decimal_scale_exponent)
2254 };
2255
2256 assert_eq!(
2259 decode(&[0x93, 0x3D]),
2260 (vec![(UnitName::AmericanGallon, 1)], 0)
2261 );
2262 assert_eq!(
2264 decode(&[0x80, 0x3D]),
2265 (vec![(UnitName::BritishThermalUnit, 1)], 0)
2266 );
2267 assert_eq!(
2269 decode(&[0xA8, 0x3D]),
2270 (
2271 vec![(UnitName::BritishThermalUnit, 1), (UnitName::Second, -1)],
2272 -6
2273 )
2274 );
2275 assert_eq!(
2277 decode(&[0xC0, 0x3D]),
2278 (
2279 vec![(UnitName::AmericanGallon, 1), (UnitName::Minute, -1)],
2280 -4
2281 )
2282 );
2283 for vif in [0xD8, 0xDC, 0xE0, 0xE4] {
2285 assert_eq!(decode(&[vif, 0x3D]), (vec![(UnitName::Fahrenheit, 1)], -3));
2286 }
2287 assert_eq!(
2289 decode(&[0xFB, 0xF4, 0x3D]),
2290 (vec![(UnitName::Fahrenheit, 1)], -3)
2291 );
2292
2293 for bytes in [&[0x13][..], &[0x93, 0xFC, 0x3D], &[0x93, 0xFF, 0x3D]] {
2297 let vi = ValueInformation::try_from(&ValueInformationBlock::try_from(bytes).unwrap())
2298 .unwrap();
2299 assert!(!vi.has_label(ValueLabel::NonMetricUnits), "{bytes:02X?}");
2300 assert_eq!(
2301 vi.first_unit().unwrap().name,
2302 UnitName::Meter,
2303 "{bytes:02X?}"
2304 );
2305 assert_eq!(vi.decimal_scale_exponent, -3, "{bytes:02X?}");
2306 }
2307 }
2308
2309 #[test]
2310 fn test_vib_struct_layout() {
2311 use crate::value_information::ValueInformationBlock;
2312
2313 let vib = ValueInformationBlock::try_from([0xFD, 0xD9, 0xFC, 0x01].as_slice()).unwrap();
2316
2317 assert_eq!(vib.value_information.data, 0xFD);
2318 assert_eq!(vib.get_size(), 4);
2319 assert!(vib.plaintext_vife.is_none());
2320
2321 let mut ext = vib.value_information_extension.unwrap();
2322 assert_eq!(ext.len(), 3);
2323 assert_eq!(ext.next().unwrap().data, 0xD9);
2324 assert_eq!(ext.next().unwrap().data, 0xFC);
2325 assert_eq!(ext.next().unwrap().data, 0x01);
2326
2327 let vib = ValueInformationBlock::try_from([0x96, 0x12].as_slice()).unwrap();
2330
2331 assert_eq!(vib.value_information.data, 0x96);
2332 assert_eq!(vib.get_size(), 2);
2333
2334 let mut ext = vib.value_information_extension.unwrap();
2335 assert_eq!(ext.len(), 1);
2336 assert_eq!(ext.next().unwrap().data, 0x12);
2337
2338 let vib = ValueInformationBlock::try_from([0x13].as_slice()).unwrap();
2340
2341 assert_eq!(vib.value_information.data, 0x13);
2342 assert_eq!(vib.get_size(), 1);
2343 assert!(vib.value_information_extension.is_none());
2344 }
2345
2346 #[test]
2347 fn test_combinable_orthogonal_vife_limit_exceed_mappings() {
2348 use crate::value_information::{
2349 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2350 };
2351
2352 let cases: &[(u8, ValueLabel, Option<UnitName>)] = &[
2354 (0x40, ValueLabel::LowerLimitValue, None),
2355 (0x48, ValueLabel::UpperLimitValue, None),
2356 (0x41, ValueLabel::NumberOfExceedsOfLowerLimitValue, None),
2357 (0x49, ValueLabel::NumberOfExceedsOfUpperLimitValue, None),
2358 (0x42, ValueLabel::DateOfBeginFirstLowerLimitExceed, None),
2360 (0x43, ValueLabel::DateOfEndFirstLowerLimitExceed, None),
2361 (0x46, ValueLabel::DateOfBeginLastLowerLimitExceed, None),
2362 (0x47, ValueLabel::DateOfEndLastLowerLimitExceed, None),
2363 (0x4A, ValueLabel::DateOfBeginFirstUpperLimitExceed, None),
2364 (0x4B, ValueLabel::DateOfEndFirstUpperLimitExceed, None),
2365 (0x4E, ValueLabel::DateOfBeginLastUpperLimitExceed, None),
2366 (0x4F, ValueLabel::DateOfEndLastUpperLimitExceed, None),
2367 (
2369 0x50,
2370 ValueLabel::DurationOfFirstLowerLimitExceed,
2371 Some(UnitName::Second),
2372 ),
2373 (
2374 0x51,
2375 ValueLabel::DurationOfFirstLowerLimitExceed,
2376 Some(UnitName::Minute),
2377 ),
2378 (
2379 0x52,
2380 ValueLabel::DurationOfFirstLowerLimitExceed,
2381 Some(UnitName::Hour),
2382 ),
2383 (
2384 0x53,
2385 ValueLabel::DurationOfFirstLowerLimitExceed,
2386 Some(UnitName::Day),
2387 ),
2388 (
2390 0x54,
2391 ValueLabel::DurationOfLastLowerLimitExceed,
2392 Some(UnitName::Second),
2393 ),
2394 (
2395 0x55,
2396 ValueLabel::DurationOfLastLowerLimitExceed,
2397 Some(UnitName::Minute),
2398 ),
2399 (
2400 0x56,
2401 ValueLabel::DurationOfLastLowerLimitExceed,
2402 Some(UnitName::Hour),
2403 ),
2404 (
2405 0x57,
2406 ValueLabel::DurationOfLastLowerLimitExceed,
2407 Some(UnitName::Day),
2408 ),
2409 (
2411 0x58,
2412 ValueLabel::DurationOfFirstUpperLimitExceed,
2413 Some(UnitName::Second),
2414 ),
2415 (
2416 0x59,
2417 ValueLabel::DurationOfFirstUpperLimitExceed,
2418 Some(UnitName::Minute),
2419 ),
2420 (
2421 0x5A,
2422 ValueLabel::DurationOfFirstUpperLimitExceed,
2423 Some(UnitName::Hour),
2424 ),
2425 (
2426 0x5B,
2427 ValueLabel::DurationOfFirstUpperLimitExceed,
2428 Some(UnitName::Day),
2429 ),
2430 (
2432 0x5C,
2433 ValueLabel::DurationOfLastUpperLimitExceed,
2434 Some(UnitName::Second),
2435 ),
2436 (
2437 0x5D,
2438 ValueLabel::DurationOfLastUpperLimitExceed,
2439 Some(UnitName::Minute),
2440 ),
2441 (
2442 0x5E,
2443 ValueLabel::DurationOfLastUpperLimitExceed,
2444 Some(UnitName::Hour),
2445 ),
2446 (
2447 0x5F,
2448 ValueLabel::DurationOfLastUpperLimitExceed,
2449 Some(UnitName::Day),
2450 ),
2451 ];
2452
2453 for (vife_byte, expected_label, expected_unit) in cases {
2454 let data = [0x93, *vife_byte];
2455 let vib = ValueInformationBlock::try_from(data.as_slice()).unwrap();
2456 let vi = ValueInformation::try_from(&vib).unwrap();
2457 assert!(
2458 vi.has_label(*expected_label),
2459 "VIFE 0x{vife_byte:02X}: expected label {expected_label:?}, got {:?}",
2460 vi.labels()
2461 );
2462 if let Some(unit_name) = expected_unit {
2463 assert!(
2464 vi.units().any(|u| u.name == *unit_name),
2465 "VIFE 0x{vife_byte:02X}: expected unit {unit_name:?}, got {:?}",
2466 vi.units()
2467 );
2468 }
2469 }
2470 }
2471
2472 #[test]
2473 fn test_combinable_orthogonal_vife_fc_extension_mappings() {
2474 use crate::value_information::{ValueInformation, ValueInformationBlock, ValueLabel};
2475
2476 let cases: &[(u8, ValueLabel)] = &[
2477 (0x02, ValueLabel::AtPhaseL2),
2478 (0x0D, ValueLabel::AlternativeNonMetricUnits),
2479 (0x0E, ValueLabel::SecondarySensorMeasurement),
2480 (0x13, ValueLabel::EndDate),
2481 ];
2482
2483 for (vife_byte, expected_label) in cases {
2484 let data = [0x93, 0xFC, *vife_byte];
2485 let vib = ValueInformationBlock::try_from(data.as_slice()).unwrap();
2486 let vi = ValueInformation::try_from(&vib).unwrap();
2487 assert!(
2488 vi.has_label(*expected_label),
2489 "FC VIFE 0x{vife_byte:02X}: expected {expected_label:?}, got {:?}",
2490 vi.labels()
2491 );
2492 }
2493 }
2494 #[test]
2495 fn units_exceed_old_capacity() {
2496 use super::*;
2497 let block =
2498 ValueInformationBlock::try_from([0xB8, 0xA8, 0xA8, 0xA8, 0xA8, 0x28].as_slice())
2499 .unwrap();
2500 let vi = ValueInformation::try_from(&block).unwrap();
2501 assert!(vi
2502 .units()
2503 .eq([unit!(Meter ^ 3), unit!(Hour ^ -1)].into_iter().chain(
2504 core::iter::repeat_n([unit!(Increment), unit!(InputPulseOnChannel0 ^ -1)], 5)
2505 .flatten()
2506 )));
2507 assert_eq!(vi.units().count(), 12);
2508 }
2509
2510 #[test]
2511 fn labels_exceed_old_capacity() {
2512 use super::*;
2513 let block = ValueInformationBlock::try_from(
2514 [
2515 0xFD, 0x9A, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x12,
2516 ]
2517 .as_slice(),
2518 )
2519 .unwrap();
2520 let vi = ValueInformation::try_from(&block).unwrap();
2521 assert!(vi
2522 .labels()
2523 .eq([ValueLabel::DigitalOutput, ValueLabel::Binary]
2524 .into_iter()
2525 .chain(core::iter::repeat_n(ValueLabel::Averaged, 9))));
2526 assert_eq!(vi.labels().count(), 11);
2527 }
2528
2529 #[test]
2530 fn repeated_fc_and_terminal_7c_preserve_table_selection() {
2531 use super::*;
2532 for (bytes, expected) in [
2533 (
2534 &[0x93, 0xFC, 0xFC, 0x01][..],
2535 &[ValueLabel::Volume, ValueLabel::AtPhaseL1][..],
2536 ),
2537 (
2538 &[0x93, 0x7C][..],
2539 &[ValueLabel::Volume, ValueLabel::Reserved][..],
2540 ),
2541 (
2542 &[0x93, 0xFC, 0x81, 0x12][..],
2543 &[
2544 ValueLabel::Volume,
2545 ValueLabel::AtPhaseL1,
2546 ValueLabel::Averaged,
2547 ][..],
2548 ),
2549 ] {
2550 let block = ValueInformationBlock::try_from(bytes).unwrap();
2551 assert!(ValueInformation::try_from(&block)
2552 .unwrap()
2553 .labels()
2554 .eq(expected.iter().copied()));
2555 }
2556 }
2557
2558 #[test]
2559 fn main_extension_subcode_is_also_orthogonal() {
2560 use super::*;
2561 let block = ValueInformationBlock::try_from([0xFD, 0xFD, 0x3E].as_slice()).unwrap();
2562 let vi = ValueInformation::try_from(&block).unwrap();
2563 assert!(vi
2564 .labels()
2565 .eq([ValueLabel::MoistureLevel, ValueLabel::ValueAtBaseConditions]));
2566 assert!(vi.units().eq([unit!(Percent)]));
2567 }
2568
2569 #[test]
2570 fn equality_is_semantic_and_iterators_outlive_the_view() {
2571 use super::*;
2572 let short = ValueInformationBlock::try_from([0x13].as_slice()).unwrap();
2573 let equivalent = ValueInformationBlock::try_from([0x93, 0x76].as_slice()).unwrap();
2575 assert_eq!(
2576 ValueInformation::try_from(&short).unwrap(),
2577 ValueInformation::try_from(&equivalent).unwrap()
2578 );
2579 let (mut labels, mut units) = {
2580 let vi = ValueInformation::try_from(&short).unwrap();
2581 (vi.labels(), vi.units())
2582 };
2583 assert_eq!(labels.next(), Some(ValueLabel::Volume));
2584 assert_eq!(units.next(), Some(unit!(Meter ^ 3)));
2585 assert!(labels.clone().eq(labels));
2586 assert!(units.clone().eq(units));
2587 }
2588
2589 #[test]
2590 fn manufacturer_escape_stops_standard_vife_decoding() {
2591 use super::*;
2592 let block = ValueInformationBlock::try_from([0x84, 0xFF, 0xF2, 0x00].as_slice()).unwrap();
2596 let vi = ValueInformation::try_from(&block).unwrap();
2597 assert_eq!(vi.decimal_scale_exponent, 1);
2598 let mut labels = vi.labels();
2599 assert_eq!(labels.next(), Some(ValueLabel::Energy));
2600 assert_eq!(
2601 labels.next(),
2602 Some(ValueLabel::NextVIFEAndDataOfThisBlockAreManufacturerSpecific)
2603 );
2604 assert_eq!(labels.next(), None);
2605 }
2606
2607 #[test]
2608 fn exponents_accumulate_and_iterators_clone_mid_chain() {
2609 use super::*;
2610 let block =
2611 ValueInformationBlock::try_from([0x93, 0x92, 0xA8, 0xF5, 0x78].as_slice()).unwrap();
2612 let vi = ValueInformation::try_from(&block).unwrap();
2613 assert_eq!(vi.decimal_scale_exponent, -4);
2614 assert_eq!(vi.decimal_offset_exponent, -3);
2615 let mut units = vi.units();
2616 assert_eq!(units.next(), Some(unit!(Meter ^ 3)));
2617 assert_eq!(units.next(), Some(unit!(Increment)));
2618 assert!(units.clone().eq(units));
2619 let mut labels = vi.labels();
2620 assert_eq!(labels.next(), Some(ValueLabel::Volume));
2621 assert!(labels.clone().eq(labels));
2622 }
2623
2624 #[test]
2625 fn head_table_and_missing_extensions() {
2626 use super::*;
2627 let cases = [
2628 (
2629 0x13,
2630 None,
2631 None,
2632 VifInfo {
2633 labels: &[ValueLabel::Volume],
2634 units: &[unit!(Meter ^ 3)],
2635 scale: -3,
2636 ..VifInfo::EMPTY
2637 },
2638 ),
2639 (
2640 0xFD,
2641 Some(0x1A),
2642 None,
2643 labels!(ValueLabel::DigitalOutput, ValueLabel::Binary),
2644 ),
2645 (
2646 0xFB,
2647 Some(0x1A),
2648 None,
2649 VifInfo {
2650 labels: &[ValueLabel::RelativeHumidity],
2651 units: &[unit!(Percent)],
2652 scale: -1,
2653 ..VifInfo::EMPTY
2654 },
2655 ),
2656 (
2657 0xFD,
2658 Some(0x7D),
2659 Some(0x3E),
2660 VifInfo {
2661 labels: &[ValueLabel::MoistureLevel],
2662 units: &[unit!(Percent)],
2663 ..VifInfo::EMPTY
2664 },
2665 ),
2666 (0x7C, None, None, labels!(ValueLabel::PlainText)),
2667 (0x7F, None, None, labels!(ValueLabel::ManufacturerSpecific)),
2668 ];
2669 for (vif, first, second, expected) in cases {
2670 assert_eq!(head_vif_info(vif.into(), first, second).unwrap(), expected);
2671 }
2672 assert!(matches!(
2673 head_vif_info(0x6F.into(), None, None),
2674 Err(DataInformationError::Unimplemented { .. })
2675 ));
2676 for vif in [0xFD, 0xFB] {
2677 let mut block = ValueInformationBlock {
2678 value_information: vif.into(),
2679 value_information_extension: None,
2680 plaintext_vife: None,
2681 };
2682 assert!(ValueInformation::try_from(&block)
2683 .unwrap()
2684 .labels()
2685 .next()
2686 .is_none());
2687 block.value_information_extension = Some(ValueInformationFieldExtensions(&[]));
2688 assert_eq!(
2689 ValueInformation::try_from(&block),
2690 Err(DataInformationError::DataTooShort)
2691 );
2692 }
2693 }
2694}