Skip to main content

m_bus_application_layer/
value_information.rs

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            // When the plaintext VIF precedes the extensions, the VIFE chain
74            // starts after the ASCII length byte and string, not at offset 1.
75            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            // 1 byte for the length of the ASCII string
328            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            // E100 u000 where u = 0: Lower; u = 1: Upper
956            0x40 => labels!(ValueLabel::LowerLimitValue),
957            0x48 => labels!(ValueLabel::UpperLimitValue),
958            // E100 u001 where u = 0: Lower; u = 1: Upper
959            0x41 => labels!(ValueLabel::NumberOfExceedsOfLowerLimitValue),
960            0x49 => labels!(ValueLabel::NumberOfExceedsOfUpperLimitValue),
961            /* E100 uf1b where
962            b = 0: Begin; b = 1: End
963            f = 0: First; b = 1: Last
964            u = 0: Lower; u = 1: Upper
965            */
966            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}
1134/// Selects the orthogonal part of a VIFE chain.
1135///
1136/// Primary and PlainText start at VIFE[0]; MainVIFExtension and
1137/// AlternateVIFExtension start at VIFE[1]; ManufacturerSpecific has no chain.
1138/// Main extension 0x7D deliberately reuses VIFE[1] as both its sub-code and
1139/// the first orthogonal VIFE, preserving the original decoder's behavior.
1140fn 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            // Whole-byte, unconditional comparison: repeated 0xFC prefixes keep
1167            // the extension table selected; a terminal 0x7C is ordinary data.
1168            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                // Everything following the manufacturer escape is vendor data,
1175                // so it must not be matched against the standard VIFE table.
1176                self.vife = ValueInformationFieldExtensions(&[]);
1177            }
1178            return Some(orthogonal_vife_info(v.data, ext));
1179        }
1180    }
1181}
1182
1183/// Substitutes the metric units of a VIF followed by VIFE 0x3D, per
1184/// EN 13757-3 Annex C Table C.1. Returns the non-metric units and the change
1185/// to the decimal exponent; kBTU and mBTU/s are expressed as BTU and BTU/s.
1186///
1187/// Kept out of line and run after the scale fold, so the scale fold keeps its
1188/// two-word accumulator and the scan's frame is not live at the same time.
1189#[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            // 10^(nnn-3) Wh -> 10^(nnn-3) kBTU
1199            0x00..=0x07 => Some((&[unit!(BritishThermalUnit)], 3)),
1200            // 10^(nnn-6) m³ -> 10^(nnn-3) USgal
1201            0x10..=0x17 => Some((&[unit!(AmericanGallon)], 3)),
1202            // 10^(nnn-3) W -> 10^(nnn-3) mBTU/s
1203            0x28..=0x2F => Some((&[unit!(BritishThermalUnit), unit!(Second ^ -1)], -3)),
1204            // 10^(nnn-7) m³/min -> 10^(nnn-4) USgal/min
1205            0x40..=0x47 => Some((&[unit!(AmericanGallon), unit!(Minute ^ -1)], 3)),
1206            // Flow, return, difference and external temperature: °C/K -> °F
1207            0x58..=0x67 => Some((&[unit!(Fahrenheit)], 0)),
1208            _ => None,
1209        },
1210        // Cold/warm temperature limit: °C -> °F
1211        ValueInformationCoding::AlternateVIFExtension => match first_vife? & 0x7F {
1212            0x74..=0x77 => Some((&[unit!(Fahrenheit)], 0)),
1213            _ => None,
1214        },
1215        _ => None,
1216    };
1217    let units = units?;
1218    // Same walk as OrthogonalVifes::next, but on raw bytes: decoding each VIFE
1219    // into a VifInfo would add an iterator frame below this one.
1220    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/// A borrowed, allocation-free view of decoded VIF and VIFE information.
1238///
1239/// Labels and units are produced in wire order, including duplicates. The view
1240/// borrows frame bytes, independently of the block used to construct it.
1241#[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    /// Iterates over all decoded labels in wire order.
1251    #[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    /// Iterates over all decoded units in wire order.
1262    #[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        // Peek at the remaining borrowed bytes directly. Flattening an optional
1287        // iterator adds state transitions even for the common no-extension case.
1288        let bytes = ext.as_ref().map_or(&[][..], |ext| ext.0);
1289        let first = bytes.first().copied();
1290        let second = bytes.get(1).copied();
1291        // A present but exhausted extension iterator was an error in the old
1292        // decoder; absent extensions on manually constructed blocks were empty.
1293        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// Binary serializers need a known length. Count a clone so serialization stays
1371// allocation-free and leaves the caller's iterator position unchanged.
1372#[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/// Cloneable iterator over decoded labels in wire order.
1388#[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/// Cloneable iterator over decoded units in wire order.
1418#[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        /* VIB = 0x13 => m3^3*1e-3 */
1890        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        /* VIB = 0x14 => m3^-3*1e-2 */
1910        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        /* VIB = 0x15 => m3^3*1e-2 */
1930        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        /* VIB = 0x16 => m3^-3*1e-1 */
1950        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        /* 1 VIF, 1 - 10 orthogonal VIFE */
1971
1972        /* VIF 0x96 = 0x16 | 0x80  => m3^-3*1e-1 with extension*/
1973        /* VIFE 0x12 => Combinable Orthogonal VIFE meaning "averaged" */
1974        /* VIB = 0x96, 0x12 */
1975        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        /* VIF 0x96 = 0x16 | 0x80  => m3^-3*1e-1 with extension*/
1985        /* VIFE 0x92 = 0x12 | 0x80  => Combinable Orthogonal VIFE meaning "averaged" with extension */
1986        /* VIFE 0x20 => Combinable Orthogonal VIFE meaning "per second" */
1987        /* VIB = 0x96, 0x92,0x20 */
1988
1989        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        /* VIF 0x96 = 0x16 | 0x80  => m3^-3*1e-1 with extension*/
2002        /* VIFE 0x92 = 0x12 | 0x80  => Combinable Orthogonal VIFE meaning "averaged" with extension */
2003        /* VIFE 0xA0= 0x20 | 0x80 => Combinable Orthogonal VIFE meaning "per second" */
2004        /* VIFE 0x2D => Combinable Orthogonal VIFE meaning "per m3". This cancels out the VIF m3, which is useless
2005        but till a valid VIB */
2006        /* VIB = 0x96, 0x92,0xA0, 0x2D */
2007        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        // This is the ascii conform method of encoding the VIF
2027        // VIF  VIFE  LEN(3) 'H'   'R'  '%'
2028        // 0xFC, 0x74, 0x03, 0x48, 0x52, 0x25,
2029        // %RH
2030        // Combinable (orthogonal) VIFE-Code extension table
2031        // VIFE = 0x74 => E111 0nnn Multiplicative correction factor for value (not unit): 10nnn–6 => 10^-2
2032        //
2033        // according to the Norm the LEN and ASCII is not part of the VIB however this makes parsing
2034        // cumbersome so we include it in the VIB
2035
2036        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        // This is how the VIF is encoded in the test vectors
2049        // VIF  LEN(3) 'R'   'H'  '%'    VIFE
2050        // 0xFC, 0x03, 0x48, 0x52, 0x25, 0x74,
2051        // %RH
2052        // VIFE = 0x74 => E111 0nnn Multiplicative correction factor for value (not unit): 10nnn–6 => 10^-2
2053        // when not following the norm the LEN and ASCII is part of the VIB
2054        // It is however none norm conform, see the next example which follows
2055        // the MBUS Norm which explicitly states that the VIIFE should be after the VIF
2056        // not aftter the ASCII plain text and its size
2057    }
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        // VIF=0xFD VIFE=0x48: Voltage 10^(8-9) = 0.1 V
2073        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        // VIF=0xFD VIFE=0x59: Ampere 10^(9-12) = 0.001 A
2081        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        // VIF=0xFB VIFE=0x20 (E010 0000): ft³, dec: 0
2095        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        // VIF=0xFB VIFE=0x23 (E010 0011): Phase angle I-U, 0.1°
2105        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        // VIF=0xFB VIFE=0x70: °F cold/warm temp limit, 10^-3
2114        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        // VIF=0xFB VIFE=0x22 (E010 0010): Reserved — should not error
2122        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        // VIF 0x21 = 0010 0001 = On time (0x20-0x23), nn=01 => minutes
2136        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        // VIF 0x27 = 0010 0111 = Operating time (0x24-0x27), nn=11 => days
2144        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        // VIF=0xFB VIFE=0x78 (0b0111_1000): CumulativeMaximumOfActivePower, W 10^-3
2159        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        // VIF=0xFB, VIFE=0x9B (0x1B + extension bit), VIFE2=0x74 (multiplicative 10^(4-6) = 10^-2)
2175        // OMS RH01: relative humidity 10^0 %, shifted to 10^-2 by combinatorial.
2176        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        // VIF=0xFD, VIFE=0xD9 (0x59 + extension bit = Ampere 10^-3),
2193        // VIFE2=0xFC (combinatorial extension), VIFE3=0x01 (AtPhaseL1)
2194        // OMS CA01: per-phase current.
2195        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        // VIF=0xE5 (0x65 + extension bit = External temperature 10^-2),
2212        // VIFE=0x74 (multiplicative 10^(4-6) = 10^-2)
2213        // First VIFE must NOT be skipped for primary VIFs — total should be 10^-4.
2214        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        // OMS RH03: VIF=0xFD, VIFE1=0xFD (0x7D + extension bit), VIFE2=0x3E
2231        // Moisture Level, % 10^0
2232        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        // EN 13757-3 Annex C Table C.1, VIFE 0x3D after each metric VIF.
2257        // Issue #62: VIF 0x93 is 10^-3 m³ = 1 l, which maps to 1 USgal.
2258        assert_eq!(
2259            decode(&[0x93, 0x3D]),
2260            (vec![(UnitName::AmericanGallon, 1)], 0)
2261        );
2262        // 10^-3 Wh -> 10^-3 kBTU = 1 BTU
2263        assert_eq!(
2264            decode(&[0x80, 0x3D]),
2265            (vec![(UnitName::BritishThermalUnit, 1)], 0)
2266        );
2267        // 10^-3 W -> 10^-3 mBTU/s = 10^-6 BTU/s
2268        assert_eq!(
2269            decode(&[0xA8, 0x3D]),
2270            (
2271                vec![(UnitName::BritishThermalUnit, 1), (UnitName::Second, -1)],
2272                -6
2273            )
2274        );
2275        // 10^-7 m³/min -> 10^-4 USgal/min
2276        assert_eq!(
2277            decode(&[0xC0, 0x3D]),
2278            (
2279                vec![(UnitName::AmericanGallon, 1), (UnitName::Minute, -1)],
2280                -4
2281            )
2282        );
2283        // Flow, return, difference and external temperature: 10^-3 °F
2284        for vif in [0xD8, 0xDC, 0xE0, 0xE4] {
2285            assert_eq!(decode(&[vif, 0x3D]), (vec![(UnitName::Fahrenheit, 1)], -3));
2286        }
2287        // Cold/warm temperature limit via 0xFB 0x74: 10^-3 °F
2288        assert_eq!(
2289            decode(&[0xFB, 0xF4, 0x3D]),
2290            (vec![(UnitName::Fahrenheit, 1)], -3)
2291        );
2292
2293        // Without the VIFE, after the combinable extension prefix 0xFC, or after
2294        // the manufacturer escape 0xFF, 0x3D is not the non-metric VIFE and the
2295        // unit stays metric.
2296        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        // FD extension with orthogonal VIFEs: Ampere 10^-3, AtPhaseL1
2314        // VIF=0xFD, VIFE[0]=0xD9 (true VIF), VIFE[1]=0xFC, VIFE[2]=0x01
2315        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        // Primary VIF with one orthogonal VIFE
2328        // VIF=0x96 (Volume + extension bit), VIFE=0x12 (Averaged)
2329        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        // Single primary VIF, no extension
2339        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        // (vife_byte, expected_label, optional expected unit name)
2353        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            // E100 uf1b: b=Begin/End, f=First/Last, u=Lower/Upper
2359            (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            // Duration of first lower (0x50-0x53)
2368            (
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            // Duration of last lower (0x54-0x57)
2389            (
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            // Duration of first upper (0x58-0x5B)
2410            (
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            // Duration of last upper (0x5C-0x5F)
2431            (
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        // 0x76 contributes zero scale and no labels or units.
2574        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        // ABB B21 energy record: VIF 0x04 (energy, x10^1) followed by the
2593        // manufacturer escape 0xFF; 0xF2 and 0x00 are vendor data and must not
2594        // be read as the standard multiplicative correction 0x70..=0x77.
2595        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}