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.iter().count();
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/// A borrowed, allocation-free view of decoded VIF and VIFE information.
1184///
1185/// Labels and units are produced in wire order, including duplicates. The view
1186/// borrows frame bytes, independently of the block used to construct it.
1187#[derive(Clone)]
1188pub struct ValueInformation<'a> {
1189    head_labels: &'static [ValueLabel],
1190    head_units: &'static [Unit],
1191    orthogonal: ValueInformationFieldExtensions<'a>,
1192    pub decimal_scale_exponent: isize,
1193    pub decimal_offset_exponent: isize,
1194}
1195impl<'a> ValueInformation<'a> {
1196    /// Iterates over all decoded labels in wire order.
1197    #[must_use]
1198    pub fn labels(&self) -> ValueLabels<'a> {
1199        ValueLabels {
1200            current: self.head_labels,
1201            rest: OrthogonalVifes {
1202                vife: self.orthogonal.clone(),
1203                combinable_ext: false,
1204            },
1205        }
1206    }
1207    /// Iterates over all decoded units in wire order.
1208    #[must_use]
1209    pub fn units(&self) -> Units<'a> {
1210        Units {
1211            current: self.head_units,
1212            rest: OrthogonalVifes {
1213                vife: self.orthogonal.clone(),
1214                combinable_ext: false,
1215            },
1216        }
1217    }
1218    #[must_use]
1219    pub fn has_label(&self, label: ValueLabel) -> bool {
1220        self.labels().any(|item| item == label)
1221    }
1222    #[must_use]
1223    pub fn first_unit(&self) -> Option<Unit> {
1224        self.units().next()
1225    }
1226}
1227impl<'a> TryFrom<&ValueInformationBlock<'a>> for ValueInformation<'a> {
1228    type Error = DataInformationError;
1229    fn try_from(block: &ValueInformationBlock<'a>) -> Result<Self, Self::Error> {
1230        let coding = ValueInformationCoding::from(&block.value_information);
1231        let ext = block.value_information_extension.clone();
1232        // Peek at the remaining borrowed bytes directly. Flattening an optional
1233        // iterator adds state transitions even for the common no-extension case.
1234        let bytes = ext.as_ref().map_or(&[][..], |ext| ext.0);
1235        let first = bytes.first().copied();
1236        let second = bytes.get(1).copied();
1237        // A present but exhausted extension iterator was an error in the old
1238        // decoder; absent extensions on manually constructed blocks were empty.
1239        if matches!(
1240            coding,
1241            ValueInformationCoding::MainVIFExtension
1242                | ValueInformationCoding::AlternateVIFExtension
1243        ) && ext.is_some()
1244            && first.is_none()
1245        {
1246            return Err(DataInformationError::DataTooShort);
1247        }
1248        let head = head_vif_info(block.value_information.clone(), first, second)?;
1249        let orthogonal = OrthogonalVifes {
1250            vife: orthogonal_chain(coding, ext),
1251            combinable_ext: false,
1252        };
1253        let (scale, offset) = orthogonal
1254            .clone()
1255            .fold((head.scale, head.offset), |(s, o), v| {
1256                (s + v.scale, o + v.offset)
1257            });
1258        Ok(Self {
1259            head_labels: head.labels,
1260            head_units: head.units,
1261            orthogonal: orthogonal.vife,
1262            decimal_scale_exponent: scale,
1263            decimal_offset_exponent: offset,
1264        })
1265    }
1266}
1267impl PartialEq for ValueInformation<'_> {
1268    fn eq(&self, other: &Self) -> bool {
1269        self.decimal_scale_exponent == other.decimal_scale_exponent
1270            && self.decimal_offset_exponent == other.decimal_offset_exponent
1271            && self.labels().eq(other.labels())
1272            && self.units().eq(other.units())
1273    }
1274}
1275impl core::fmt::Debug for ValueInformation<'_> {
1276    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1277        f.debug_struct("ValueInformation")
1278            .field("decimal_offset_exponent", &self.decimal_offset_exponent)
1279            .field("labels", &self.labels())
1280            .field("decimal_scale_exponent", &self.decimal_scale_exponent)
1281            .field("units", &self.units())
1282            .finish()
1283    }
1284}
1285#[cfg(feature = "serde")]
1286impl serde::Serialize for ValueInformation<'_> {
1287    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1288        use serde::ser::SerializeStruct;
1289        let mut state = serializer.serialize_struct("ValueInformation", 4)?;
1290        state.serialize_field("decimal_offset_exponent", &self.decimal_offset_exponent)?;
1291        state.serialize_field("labels", &self.labels())?;
1292        state.serialize_field("decimal_scale_exponent", &self.decimal_scale_exponent)?;
1293        state.serialize_field("units", &self.units())?;
1294        state.end()
1295    }
1296}
1297
1298// Binary serializers need a known length. Count a clone so serialization stays
1299// allocation-free and leaves the caller's iterator position unchanged.
1300#[cfg(feature = "serde")]
1301fn serialize_counted_sequence<I, S>(items: I, serializer: S) -> Result<S::Ok, S::Error>
1302where
1303    I: Iterator + Clone,
1304    I::Item: serde::Serialize,
1305    S: serde::Serializer,
1306{
1307    use serde::ser::SerializeSeq;
1308    let mut sequence = serializer.serialize_seq(Some(items.clone().count()))?;
1309    for item in items {
1310        sequence.serialize_element(&item)?;
1311    }
1312    sequence.end()
1313}
1314
1315/// Cloneable iterator over decoded labels in wire order.
1316#[derive(Clone)]
1317pub struct ValueLabels<'a> {
1318    current: &'static [ValueLabel],
1319    rest: OrthogonalVifes<'a>,
1320}
1321impl Iterator for ValueLabels<'_> {
1322    type Item = ValueLabel;
1323    fn next(&mut self) -> Option<Self::Item> {
1324        loop {
1325            if let Some((first, tail)) = self.current.split_first() {
1326                self.current = tail;
1327                return Some(*first);
1328            }
1329            self.current = self.rest.next()?.labels;
1330        }
1331    }
1332}
1333impl core::fmt::Debug for ValueLabels<'_> {
1334    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1335        f.debug_list().entries(self.clone()).finish()
1336    }
1337}
1338#[cfg(feature = "serde")]
1339impl serde::Serialize for ValueLabels<'_> {
1340    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1341        serialize_counted_sequence(self.clone(), serializer)
1342    }
1343}
1344
1345/// Cloneable iterator over decoded units in wire order.
1346#[derive(Clone)]
1347pub struct Units<'a> {
1348    current: &'static [Unit],
1349    rest: OrthogonalVifes<'a>,
1350}
1351impl Iterator for Units<'_> {
1352    type Item = Unit;
1353    fn next(&mut self) -> Option<Self::Item> {
1354        loop {
1355            if let Some((first, tail)) = self.current.split_first() {
1356                self.current = tail;
1357                return Some(*first);
1358            }
1359            self.current = self.rest.next()?.units;
1360        }
1361    }
1362}
1363impl core::fmt::Debug for Units<'_> {
1364    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1365        f.debug_list().entries(self.clone()).finish()
1366    }
1367}
1368#[cfg(feature = "serde")]
1369impl serde::Serialize for Units<'_> {
1370    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1371        serialize_counted_sequence(self.clone(), serializer)
1372    }
1373}
1374#[cfg(feature = "defmt")]
1375impl defmt::Format for ValueInformation<'_> {
1376    fn format(&self, f: defmt::Formatter) {
1377        defmt::write!(
1378            f,
1379            "ValueInformation{{ decimal_offset_exponent: {}, decimal_scale_exponent: {}",
1380            self.decimal_offset_exponent,
1381            self.decimal_scale_exponent
1382        );
1383        let mut labels = self.labels().peekable();
1384        if labels.peek().is_some() {
1385            defmt::write!(f, ", labels: [");
1386            for (i, label) in labels.enumerate() {
1387                if i != 0 {
1388                    defmt::write!(f, ", ");
1389                }
1390                defmt::write!(f, "{:?}", label);
1391            }
1392            defmt::write!(f, "]");
1393        }
1394        let mut units = self.units().peekable();
1395        if units.peek().is_some() {
1396            defmt::write!(f, ", units: [");
1397            for (i, unit) in units.enumerate() {
1398                if i != 0 {
1399                    defmt::write!(f, ", ");
1400                }
1401                defmt::write!(f, "{:?}", unit);
1402            }
1403            defmt::write!(f, "]");
1404        }
1405        defmt::write!(f, " }}");
1406    }
1407}
1408
1409#[cfg(feature = "std")]
1410impl fmt::Display for ValueInformation<'_> {
1411    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1412        if self.decimal_offset_exponent != 0 {
1413            write!(f, "+{})", self.decimal_offset_exponent)?;
1414        } else {
1415            write!(f, ")")?;
1416        }
1417        if self.decimal_scale_exponent != 0 {
1418            write!(f, "e{}", self.decimal_scale_exponent)?;
1419        }
1420        let mut units = self.units().peekable();
1421        if units.peek().is_some() {
1422            write!(f, "[")?;
1423            for unit in units {
1424                write!(f, "{}", unit)?;
1425            }
1426            write!(f, "]")?;
1427        }
1428        let mut labels = self.labels().peekable();
1429        if labels.peek().is_some() {
1430            write!(f, "(")?;
1431            for (i, label) in labels.enumerate() {
1432                if i != 0 {
1433                    write!(f, ", ")?;
1434                }
1435                write!(f, "{:?}", label)?;
1436            }
1437
1438            return write!(f, ")");
1439        }
1440        Ok(())
1441    }
1442}
1443#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1444#[derive(Debug, Clone, Copy, PartialEq)]
1445#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1446#[non_exhaustive]
1447pub enum ValueLabel {
1448    Instantaneous,
1449    ReservedForObjectActions,
1450    Reserved,
1451    Averaged,
1452    Integral,
1453    Parameter,
1454    InverseCompactProfile,
1455    RelativeDeviation,
1456    RecordErrorCodes,
1457    StandardConformDataContent,
1458    CompactProfileWithRegisterNumbers,
1459    CompactProfile,
1460    ActualityDuration,
1461    AveragingDuration,
1462    Date,
1463    Time,
1464    DateTime,
1465    DateTimeWithSeconds,
1466    FabricationNumber,
1467    EnhancedIdentification,
1468    Address,
1469    PlainText,
1470    RevolutionOrMeasurement,
1471    IncrementPerInputPulseOnChannelP,
1472    IncrementPerOutputPulseOnChannelP,
1473    HourMinuteSecond,
1474    DayMonthYear,
1475    StartDateOf,
1476    VifContainsUncorrectedUnitOrValue,
1477    AccumulationOnlyIfValueIsPositive,
1478    AccumulationOnlyIfValueIsNegative,
1479    NonMetricUnits,
1480    AlternativeNonMetricUnits,
1481    ValueAtBaseConditions,
1482    ObisDeclaration,
1483    UpperLimitValue,
1484    LowerLimitValue,
1485    NumberOfExceedsOfUpperLimitValue,
1486    NumberOfExceedsOfLowerLimitValue,
1487    DateOfBeginFirstLowerLimitExceed,
1488    DateOfBeginFirstUpperLimitExceed,
1489    DateOfBeginLastLowerLimitExceed,
1490    DateOfBeginLastUpperLimitExceed,
1491    DateOfEndLastLowerLimitExceed,
1492    DateOfEndLastUpperLimitExceed,
1493    DateOfEndFirstLowerLimitExceed,
1494    DateOfEndFirstUpperLimitExceed,
1495    DurationOfFirstLowerLimitExceed,
1496    DurationOfFirstUpperLimitExceed,
1497    DurationOfLastLowerLimitExceed,
1498    DurationOfLastUpperLimitExceed,
1499    DurationOfFirst,
1500    DurationOfLast,
1501    ValueDuringLowerValueExceed,
1502    ValueDuringUpperValueExceed,
1503    LeakageValues,
1504    OverflowValues,
1505    DateOfBeginLast,
1506    DateOfBeginFirst,
1507    DateOfEndLast,
1508    DateOfEndFirst,
1509    ExtensionOfCombinableOrthogonalVIFE,
1510    FutureValue,
1511    NextVIFEAndDataOfThisBlockAreManufacturerSpecific,
1512    Credit,
1513    Debit,
1514    UniqueMessageIdentificationOrAccessNumber,
1515    DeviceType,
1516    Manufacturer,
1517    ParameterSetIdentification,
1518    ModelOrVersion,
1519    HardwareVersion,
1520    MetrologyFirmwareVersion,
1521    OtherSoftwareVersion,
1522    CustomerLocation,
1523    Customer,
1524    AccessCodeUser,
1525    AccessCodeOperator,
1526    AccessCodeSystemOperator,
1527    AccessCodeDeveloper,
1528    Password,
1529    ErrorFlags,
1530    ErrorMask,
1531    SecurityKey,
1532    DigitalInput,
1533    DigitalOutput,
1534    Binary,
1535    BaudRate,
1536    ResponseDelayTime,
1537    Retry,
1538    RemoteControl,
1539    FirstStorageForCycleStorage,
1540    LastStorageForCycleStorage,
1541    SizeOfStorageBlock,
1542    DescriptionOfTariffAndSubunit,
1543    StorageInterval,
1544    Dimensionless,
1545    DimensionlessHCA,
1546    DataContainerForWmbusProtocol,
1547    PeriodOfNormalDataTransmission,
1548    ResetCounter,
1549    CumulationCounter,
1550    ControlSignal,
1551    DayOfWeek,
1552    WeekNumber,
1553    TimePointOfChangeOfTariff,
1554    StateOfParameterActivation,
1555    SpecialSupplierInformation,
1556    DurationSinceLastCumulation,
1557    OperatingTimeBattery,
1558    DateAndTimeOfBatteryChange,
1559    RFPowerLevel,
1560    DaylightSavingBeginningEndingDeviation,
1561    ListeningWindowManagementData,
1562    RemainingBatteryLifeTime,
1563    NumberOfTimesTheMeterWasStopped,
1564    DataContainerForManufacturerSpecificProtocol,
1565    CurrentlySelectedApplication,
1566    Energy,
1567    ReactiveEnergy,
1568    ApparentEnergy,
1569    CoefficientOfPerformance,
1570    ReactivePower,
1571    Frequency,
1572    ApparentPower,
1573    AtPhaseL1,
1574    AtPhaseL2,
1575    AtPhaseL3,
1576    AtNeutral,
1577    BetweenPhasesL1L2,
1578    BetweenPhasesL2L3,
1579    BetweenPhasesL3L1,
1580    AtQuadrant1,
1581    AtQuadrant2,
1582    AtQuadrant3,
1583    AtQuadrant4,
1584    DeltaBetweenImportAndExport,
1585    AccumulationOfAbsoluteValueBothPositiveAndNegativeContribution,
1586    SecondarySensorMeasurement,
1587    HigherResolutionRegister,
1588    DataPresentedWithTypeC,
1589    DataPresentedWithTypeD,
1590    EndDate,
1591    DirectionFromCommunicationPartnerToMeter,
1592    DirectionFromMeterToCommunicationPartner,
1593    RelativeHumidity,
1594    MoistureLevel,
1595    PhaseUtoU,
1596    PhaseUtoI,
1597    PhaseItoU,
1598    ColdWarmTemperatureLimit,
1599    CumulativeMaximumOfActivePower,
1600    ResultingRatingFactor,
1601    ThermalOutputRatingFactor,
1602    ThermalCouplingRatingFactorOverall,
1603    ThermalCouplingRatingRoomSide,
1604    ThermalCouplingRatingFactorHeatingSide,
1605    LowTemperatureRatingFactor,
1606    DisplayOutputScalingFactor,
1607    ManufacturerSpecific,
1608    OnTime,
1609    OperatingTime,
1610    Volume,
1611    Mass,
1612    Power,
1613    VolumeFlow,
1614    MassFlow,
1615    Pressure,
1616    Voltage,
1617    Current,
1618    FlowTemperature,
1619    ReturnTemperature,
1620    TemperatureDifference,
1621    ExternalTemperature,
1622}
1623
1624#[cfg(feature = "std")]
1625impl fmt::Display for Unit {
1626    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1627        let superscripts = ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
1628        let invalid_superscript = '⁻';
1629        match self.exponent {
1630            1 => write!(f, "{}", self.name),
1631            0..=9 => write!(
1632                f,
1633                "{}{}",
1634                self.name,
1635                superscripts
1636                    .get(self.exponent as usize)
1637                    .unwrap_or(&invalid_superscript)
1638            ),
1639            10..=19 => write!(
1640                f,
1641                "{}{}{}",
1642                self.name,
1643                superscripts.get(1).unwrap_or(&invalid_superscript),
1644                superscripts
1645                    .get(self.exponent as usize - 10)
1646                    .unwrap_or(&invalid_superscript)
1647            ),
1648            x if (-9..0).contains(&x) => {
1649                write!(
1650                    f,
1651                    "{}⁻{}",
1652                    self.name,
1653                    superscripts
1654                        .get((-x) as usize)
1655                        .unwrap_or(&invalid_superscript)
1656                )
1657            }
1658            x if (-19..0).contains(&x) => write!(
1659                f,
1660                "{}⁻{}{}",
1661                self.name,
1662                superscripts.get(1).unwrap_or(&invalid_superscript),
1663                superscripts
1664                    .get((-x) as usize - 10)
1665                    .unwrap_or(&invalid_superscript)
1666            ),
1667            x => write!(f, "{}^{}", self.name, x),
1668        }
1669    }
1670}
1671#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1672#[derive(Debug, Clone, Copy, PartialEq)]
1673#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1674#[non_exhaustive]
1675pub enum UnitName {
1676    Watt,
1677    ReactiveWatt,
1678    ApparentWatt,
1679    Joul,
1680    Kilogram,
1681    Tonne,
1682    Meter,
1683    Feet,
1684    Celsius,
1685    Kelvin,
1686    Bar,
1687    HCA,
1688    Reserved,
1689    WithoutUnits,
1690    Second,
1691    Minute,
1692    Hour,
1693    Day,
1694    Week,
1695    Month,
1696    Year,
1697    Revolution,
1698    Increment,
1699    InputPulseOnChannel0,
1700    OutputPulseOnChannel0,
1701    InputPulseOnChannel1,
1702    OutputPulseOnChannel1,
1703    Liter,
1704    Volt,
1705    Ampere,
1706    LocalMoneyCurrency,
1707    Symbol,
1708    BitTime,
1709    DecibelMilliWatt,
1710    Percent,
1711    Degree,
1712    Hertz,
1713    HCAUnit,
1714    Fahrenheit,
1715    AmericanGallon,
1716    Calorie,
1717}
1718
1719#[cfg(feature = "std")]
1720impl fmt::Display for UnitName {
1721    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1722        match self {
1723            UnitName::Watt => write!(f, "W"),
1724            UnitName::ReactiveWatt => write!(f, "W (reactive)"),
1725            UnitName::ApparentWatt => write!(f, "W (apparent)"),
1726            UnitName::Joul => write!(f, "J"),
1727            UnitName::Kilogram => write!(f, "Kg"),
1728            UnitName::Tonne => write!(f, "t"),
1729            UnitName::Meter => write!(f, "m"),
1730            UnitName::Feet => write!(f, "ft"),
1731            UnitName::Celsius => write!(f, "°C"),
1732            UnitName::Kelvin => write!(f, "°K"),
1733            UnitName::Bar => write!(f, "Bar"),
1734            UnitName::HCA => write!(f, "HCA"),
1735            UnitName::Reserved => write!(f, "Reserved"),
1736            UnitName::WithoutUnits => write!(f, "-"),
1737            UnitName::Second => write!(f, "s"),
1738            UnitName::Minute => write!(f, "min"),
1739            UnitName::Hour => write!(f, "h"),
1740            UnitName::Day => write!(f, "day"),
1741            UnitName::Week => write!(f, "week"),
1742            UnitName::Month => write!(f, "month"),
1743            UnitName::Year => write!(f, "year"),
1744            UnitName::Revolution => write!(f, "revolution"),
1745            UnitName::Increment => write!(f, "increment"),
1746            UnitName::InputPulseOnChannel0 => write!(f, "InputPulseOnChannel0"),
1747            UnitName::OutputPulseOnChannel0 => write!(f, "OutputPulseOnChannel0"),
1748            UnitName::InputPulseOnChannel1 => write!(f, "InputPulseOnChannel1"),
1749            UnitName::OutputPulseOnChannel1 => write!(f, "OutputPulseOnChannel1"),
1750            UnitName::Liter => write!(f, "l"),
1751            UnitName::Volt => write!(f, "V"),
1752            UnitName::Ampere => write!(f, "A"),
1753            UnitName::LocalMoneyCurrency => write!(f, "$ (local)"),
1754            UnitName::Symbol => write!(f, "Symbol"),
1755            UnitName::BitTime => write!(f, "BitTime"),
1756            UnitName::DecibelMilliWatt => write!(f, "dBmW"),
1757            UnitName::Percent => write!(f, "%"),
1758            UnitName::Degree => write!(f, "°"),
1759            UnitName::Hertz => write!(f, "Hz"),
1760            UnitName::HCAUnit => write!(f, "HCAUnit"),
1761            UnitName::Fahrenheit => write!(f, "°F"),
1762            UnitName::AmericanGallon => write!(f, "UsGal"),
1763            UnitName::Calorie => write!(f, "cal"),
1764        }
1765    }
1766}
1767
1768#[cfg(test)]
1769mod tests {
1770    fn assert_information(
1771        actual: super::ValueInformation<'_>,
1772        offset: isize,
1773        scale: isize,
1774        labels: &[super::ValueLabel],
1775        units: &[super::Unit],
1776    ) {
1777        assert_eq!(actual.decimal_offset_exponent, offset);
1778        assert_eq!(actual.decimal_scale_exponent, scale);
1779        assert!(actual.labels().eq(labels.iter().copied()));
1780        assert!(actual.units().eq(units.iter().copied()));
1781    }
1782
1783    #[test]
1784    fn value_information_peeks_remaining_extension_bytes_without_consuming_them() {
1785        use super::{
1786            ValueInformation, ValueInformationBlock, ValueInformationFieldExtensions, ValueLabel,
1787        };
1788        let bytes = [0x80, 0xfd, 0x3e];
1789        let mut extensions = ValueInformationFieldExtensions::new(&bytes).unwrap();
1790        assert_eq!(extensions.next().unwrap().data, 0x80);
1791        let block = ValueInformationBlock::new(0xfd.into(), Some(extensions), None);
1792        let info = ValueInformation::try_from(&block).unwrap();
1793        assert!(info.has_label(ValueLabel::MoistureLevel));
1794        assert_eq!(
1795            info,
1796            ValueInformation::try_from(
1797                &ValueInformationBlock::try_from([0xfd, 0xfd, 0x3e].as_slice()).unwrap()
1798            )
1799            .unwrap()
1800        );
1801        assert_eq!(block.value_information_extension.as_ref().unwrap().len(), 2);
1802        assert_eq!(ValueInformation::try_from(&block).unwrap(), info);
1803    }
1804
1805    #[test]
1806    fn test_single_byte_primary_value_information_parsing() {
1807        use crate::value_information::UnitName;
1808        use crate::value_information::{
1809            Unit, ValueInformation, ValueInformationBlock, ValueInformationField, ValueLabel,
1810        };
1811
1812        /* VIB = 0x13 => m3^3*1e-3 */
1813        let data = [0x13];
1814        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1815        assert_eq!(
1816            result,
1817            ValueInformationBlock {
1818                value_information: ValueInformationField::from(0x13),
1819                value_information_extension: None,
1820                plaintext_vife: None
1821            }
1822        );
1823        assert_eq!(result.get_size(), 1);
1824        assert_information(
1825            ValueInformation::try_from(&result).unwrap(),
1826            0,
1827            -3,
1828            &[ValueLabel::Volume],
1829            &[unit!(Meter ^ 3)],
1830        );
1831
1832        /* VIB = 0x14 => m3^-3*1e-2 */
1833        let data = [0x14];
1834        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1835        assert_eq!(
1836            result,
1837            ValueInformationBlock {
1838                value_information: ValueInformationField::from(0x14),
1839                value_information_extension: None,
1840                plaintext_vife: None
1841            }
1842        );
1843        assert_eq!(result.get_size(), 1);
1844        assert_information(
1845            ValueInformation::try_from(&result).unwrap(),
1846            0,
1847            -2,
1848            &[ValueLabel::Volume],
1849            &[unit!(Meter ^ 3)],
1850        );
1851
1852        /* VIB = 0x15 => m3^3*1e-2 */
1853        let data = [0x15];
1854        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1855        assert_eq!(
1856            result,
1857            ValueInformationBlock {
1858                value_information: ValueInformationField::from(0x15),
1859                value_information_extension: None,
1860                plaintext_vife: None
1861            }
1862        );
1863        assert_eq!(result.get_size(), 1);
1864        assert_information(
1865            ValueInformation::try_from(&result).unwrap(),
1866            0,
1867            -1,
1868            &[ValueLabel::Volume],
1869            &[unit!(Meter ^ 3)],
1870        );
1871
1872        /* VIB = 0x16 => m3^-3*1e-1 */
1873        let data = [0x16];
1874        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1875        assert_eq!(
1876            result,
1877            ValueInformationBlock {
1878                value_information: ValueInformationField::from(0x16),
1879                value_information_extension: None,
1880                plaintext_vife: None
1881            },
1882        );
1883        assert_eq!(result.get_size(), 1);
1884    }
1885
1886    #[test]
1887    fn test_multibyte_primary_value_information() {
1888        use crate::value_information::UnitName;
1889        use crate::value_information::{
1890            Unit, ValueInformation, ValueInformationBlock, ValueInformationField, ValueLabel,
1891        };
1892
1893        /* 1 VIF, 1 - 10 orthogonal VIFE */
1894
1895        /* VIF 0x96 = 0x16 | 0x80  => m3^-3*1e-1 with extension*/
1896        /* VIFE 0x12 => Combinable Orthogonal VIFE meaning "averaged" */
1897        /* VIB = 0x96, 0x12 */
1898        let data = [0x96, 0x12];
1899        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1900        assert_eq!(result.get_size(), 2);
1901        assert_eq!(result.value_information, ValueInformationField::from(0x96));
1902        assert!(ValueInformation::try_from(&result)
1903            .unwrap()
1904            .labels()
1905            .eq([ValueLabel::Volume, ValueLabel::Averaged]));
1906
1907        /* VIF 0x96 = 0x16 | 0x80  => m3^-3*1e-1 with extension*/
1908        /* VIFE 0x92 = 0x12 | 0x80  => Combinable Orthogonal VIFE meaning "averaged" with extension */
1909        /* VIFE 0x20 => Combinable Orthogonal VIFE meaning "per second" */
1910        /* VIB = 0x96, 0x92,0x20 */
1911
1912        let data = [0x96, 0x92, 0x20];
1913        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1914        assert_eq!(result.get_size(), 3);
1915        assert_eq!(result.value_information, ValueInformationField::from(0x96));
1916        assert_information(
1917            ValueInformation::try_from(&result).unwrap(),
1918            0,
1919            0,
1920            &[ValueLabel::Volume, ValueLabel::Averaged],
1921            &[unit!(Meter ^ 3), unit!(Second ^ -1)],
1922        );
1923
1924        /* VIF 0x96 = 0x16 | 0x80  => m3^-3*1e-1 with extension*/
1925        /* VIFE 0x92 = 0x12 | 0x80  => Combinable Orthogonal VIFE meaning "averaged" with extension */
1926        /* VIFE 0xA0= 0x20 | 0x80 => Combinable Orthogonal VIFE meaning "per second" */
1927        /* VIFE 0x2D => Combinable Orthogonal VIFE meaning "per m3". This cancels out the VIF m3, which is useless
1928        but till a valid VIB */
1929        /* VIB = 0x96, 0x92,0xA0, 0x2D */
1930        let data = [0x96, 0x92, 0xA0, 0x2D];
1931        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1932        assert_eq!(result.get_size(), 4);
1933        assert_eq!(result.value_information, ValueInformationField::from(0x96));
1934        assert_information(
1935            ValueInformation::try_from(&result).unwrap(),
1936            0,
1937            0,
1938            &[ValueLabel::Volume, ValueLabel::Averaged],
1939            &[unit!(Meter ^ 3), unit!(Second ^ -1), unit!(Meter ^ -3)],
1940        );
1941    }
1942
1943    #[cfg(not(feature = "plaintext-before-extension"))]
1944    #[test]
1945    fn test_plain_text_vif_norm_conform() {
1946        use crate::value_information::{ValueInformation, ValueLabel};
1947
1948        use crate::value_information::ValueInformationBlock;
1949        // This is the ascii conform method of encoding the VIF
1950        // VIF  VIFE  LEN(3) 'H'   'R'  '%'
1951        // 0xFC, 0x74, 0x03, 0x48, 0x52, 0x25,
1952        // %RH
1953        // Combinable (orthogonal) VIFE-Code extension table
1954        // VIFE = 0x74 => E111 0nnn Multiplicative correction factor for value (not unit): 10nnn–6 => 10^-2
1955        //
1956        // according to the Norm the LEN and ASCII is not part of the VIB however this makes parsing
1957        // cumbersome so we include it in the VIB
1958
1959        let data = [0xFC, 0x74, 0x03, 0x48, 0x52, 0x25];
1960        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1961        assert_eq!(result.get_size(), 6);
1962        assert_eq!(result.value_information.data, 0xFC);
1963        assert_information(
1964            ValueInformation::try_from(&result).unwrap(),
1965            0,
1966            -2,
1967            &[ValueLabel::PlainText],
1968            &[],
1969        );
1970
1971        // This is how the VIF is encoded in the test vectors
1972        // VIF  LEN(3) 'R'   'H'  '%'    VIFE
1973        // 0xFC, 0x03, 0x48, 0x52, 0x25, 0x74,
1974        // %RH
1975        // VIFE = 0x74 => E111 0nnn Multiplicative correction factor for value (not unit): 10nnn–6 => 10^-2
1976        // when not following the norm the LEN and ASCII is part of the VIB
1977        // It is however none norm conform, see the next example which follows
1978        // the MBUS Norm which explicitly states that the VIIFE should be after the VIF
1979        // not aftter the ASCII plain text and its size
1980    }
1981
1982    #[test]
1983    fn test_short_vif_with_vife() {
1984        use crate::value_information::ValueInformationBlock;
1985        let data = [253, 27];
1986        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1987        assert_eq!(result.get_size(), 2);
1988    }
1989
1990    #[test]
1991    fn test_vif_fd_voltage_and_ampere() {
1992        use crate::value_information::UnitName;
1993        use crate::value_information::{ValueInformation, ValueInformationBlock};
1994
1995        // VIF=0xFD VIFE=0x48: Voltage 10^(8-9) = 0.1 V
1996        let vi = ValueInformation::try_from(
1997            &ValueInformationBlock::try_from([0xFD, 0x48].as_slice()).unwrap(),
1998        )
1999        .unwrap();
2000        assert_eq!(vi.first_unit().unwrap().name, UnitName::Volt);
2001        assert_eq!(vi.decimal_scale_exponent, -1);
2002
2003        // VIF=0xFD VIFE=0x59: Ampere 10^(9-12) = 0.001 A
2004        let vi = ValueInformation::try_from(
2005            &ValueInformationBlock::try_from([0xFD, 0x59].as_slice()).unwrap(),
2006        )
2007        .unwrap();
2008        assert_eq!(vi.first_unit().unwrap().name, UnitName::Ampere);
2009        assert_eq!(vi.decimal_scale_exponent, -3);
2010    }
2011
2012    #[test]
2013    fn test_vif_fb_added_codes_and_reserved_fallback() {
2014        use crate::value_information::UnitName;
2015        use crate::value_information::{ValueInformation, ValueInformationBlock, ValueLabel};
2016
2017        // VIF=0xFB VIFE=0x20 (E010 0000): ft³, dec: 0
2018        let vi = ValueInformation::try_from(
2019            &ValueInformationBlock::try_from([0xFB, 0x20].as_slice()).unwrap(),
2020        )
2021        .unwrap();
2022        assert_eq!(vi.first_unit().unwrap().name, UnitName::Feet);
2023        assert_eq!(vi.first_unit().unwrap().exponent, 3);
2024        assert_eq!(vi.decimal_scale_exponent, 0);
2025        assert!(vi.has_label(ValueLabel::Volume));
2026
2027        // VIF=0xFB VIFE=0x23 (E010 0011): Phase angle I-U, 0.1°
2028        let vi = ValueInformation::try_from(
2029            &ValueInformationBlock::try_from([0xFB, 0x23].as_slice()).unwrap(),
2030        )
2031        .unwrap();
2032        assert_eq!(vi.first_unit().unwrap().name, UnitName::Degree);
2033        assert_eq!(vi.decimal_scale_exponent, -1);
2034        assert!(vi.has_label(ValueLabel::PhaseItoU));
2035
2036        // VIF=0xFB VIFE=0x70: °F cold/warm temp limit, 10^-3
2037        let vi = ValueInformation::try_from(
2038            &ValueInformationBlock::try_from([0xFB, 0x70].as_slice()).unwrap(),
2039        )
2040        .unwrap();
2041        assert_eq!(vi.first_unit().unwrap().name, UnitName::Fahrenheit);
2042        assert_eq!(vi.decimal_scale_exponent, -3);
2043
2044        // VIF=0xFB VIFE=0x22 (E010 0010): Reserved — should not error
2045        let vi = ValueInformation::try_from(
2046            &ValueInformationBlock::try_from([0xFB, 0x22].as_slice()).unwrap(),
2047        )
2048        .unwrap();
2049        assert!(vi.has_label(ValueLabel::Reserved));
2050    }
2051
2052    #[test]
2053    fn test_primary_vif_on_time_and_operating_time_labels() {
2054        use crate::value_information::{
2055            UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2056        };
2057
2058        // VIF 0x21 = 0010 0001 = On time (0x20-0x23), nn=01 => minutes
2059        let vi = ValueInformation::try_from(
2060            &ValueInformationBlock::try_from([0x21].as_slice()).unwrap(),
2061        )
2062        .unwrap();
2063        assert!(vi.has_label(ValueLabel::OnTime));
2064        assert_eq!(vi.first_unit().unwrap().name, UnitName::Minute);
2065
2066        // VIF 0x27 = 0010 0111 = Operating time (0x24-0x27), nn=11 => days
2067        let vi = ValueInformation::try_from(
2068            &ValueInformationBlock::try_from([0x27].as_slice()).unwrap(),
2069        )
2070        .unwrap();
2071        assert!(vi.has_label(ValueLabel::OperatingTime));
2072        assert_eq!(vi.first_unit().unwrap().name, UnitName::Day);
2073    }
2074
2075    #[test]
2076    fn test_fb_cumulative_maximum_of_active_power() {
2077        use crate::value_information::{
2078            UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2079        };
2080
2081        // VIF=0xFB VIFE=0x78 (0b0111_1000): CumulativeMaximumOfActivePower, W 10^-3
2082        let vi = ValueInformation::try_from(
2083            &ValueInformationBlock::try_from([0xFB, 0x78].as_slice()).unwrap(),
2084        )
2085        .unwrap();
2086        assert!(vi.has_label(ValueLabel::CumulativeMaximumOfActivePower));
2087        assert_eq!(vi.first_unit().unwrap().name, UnitName::Watt);
2088        assert_eq!(vi.decimal_scale_exponent, -3);
2089    }
2090
2091    #[test]
2092    fn test_fb_humidity_with_combinatorial_scale() {
2093        use crate::value_information::{
2094            UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2095        };
2096
2097        // VIF=0xFB, VIFE=0x9B (0x1B + extension bit), VIFE2=0x74 (multiplicative 10^(4-6) = 10^-2)
2098        // OMS RH01: relative humidity 10^0 %, shifted to 10^-2 by combinatorial.
2099        let vi = ValueInformation::try_from(
2100            &ValueInformationBlock::try_from([0xFB, 0x9B, 0x74].as_slice()).unwrap(),
2101        )
2102        .unwrap();
2103
2104        assert!(vi.has_label(ValueLabel::RelativeHumidity));
2105        assert_eq!(vi.first_unit().unwrap().name, UnitName::Percent);
2106        assert_eq!(vi.decimal_scale_exponent, -2);
2107    }
2108
2109    #[test]
2110    fn test_fd_ampere_with_phase_combinatorial() {
2111        use crate::value_information::{
2112            UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2113        };
2114
2115        // VIF=0xFD, VIFE=0xD9 (0x59 + extension bit = Ampere 10^-3),
2116        // VIFE2=0xFC (combinatorial extension), VIFE3=0x01 (AtPhaseL1)
2117        // OMS CA01: per-phase current.
2118        let vi = ValueInformation::try_from(
2119            &ValueInformationBlock::try_from([0xFD, 0xD9, 0xFC, 0x01].as_slice()).unwrap(),
2120        )
2121        .unwrap();
2122
2123        assert_eq!(vi.first_unit().unwrap().name, UnitName::Ampere);
2124        assert_eq!(vi.decimal_scale_exponent, -3);
2125        assert!(vi.has_label(ValueLabel::AtPhaseL1));
2126    }
2127
2128    #[test]
2129    fn test_primary_vif_combinatorial_not_skipped() {
2130        use crate::value_information::{
2131            UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2132        };
2133
2134        // VIF=0xE5 (0x65 + extension bit = External temperature 10^-2),
2135        // VIFE=0x74 (multiplicative 10^(4-6) = 10^-2)
2136        // First VIFE must NOT be skipped for primary VIFs — total should be 10^-4.
2137        let vi = ValueInformation::try_from(
2138            &ValueInformationBlock::try_from([0xE5, 0x74].as_slice()).unwrap(),
2139        )
2140        .unwrap();
2141
2142        assert!(vi.has_label(ValueLabel::ExternalTemperature));
2143        assert_eq!(vi.first_unit().unwrap().name, UnitName::Celsius);
2144        assert_eq!(vi.decimal_scale_exponent, -4);
2145    }
2146
2147    #[test]
2148    fn test_fd_moisture_level() {
2149        use crate::value_information::{
2150            UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2151        };
2152
2153        // OMS RH03: VIF=0xFD, VIFE1=0xFD (0x7D + extension bit), VIFE2=0x3E
2154        // Moisture Level, % 10^0
2155        let vi = ValueInformation::try_from(
2156            &ValueInformationBlock::try_from([0xFD, 0xFD, 0x3E].as_slice()).unwrap(),
2157        )
2158        .unwrap();
2159
2160        assert!(vi.has_label(ValueLabel::MoistureLevel));
2161        assert_eq!(vi.first_unit().unwrap().name, UnitName::Percent);
2162        assert_eq!(vi.decimal_scale_exponent, 0);
2163    }
2164
2165    #[test]
2166    fn test_vib_struct_layout() {
2167        use crate::value_information::ValueInformationBlock;
2168
2169        // FD extension with orthogonal VIFEs: Ampere 10^-3, AtPhaseL1
2170        // VIF=0xFD, VIFE[0]=0xD9 (true VIF), VIFE[1]=0xFC, VIFE[2]=0x01
2171        let vib = ValueInformationBlock::try_from([0xFD, 0xD9, 0xFC, 0x01].as_slice()).unwrap();
2172
2173        assert_eq!(vib.value_information.data, 0xFD);
2174        assert_eq!(vib.get_size(), 4);
2175        assert!(vib.plaintext_vife.is_none());
2176
2177        let mut ext = vib.value_information_extension.unwrap();
2178        assert_eq!(ext.len(), 3);
2179        assert_eq!(ext.next().unwrap().data, 0xD9);
2180        assert_eq!(ext.next().unwrap().data, 0xFC);
2181        assert_eq!(ext.next().unwrap().data, 0x01);
2182
2183        // Primary VIF with one orthogonal VIFE
2184        // VIF=0x96 (Volume + extension bit), VIFE=0x12 (Averaged)
2185        let vib = ValueInformationBlock::try_from([0x96, 0x12].as_slice()).unwrap();
2186
2187        assert_eq!(vib.value_information.data, 0x96);
2188        assert_eq!(vib.get_size(), 2);
2189
2190        let mut ext = vib.value_information_extension.unwrap();
2191        assert_eq!(ext.len(), 1);
2192        assert_eq!(ext.next().unwrap().data, 0x12);
2193
2194        // Single primary VIF, no extension
2195        let vib = ValueInformationBlock::try_from([0x13].as_slice()).unwrap();
2196
2197        assert_eq!(vib.value_information.data, 0x13);
2198        assert_eq!(vib.get_size(), 1);
2199        assert!(vib.value_information_extension.is_none());
2200    }
2201
2202    #[test]
2203    fn test_combinable_orthogonal_vife_limit_exceed_mappings() {
2204        use crate::value_information::{
2205            UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2206        };
2207
2208        // (vife_byte, expected_label, optional expected unit name)
2209        let cases: &[(u8, ValueLabel, Option<UnitName>)] = &[
2210            (0x40, ValueLabel::LowerLimitValue, None),
2211            (0x48, ValueLabel::UpperLimitValue, None),
2212            (0x41, ValueLabel::NumberOfExceedsOfLowerLimitValue, None),
2213            (0x49, ValueLabel::NumberOfExceedsOfUpperLimitValue, None),
2214            // E100 uf1b: b=Begin/End, f=First/Last, u=Lower/Upper
2215            (0x42, ValueLabel::DateOfBeginFirstLowerLimitExceed, None),
2216            (0x43, ValueLabel::DateOfEndFirstLowerLimitExceed, None),
2217            (0x46, ValueLabel::DateOfBeginLastLowerLimitExceed, None),
2218            (0x47, ValueLabel::DateOfEndLastLowerLimitExceed, None),
2219            (0x4A, ValueLabel::DateOfBeginFirstUpperLimitExceed, None),
2220            (0x4B, ValueLabel::DateOfEndFirstUpperLimitExceed, None),
2221            (0x4E, ValueLabel::DateOfBeginLastUpperLimitExceed, None),
2222            (0x4F, ValueLabel::DateOfEndLastUpperLimitExceed, None),
2223            // Duration of first lower (0x50-0x53)
2224            (
2225                0x50,
2226                ValueLabel::DurationOfFirstLowerLimitExceed,
2227                Some(UnitName::Second),
2228            ),
2229            (
2230                0x51,
2231                ValueLabel::DurationOfFirstLowerLimitExceed,
2232                Some(UnitName::Minute),
2233            ),
2234            (
2235                0x52,
2236                ValueLabel::DurationOfFirstLowerLimitExceed,
2237                Some(UnitName::Hour),
2238            ),
2239            (
2240                0x53,
2241                ValueLabel::DurationOfFirstLowerLimitExceed,
2242                Some(UnitName::Day),
2243            ),
2244            // Duration of last lower (0x54-0x57)
2245            (
2246                0x54,
2247                ValueLabel::DurationOfLastLowerLimitExceed,
2248                Some(UnitName::Second),
2249            ),
2250            (
2251                0x55,
2252                ValueLabel::DurationOfLastLowerLimitExceed,
2253                Some(UnitName::Minute),
2254            ),
2255            (
2256                0x56,
2257                ValueLabel::DurationOfLastLowerLimitExceed,
2258                Some(UnitName::Hour),
2259            ),
2260            (
2261                0x57,
2262                ValueLabel::DurationOfLastLowerLimitExceed,
2263                Some(UnitName::Day),
2264            ),
2265            // Duration of first upper (0x58-0x5B)
2266            (
2267                0x58,
2268                ValueLabel::DurationOfFirstUpperLimitExceed,
2269                Some(UnitName::Second),
2270            ),
2271            (
2272                0x59,
2273                ValueLabel::DurationOfFirstUpperLimitExceed,
2274                Some(UnitName::Minute),
2275            ),
2276            (
2277                0x5A,
2278                ValueLabel::DurationOfFirstUpperLimitExceed,
2279                Some(UnitName::Hour),
2280            ),
2281            (
2282                0x5B,
2283                ValueLabel::DurationOfFirstUpperLimitExceed,
2284                Some(UnitName::Day),
2285            ),
2286            // Duration of last upper (0x5C-0x5F)
2287            (
2288                0x5C,
2289                ValueLabel::DurationOfLastUpperLimitExceed,
2290                Some(UnitName::Second),
2291            ),
2292            (
2293                0x5D,
2294                ValueLabel::DurationOfLastUpperLimitExceed,
2295                Some(UnitName::Minute),
2296            ),
2297            (
2298                0x5E,
2299                ValueLabel::DurationOfLastUpperLimitExceed,
2300                Some(UnitName::Hour),
2301            ),
2302            (
2303                0x5F,
2304                ValueLabel::DurationOfLastUpperLimitExceed,
2305                Some(UnitName::Day),
2306            ),
2307        ];
2308
2309        for (vife_byte, expected_label, expected_unit) in cases {
2310            let data = [0x93, *vife_byte];
2311            let vib = ValueInformationBlock::try_from(data.as_slice()).unwrap();
2312            let vi = ValueInformation::try_from(&vib).unwrap();
2313            assert!(
2314                vi.has_label(*expected_label),
2315                "VIFE 0x{vife_byte:02X}: expected label {expected_label:?}, got {:?}",
2316                vi.labels()
2317            );
2318            if let Some(unit_name) = expected_unit {
2319                assert!(
2320                    vi.units().any(|u| u.name == *unit_name),
2321                    "VIFE 0x{vife_byte:02X}: expected unit {unit_name:?}, got {:?}",
2322                    vi.units()
2323                );
2324            }
2325        }
2326    }
2327
2328    #[test]
2329    fn test_combinable_orthogonal_vife_fc_extension_mappings() {
2330        use crate::value_information::{ValueInformation, ValueInformationBlock, ValueLabel};
2331
2332        let cases: &[(u8, ValueLabel)] = &[
2333            (0x02, ValueLabel::AtPhaseL2),
2334            (0x0D, ValueLabel::AlternativeNonMetricUnits),
2335            (0x0E, ValueLabel::SecondarySensorMeasurement),
2336            (0x13, ValueLabel::EndDate),
2337        ];
2338
2339        for (vife_byte, expected_label) in cases {
2340            let data = [0x93, 0xFC, *vife_byte];
2341            let vib = ValueInformationBlock::try_from(data.as_slice()).unwrap();
2342            let vi = ValueInformation::try_from(&vib).unwrap();
2343            assert!(
2344                vi.has_label(*expected_label),
2345                "FC VIFE 0x{vife_byte:02X}: expected {expected_label:?}, got {:?}",
2346                vi.labels()
2347            );
2348        }
2349    }
2350    #[test]
2351    fn units_exceed_old_capacity() {
2352        use super::*;
2353        let block =
2354            ValueInformationBlock::try_from([0xB8, 0xA8, 0xA8, 0xA8, 0xA8, 0x28].as_slice())
2355                .unwrap();
2356        let vi = ValueInformation::try_from(&block).unwrap();
2357        assert!(vi
2358            .units()
2359            .eq([unit!(Meter ^ 3), unit!(Hour ^ -1)].into_iter().chain(
2360                core::iter::repeat_n([unit!(Increment), unit!(InputPulseOnChannel0 ^ -1)], 5)
2361                    .flatten()
2362            )));
2363        assert_eq!(vi.units().count(), 12);
2364    }
2365
2366    #[test]
2367    fn labels_exceed_old_capacity() {
2368        use super::*;
2369        let block = ValueInformationBlock::try_from(
2370            [
2371                0xFD, 0x9A, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x12,
2372            ]
2373            .as_slice(),
2374        )
2375        .unwrap();
2376        let vi = ValueInformation::try_from(&block).unwrap();
2377        assert!(vi
2378            .labels()
2379            .eq([ValueLabel::DigitalOutput, ValueLabel::Binary]
2380                .into_iter()
2381                .chain(core::iter::repeat_n(ValueLabel::Averaged, 9))));
2382        assert_eq!(vi.labels().count(), 11);
2383    }
2384
2385    #[test]
2386    fn repeated_fc_and_terminal_7c_preserve_table_selection() {
2387        use super::*;
2388        for (bytes, expected) in [
2389            (
2390                &[0x93, 0xFC, 0xFC, 0x01][..],
2391                &[ValueLabel::Volume, ValueLabel::AtPhaseL1][..],
2392            ),
2393            (
2394                &[0x93, 0x7C][..],
2395                &[ValueLabel::Volume, ValueLabel::Reserved][..],
2396            ),
2397            (
2398                &[0x93, 0xFC, 0x81, 0x12][..],
2399                &[
2400                    ValueLabel::Volume,
2401                    ValueLabel::AtPhaseL1,
2402                    ValueLabel::Averaged,
2403                ][..],
2404            ),
2405        ] {
2406            let block = ValueInformationBlock::try_from(bytes).unwrap();
2407            assert!(ValueInformation::try_from(&block)
2408                .unwrap()
2409                .labels()
2410                .eq(expected.iter().copied()));
2411        }
2412    }
2413
2414    #[test]
2415    fn main_extension_subcode_is_also_orthogonal() {
2416        use super::*;
2417        let block = ValueInformationBlock::try_from([0xFD, 0xFD, 0x3E].as_slice()).unwrap();
2418        let vi = ValueInformation::try_from(&block).unwrap();
2419        assert!(vi
2420            .labels()
2421            .eq([ValueLabel::MoistureLevel, ValueLabel::ValueAtBaseConditions]));
2422        assert!(vi.units().eq([unit!(Percent)]));
2423    }
2424
2425    #[test]
2426    fn equality_is_semantic_and_iterators_outlive_the_view() {
2427        use super::*;
2428        let short = ValueInformationBlock::try_from([0x13].as_slice()).unwrap();
2429        // 0x76 contributes zero scale and no labels or units.
2430        let equivalent = ValueInformationBlock::try_from([0x93, 0x76].as_slice()).unwrap();
2431        assert_eq!(
2432            ValueInformation::try_from(&short).unwrap(),
2433            ValueInformation::try_from(&equivalent).unwrap()
2434        );
2435        let (mut labels, mut units) = {
2436            let vi = ValueInformation::try_from(&short).unwrap();
2437            (vi.labels(), vi.units())
2438        };
2439        assert_eq!(labels.next(), Some(ValueLabel::Volume));
2440        assert_eq!(units.next(), Some(unit!(Meter ^ 3)));
2441        assert!(labels.clone().eq(labels));
2442        assert!(units.clone().eq(units));
2443    }
2444
2445    #[test]
2446    fn manufacturer_escape_stops_standard_vife_decoding() {
2447        use super::*;
2448        // ABB B21 energy record: VIF 0x04 (energy, x10^1) followed by the
2449        // manufacturer escape 0xFF; 0xF2 and 0x00 are vendor data and must not
2450        // be read as the standard multiplicative correction 0x70..=0x77.
2451        let block = ValueInformationBlock::try_from([0x84, 0xFF, 0xF2, 0x00].as_slice()).unwrap();
2452        let vi = ValueInformation::try_from(&block).unwrap();
2453        assert_eq!(vi.decimal_scale_exponent, 1);
2454        let mut labels = vi.labels();
2455        assert_eq!(labels.next(), Some(ValueLabel::Energy));
2456        assert_eq!(
2457            labels.next(),
2458            Some(ValueLabel::NextVIFEAndDataOfThisBlockAreManufacturerSpecific)
2459        );
2460        assert_eq!(labels.next(), None);
2461    }
2462
2463    #[test]
2464    fn exponents_accumulate_and_iterators_clone_mid_chain() {
2465        use super::*;
2466        let block =
2467            ValueInformationBlock::try_from([0x93, 0x92, 0xA8, 0xF5, 0x78].as_slice()).unwrap();
2468        let vi = ValueInformation::try_from(&block).unwrap();
2469        assert_eq!(vi.decimal_scale_exponent, -4);
2470        assert_eq!(vi.decimal_offset_exponent, -3);
2471        let mut units = vi.units();
2472        assert_eq!(units.next(), Some(unit!(Meter ^ 3)));
2473        assert_eq!(units.next(), Some(unit!(Increment)));
2474        assert!(units.clone().eq(units));
2475        let mut labels = vi.labels();
2476        assert_eq!(labels.next(), Some(ValueLabel::Volume));
2477        assert!(labels.clone().eq(labels));
2478    }
2479
2480    #[test]
2481    fn head_table_and_missing_extensions() {
2482        use super::*;
2483        let cases = [
2484            (
2485                0x13,
2486                None,
2487                None,
2488                VifInfo {
2489                    labels: &[ValueLabel::Volume],
2490                    units: &[unit!(Meter ^ 3)],
2491                    scale: -3,
2492                    ..VifInfo::EMPTY
2493                },
2494            ),
2495            (
2496                0xFD,
2497                Some(0x1A),
2498                None,
2499                labels!(ValueLabel::DigitalOutput, ValueLabel::Binary),
2500            ),
2501            (
2502                0xFB,
2503                Some(0x1A),
2504                None,
2505                VifInfo {
2506                    labels: &[ValueLabel::RelativeHumidity],
2507                    units: &[unit!(Percent)],
2508                    scale: -1,
2509                    ..VifInfo::EMPTY
2510                },
2511            ),
2512            (
2513                0xFD,
2514                Some(0x7D),
2515                Some(0x3E),
2516                VifInfo {
2517                    labels: &[ValueLabel::MoistureLevel],
2518                    units: &[unit!(Percent)],
2519                    ..VifInfo::EMPTY
2520                },
2521            ),
2522            (0x7C, None, None, labels!(ValueLabel::PlainText)),
2523            (0x7F, None, None, labels!(ValueLabel::ManufacturerSpecific)),
2524        ];
2525        for (vif, first, second, expected) in cases {
2526            assert_eq!(head_vif_info(vif.into(), first, second).unwrap(), expected);
2527        }
2528        assert!(matches!(
2529            head_vif_info(0x6F.into(), None, None),
2530            Err(DataInformationError::Unimplemented { .. })
2531        ));
2532        for vif in [0xFD, 0xFB] {
2533            let mut block = ValueInformationBlock {
2534                value_information: vif.into(),
2535                value_information_extension: None,
2536                plaintext_vife: None,
2537            };
2538            assert!(ValueInformation::try_from(&block)
2539                .unwrap()
2540                .labels()
2541                .next()
2542                .is_none());
2543            block.value_information_extension = Some(ValueInformationFieldExtensions(&[]));
2544            assert_eq!(
2545                ValueInformation::try_from(&block),
2546                Err(DataInformationError::DataTooShort)
2547            );
2548        }
2549    }
2550}