Skip to main content

m_bus_application_layer/
value_information.rs

1#[cfg(feature = "std")]
2use std::fmt;
3
4use super::data_information::DataInformationError;
5use arrayvec::ArrayVec;
6
7const MAX_VIFE_RECORDS: usize = 10;
8
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[derive(Debug, PartialEq, Copy, Clone)]
11#[cfg_attr(feature = "defmt", derive(defmt::Format))]
12pub struct Unit {
13    pub name: UnitName,
14    pub exponent: i32,
15}
16macro_rules! unit {
17    ($name:ident) => {
18        Unit {
19            name: UnitName::$name,
20            exponent: 1,
21        }
22    };
23    ($name:ident ^ $exponent:literal) => {
24        Unit {
25            name: UnitName::$name,
26            exponent: $exponent,
27        }
28    };
29}
30
31impl TryFrom<&[u8]> for ValueInformationBlock {
32    type Error = DataInformationError;
33
34    fn try_from(data: &[u8]) -> Result<Self, DataInformationError> {
35        let mut vife = ArrayVec::<ValueInformationFieldExtension, MAX_VIFE_RECORDS>::new();
36        let vif =
37            ValueInformationField::from(*data.first().ok_or(DataInformationError::DataTooShort)?);
38        let mut plaintext_vife: Option<ArrayVec<char, 9>> = None;
39
40        #[cfg(not(feature = "plaintext-before-extension"))]
41        let standard_plaintex_vib = true;
42        #[cfg(feature = "plaintext-before-extension")]
43        let standard_plaintex_vib = false;
44
45        if !standard_plaintex_vib && vif.value_information_contains_ascii() {
46            plaintext_vife = Some(extract_plaintext_vife(
47                data.get(1..).ok_or(DataInformationError::DataTooShort)?,
48            )?);
49        }
50
51        if vif.has_extension() {
52            // When the plaintext VIF precedes the extensions, the VIFE chain
53            // starts after the ASCII length byte and string, not at offset 1.
54            let mut offset = match &plaintext_vife {
55                Some(chars) if !standard_plaintex_vib => 1 + 1 + chars.len(),
56                _ => 1,
57            };
58            while offset < data.len() {
59                let vife_data = *data.get(offset).ok_or(DataInformationError::DataTooShort)?;
60                let current_vife = ValueInformationFieldExtension { data: vife_data };
61                let has_extension = current_vife.has_extension();
62                vife.push(current_vife);
63                offset += 1;
64                if !has_extension {
65                    break;
66                }
67                if vife.len() > MAX_VIFE_RECORDS {
68                    return Err(DataInformationError::InvalidValueInformation);
69                }
70            }
71            if standard_plaintex_vib && vif.value_information_contains_ascii() {
72                plaintext_vife = Some(extract_plaintext_vife(
73                    data.get(offset..)
74                        .ok_or(DataInformationError::DataTooShort)?,
75                )?);
76            }
77        }
78
79        Ok(Self {
80            value_information: vif,
81            value_information_extension: if vife.is_empty() { None } else { Some(vife) },
82            plaintext_vife,
83        })
84    }
85}
86
87fn extract_plaintext_vife(data: &[u8]) -> Result<ArrayVec<char, 9>, DataInformationError> {
88    let ascii_length = *data.first().ok_or(DataInformationError::DataTooShort)? as usize;
89    let mut ascii = ArrayVec::<char, 9>::new();
90    for item in data
91        .get(1..=ascii_length)
92        .ok_or(DataInformationError::DataTooShort)?
93    {
94        ascii.push(*item as char);
95    }
96    Ok(ascii)
97}
98
99#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
100#[derive(Debug, PartialEq, Clone)]
101pub struct ValueInformationBlock {
102    pub value_information: ValueInformationField,
103    pub value_information_extension:
104        Option<ArrayVec<ValueInformationFieldExtension, MAX_VIFE_RECORDS>>,
105    pub plaintext_vife: Option<ArrayVec<char, 9>>,
106}
107
108#[cfg(feature = "defmt")]
109impl defmt::Format for ValueInformationBlock {
110    fn format(&self, f: defmt::Formatter) {
111        defmt::write!(
112            f,
113            "ValueInformationBlock{{ value_information: {:?}",
114            self.value_information
115        );
116        if let Some(ext) = &self.value_information_extension {
117            defmt::write!(f, ", value_information_extension: [");
118            for (i, vife) in ext.iter().enumerate() {
119                if i != 0 {
120                    defmt::write!(f, ", ");
121                }
122                defmt::write!(f, "{:?}", vife);
123            }
124            defmt::write!(f, "]");
125        }
126        if let Some(text) = &self.plaintext_vife {
127            defmt::write!(f, ", plaintext_vife: ");
128            for c in text {
129                defmt::write!(f, "{}", c);
130            }
131        }
132        defmt::write!(f, " }}");
133    }
134}
135#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136#[derive(Debug, PartialEq, Clone)]
137#[cfg_attr(feature = "defmt", derive(defmt::Format))]
138pub struct ValueInformationField {
139    pub data: u8,
140}
141
142impl ValueInformationField {
143    const fn value_information_contains_ascii(&self) -> bool {
144        self.data == 0x7C || self.data == 0xFC
145    }
146}
147
148#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
149#[derive(Debug, PartialEq, Clone)]
150#[cfg_attr(feature = "defmt", derive(defmt::Format))]
151pub struct ValueInformationFieldExtension {
152    pub data: u8,
153}
154
155impl From<&ValueInformationField> for ValueInformationCoding {
156    fn from(value_information: &ValueInformationField) -> Self {
157        match value_information.data {
158            0x00..=0x7B | 0x80..=0xFA => Self::Primary,
159            0x7C | 0xFC => Self::PlainText,
160            0xFD => Self::MainVIFExtension,
161            0xFB => Self::AlternateVIFExtension,
162            0x7E => Self::ManufacturerSpecific,
163            0xFE => Self::ManufacturerSpecific,
164            0x7F => Self::ManufacturerSpecific,
165            0xFF => Self::ManufacturerSpecific,
166            _ => unreachable!("Invalid value information: {:X}", value_information.data),
167        }
168    }
169}
170
171impl ValueInformationField {
172    const fn has_extension(&self) -> bool {
173        self.data & 0x80 != 0
174    }
175}
176
177impl ValueInformationFieldExtension {
178    const fn has_extension(&self) -> bool {
179        self.data & 0x80 != 0
180    }
181}
182
183#[derive(Debug, Clone, Copy, PartialEq)]
184#[cfg_attr(feature = "defmt", derive(defmt::Format))]
185#[non_exhaustive]
186pub enum ValueInformationCoding {
187    Primary,
188    PlainText,
189    MainVIFExtension,
190    AlternateVIFExtension,
191    ManufacturerSpecific,
192}
193
194impl ValueInformationBlock {
195    #[must_use]
196    pub const fn get_size(&self) -> usize {
197        let mut size = 1;
198        if let Some(vife) = &self.value_information_extension {
199            size += vife.len();
200        }
201        if let Some(plaintext_vife) = &self.plaintext_vife {
202            // 1 byte for the length of the ASCII string
203            size += plaintext_vife.len() + 1;
204        }
205        size
206    }
207}
208
209impl TryFrom<&ValueInformationBlock> for ValueInformation {
210    type Error = DataInformationError;
211
212    fn try_from(
213        value_information_block: &ValueInformationBlock,
214    ) -> Result<Self, DataInformationError> {
215        let mut units = ArrayVec::<Unit, 10>::new();
216        let mut labels = ArrayVec::<ValueLabel, 10>::new();
217        let mut decimal_scale_exponent: isize = 0;
218        let mut decimal_offset_exponent = 0;
219        let vife_slice = match &value_information_block.value_information_extension {
220            Some(v) => v.as_slice(),
221            None => &[],
222        };
223        match ValueInformationCoding::from(&value_information_block.value_information) {
224            ValueInformationCoding::Primary => {
225                match value_information_block.value_information.data & 0x7F {
226                    0x00..=0x07 => {
227                        units.push(unit!(Watt));
228                        units.push(unit!(Hour));
229                        labels.push(ValueLabel::Energy);
230                        decimal_scale_exponent =
231                            (value_information_block.value_information.data & 0b111) as isize - 3;
232                    }
233                    0x08..=0x0F => {
234                        units.push(unit!(Joul));
235                        labels.push(ValueLabel::Energy);
236                        decimal_scale_exponent =
237                            (value_information_block.value_information.data & 0b111) as isize;
238                    }
239                    0x10..=0x17 => {
240                        units.push(unit!(Meter ^ 3));
241                        labels.push(ValueLabel::Volume);
242                        decimal_scale_exponent =
243                            (value_information_block.value_information.data & 0b111) as isize - 6;
244                    }
245                    0x18..=0x1F => {
246                        units.push(unit!(Kilogram));
247                        labels.push(ValueLabel::Mass);
248                        decimal_scale_exponent =
249                            (value_information_block.value_information.data & 0b111) as isize - 3;
250                    }
251                    0x20..=0x23 => {
252                        labels.push(ValueLabel::OnTime);
253                        match value_information_block.value_information.data & 0x03 {
254                            0x00 => units.push(unit!(Second)),
255                            0x01 => units.push(unit!(Minute)),
256                            0x02 => units.push(unit!(Hour)),
257                            0x03 => units.push(unit!(Day)),
258                            _ => unreachable!(),
259                        }
260                    }
261                    0x24..=0x27 => {
262                        labels.push(ValueLabel::OperatingTime);
263                        match value_information_block.value_information.data & 0x03 {
264                            0x00 => units.push(unit!(Second)),
265                            0x01 => units.push(unit!(Minute)),
266                            0x02 => units.push(unit!(Hour)),
267                            0x03 => units.push(unit!(Day)),
268                            _ => unreachable!(),
269                        }
270                    }
271                    0x28..=0x2F => {
272                        units.push(unit!(Watt));
273                        labels.push(ValueLabel::Power);
274                        decimal_scale_exponent +=
275                            (value_information_block.value_information.data & 0b111) as isize - 3;
276                    }
277                    0x30..=0x37 => {
278                        units.push(unit!(Joul));
279                        units.push(unit!(Hour ^ -1));
280                        labels.push(ValueLabel::Power);
281                        decimal_scale_exponent +=
282                            (value_information_block.value_information.data & 0b111) as isize;
283                    }
284                    0x38..=0x3F => {
285                        units.push(unit!(Meter ^ 3));
286                        units.push(unit!(Hour ^ -1));
287                        labels.push(ValueLabel::VolumeFlow);
288                        decimal_scale_exponent +=
289                            (value_information_block.value_information.data & 0b111) as isize - 6;
290                    }
291                    0x40..=0x47 => {
292                        units.push(unit!(Meter ^ 3));
293                        units.push(unit!(Minute ^ -1));
294                        labels.push(ValueLabel::VolumeFlow);
295                        decimal_scale_exponent +=
296                            (value_information_block.value_information.data & 0b111) as isize - 7;
297                    }
298                    0x48..=0x4F => {
299                        units.push(unit!(Meter ^ 3));
300                        units.push(unit!(Second ^ -1));
301                        labels.push(ValueLabel::VolumeFlow);
302                        decimal_scale_exponent +=
303                            (value_information_block.value_information.data & 0b111) as isize - 9;
304                    }
305                    0x50..=0x57 => {
306                        units.push(unit!(Kilogram));
307                        units.push(unit!(Hour ^ -1));
308                        labels.push(ValueLabel::MassFlow);
309                        decimal_scale_exponent +=
310                            (value_information_block.value_information.data & 0b111) as isize - 3;
311                    }
312                    0x58..=0x5B => {
313                        units.push(unit!(Celsius));
314                        labels.push(ValueLabel::FlowTemperature);
315                        decimal_scale_exponent +=
316                            (value_information_block.value_information.data & 0b11) as isize - 3;
317                    }
318                    0x5C..=0x5F => {
319                        units.push(unit!(Celsius));
320                        labels.push(ValueLabel::ReturnTemperature);
321                        decimal_scale_exponent +=
322                            (value_information_block.value_information.data & 0b11) as isize - 3;
323                    }
324                    0x60..=0x63 => {
325                        units.push(unit!(Kelvin));
326                        labels.push(ValueLabel::TemperatureDifference);
327                        decimal_scale_exponent +=
328                            (value_information_block.value_information.data & 0b11) as isize - 3;
329                    }
330                    0x64..=0x67 => {
331                        units.push(unit!(Celsius));
332                        labels.push(ValueLabel::ExternalTemperature);
333                        decimal_scale_exponent +=
334                            (value_information_block.value_information.data & 0b11) as isize - 3;
335                    }
336                    0x68..=0x6B => {
337                        units.push(unit!(Bar));
338                        labels.push(ValueLabel::Pressure);
339                        decimal_scale_exponent +=
340                            (value_information_block.value_information.data & 0b11) as isize - 3;
341                    }
342                    0x6C => labels.push(ValueLabel::Date),
343                    0x6D => labels.push(ValueLabel::DateTime),
344                    0x6E => labels.push(ValueLabel::DimensionlessHCA),
345                    0x70..=0x73 => labels.push(ValueLabel::AveragingDuration),
346                    0x74..=0x77 => labels.push(ValueLabel::ActualityDuration),
347                    0x78 => labels.push(ValueLabel::FabricationNumber),
348                    0x79 => labels.push(ValueLabel::EnhancedIdentification),
349                    0x7A => labels.push(ValueLabel::Address),
350                    0x7B => {}
351
352                    _ => {
353                        return Err(DataInformationError::Unimplemented {
354                            feature: "Primary value information unit codes (partial)",
355                        })
356                    }
357                };
358                consume_orthhogonal_vife(
359                    vife_slice,
360                    &mut labels,
361                    &mut units,
362                    &mut decimal_scale_exponent,
363                    &mut decimal_offset_exponent,
364                );
365            }
366            ValueInformationCoding::MainVIFExtension => {
367                let first_vife_data = vife_slice
368                    .first()
369                    .ok_or(DataInformationError::DataTooShort)?
370                    .data;
371                let second_vife_data = vife_slice.get(1).map(|v| v.data);
372                match first_vife_data & 0x7F {
373                    0x00..=0x03 => {
374                        units.push(unit!(LocalMoneyCurrency));
375                        labels.push(ValueLabel::Credit);
376                        decimal_scale_exponent = (first_vife_data & 0b11) as isize - 3;
377                    }
378                    0x04..=0x07 => {
379                        units.push(unit!(LocalMoneyCurrency));
380                        labels.push(ValueLabel::Debit);
381                        decimal_scale_exponent = (first_vife_data & 0b11) as isize - 3;
382                    }
383                    0x08 => labels.push(ValueLabel::UniqueMessageIdentificationOrAccessNumber),
384                    0x09 => labels.push(ValueLabel::DeviceType),
385                    0x0A => labels.push(ValueLabel::Manufacturer),
386                    0x0B => labels.push(ValueLabel::ParameterSetIdentification),
387                    0x0C => labels.push(ValueLabel::ModelOrVersion),
388                    0x0D => labels.push(ValueLabel::HardwareVersion),
389                    0x0E => labels.push(ValueLabel::MetrologyFirmwareVersion),
390                    0x0F => labels.push(ValueLabel::OtherSoftwareVersion),
391                    0x10 => labels.push(ValueLabel::CustomerLocation),
392                    0x11 => labels.push(ValueLabel::Customer),
393                    0x12 => labels.push(ValueLabel::AccessCodeUser),
394                    0x13 => labels.push(ValueLabel::AccessCodeOperator),
395                    0x14 => labels.push(ValueLabel::AccessCodeSystemOperator),
396                    0x15 => labels.push(ValueLabel::AccessCodeDeveloper),
397                    0x16 => labels.push(ValueLabel::Password),
398                    0x17 => labels.push(ValueLabel::ErrorFlags),
399                    0x18 => labels.push(ValueLabel::ErrorMask),
400                    0x19 => labels.push(ValueLabel::SecurityKey),
401                    0x1A => {
402                        labels.push(ValueLabel::DigitalOutput);
403                        labels.push(ValueLabel::Binary);
404                    }
405                    0x1B => {
406                        labels.push(ValueLabel::DigitalInput);
407                        labels.push(ValueLabel::Binary);
408                    }
409                    0x1C => {
410                        units.push(unit!(Symbol));
411                        units.push(unit!(Second ^ -1));
412                        labels.push(ValueLabel::BaudRate);
413                    }
414                    0x1D => {
415                        units.push(unit!(BitTime));
416                        labels.push(ValueLabel::ResponseDelayTime);
417                    }
418                    0x1E => labels.push(ValueLabel::Retry),
419                    0x1F => labels.push(ValueLabel::RemoteControl),
420                    0x20 => labels.push(ValueLabel::FirstStorageForCycleStorage),
421                    0x21 => labels.push(ValueLabel::LastStorageForCycleStorage),
422                    0x22 => labels.push(ValueLabel::SizeOfStorageBlock),
423                    0x23 => labels.push(ValueLabel::DescriptionOfTariffAndSubunit),
424                    0x24 => {
425                        units.push(unit!(Second));
426                        labels.push(ValueLabel::StorageInterval);
427                    }
428                    0x25 => {
429                        units.push(unit!(Minute));
430                        labels.push(ValueLabel::StorageInterval);
431                    }
432                    0x26 => {
433                        units.push(unit!(Hour));
434                        labels.push(ValueLabel::StorageInterval);
435                    }
436                    0x27 => {
437                        units.push(unit!(Day));
438                        labels.push(ValueLabel::StorageInterval);
439                    }
440                    0x28 => {
441                        units.push(unit!(Month));
442                        labels.push(ValueLabel::StorageInterval);
443                    }
444                    0x29 => {
445                        units.push(unit!(Year));
446                        labels.push(ValueLabel::StorageInterval);
447                    }
448                    0x30 => labels.push(ValueLabel::DimensionlessHCA),
449                    0x31 => labels.push(ValueLabel::DataContainerForWmbusProtocol),
450                    0x32 => {
451                        units.push(unit!(Second));
452                        labels.push(ValueLabel::PeriodOfNormalDataTransmission);
453                    }
454                    0x33 => {
455                        units.push(unit!(Meter));
456                        labels.push(ValueLabel::PeriodOfNormalDataTransmission);
457                    }
458                    0x34 => {
459                        units.push(unit!(Hour));
460                        labels.push(ValueLabel::PeriodOfNormalDataTransmission);
461                    }
462                    0x35 => {
463                        units.push(unit!(Day));
464                        labels.push(ValueLabel::PeriodOfNormalDataTransmission);
465                    }
466                    0x3A => labels.push(ValueLabel::Dimensionless),
467                    0x40..=0x4F => {
468                        units.push(unit!(Volt));
469                        labels.push(ValueLabel::Voltage);
470                        decimal_scale_exponent = (first_vife_data & 0b1111) as isize - 9;
471                    }
472                    0x50..=0x5F => {
473                        units.push(unit!(Ampere));
474                        labels.push(ValueLabel::Current);
475                        decimal_scale_exponent = (first_vife_data & 0b1111) as isize - 12;
476                    }
477                    0x60 => labels.push(ValueLabel::ResetCounter),
478                    0x61 => labels.push(ValueLabel::CumulationCounter),
479                    0x62 => labels.push(ValueLabel::ControlSignal),
480                    0x63 => labels.push(ValueLabel::DayOfWeek),
481                    0x64 => labels.push(ValueLabel::WeekNumber),
482                    0x65 => labels.push(ValueLabel::TimePointOfChangeOfTariff),
483                    0x66 => labels.push(ValueLabel::StateOfParameterActivation),
484                    0x67 => labels.push(ValueLabel::SpecialSupplierInformation),
485                    0x68 => {
486                        units.push(unit!(Hour));
487                        labels.push(ValueLabel::DurationSinceLastCumulation);
488                    }
489                    0x69 => {
490                        units.push(unit!(Day));
491                        labels.push(ValueLabel::DurationSinceLastCumulation);
492                    }
493                    0x6A => {
494                        units.push(unit!(Month));
495                        labels.push(ValueLabel::DurationSinceLastCumulation);
496                    }
497                    0x6B => {
498                        units.push(unit!(Year));
499                        labels.push(ValueLabel::DurationSinceLastCumulation);
500                    }
501                    0x6C => {
502                        units.push(unit!(Hour));
503                        labels.push(ValueLabel::OperatingTimeBattery);
504                    }
505                    0x6D => {
506                        units.push(unit!(Day));
507                        labels.push(ValueLabel::OperatingTimeBattery);
508                    }
509                    0x6E => {
510                        units.push(unit!(Month));
511                        labels.push(ValueLabel::OperatingTimeBattery);
512                    }
513                    0x6F => {
514                        units.push(unit!(Hour));
515                        labels.push(ValueLabel::OperatingTimeBattery);
516                    }
517                    0x70 => {
518                        units.push(unit!(Second));
519                        labels.push(ValueLabel::DateAndTimeOfBatteryChange);
520                    }
521                    0x71 => {
522                        units.push(unit!(DecibelMilliWatt));
523                        labels.push(ValueLabel::RFPowerLevel);
524                    }
525                    0x72 => labels.push(ValueLabel::DaylightSavingBeginningEndingDeviation),
526                    0x73 => labels.push(ValueLabel::ListeningWindowManagementData),
527                    0x74 => labels.push(ValueLabel::RemainingBatteryLifeTime),
528                    0x75 => labels.push(ValueLabel::NumberOfTimesTheMeterWasStopped),
529                    0x76 => labels.push(ValueLabel::DataContainerForManufacturerSpecificProtocol),
530                    0x7D => match second_vife_data.map(|s| s & 0x7F) {
531                        Some(0x00) => labels.push(ValueLabel::CurrentlySelectedApplication),
532                        Some(0x02) => {
533                            units.push(unit!(Month));
534                            labels.push(ValueLabel::RemainingBatteryLifeTime);
535                        }
536                        Some(0x03) => {
537                            units.push(unit!(Year));
538                            labels.push(ValueLabel::RemainingBatteryLifeTime);
539                        }
540                        Some(0x3E) => {
541                            units.push(unit!(Percent));
542                            labels.push(ValueLabel::MoistureLevel);
543                        }
544                        _ => labels.push(ValueLabel::Reserved),
545                    },
546                    _ => labels.push(ValueLabel::Reserved),
547                }
548                // Skip vife_slice[0] — it's the true VIF (already consumed above)
549                consume_orthhogonal_vife(
550                    vife_slice.get(1..).unwrap_or(&[]),
551                    &mut labels,
552                    &mut units,
553                    &mut decimal_scale_exponent,
554                    &mut decimal_offset_exponent,
555                );
556            }
557            ValueInformationCoding::AlternateVIFExtension => {
558                use UnitName::*;
559                use ValueLabel::*;
560                let mk_unit = |name, exponent| Unit { name, exponent };
561                macro_rules! populate {
562                    (@trd) => {};
563                    (@trd , $label:expr) => {{ labels.push($label); }};
564                    (@snd dec: $decimal:literal $($rem:tt)*) => {{
565                        decimal_scale_exponent = $decimal;
566                        populate!(@trd $($rem)*);
567                    }};
568                    ($name:ident / h, $exponent:expr, $($rem:tt)*) => {{
569                        units.push(mk_unit($name, $exponent));
570                        units.push(mk_unit(Hour, -1));
571                        populate!(@snd $($rem)*)
572                    }};
573                    ($name:ident / min, $exponent:expr, $($rem:tt)*) => {{
574                        units.push(mk_unit($name, $exponent));
575                        units.push(mk_unit(Minute, -1));
576                        populate!(@snd $($rem)*)
577                    }};
578                    ($name:ident * h, $exponent:expr, $($rem:tt)*) => {{
579                        units.push(mk_unit($name, $exponent));
580                        units.push(mk_unit(Hour, 1));
581                        populate!(@snd $($rem)*)
582                    }};
583                    ($name:ident, $exponent:expr, $($rem:tt)*) => {{
584                        units.push(mk_unit($name, $exponent));
585                        populate!(@snd $($rem)*)
586                    }};
587                }
588                let first_vife_data = vife_slice
589                    .first()
590                    .ok_or(DataInformationError::DataTooShort)?
591                    .data;
592                match first_vife_data & 0x7F {
593                    0b0 => populate!(Watt / h, 3, dec: 5, Energy),
594                    0b000_0001 => populate!(Watt / h, 3, dec: 6, Energy),
595                    0b000_0010 => populate!(ReactiveWatt * h, 1, dec: 3, ReactiveEnergy),
596                    0b000_0011 => populate!(ReactiveWatt * h, 1, dec: 4, ReactiveEnergy),
597                    0b000_0100 => populate!(ApparentWatt * h, 1, dec: 3, ApparentEnergy),
598                    0b000_0101 => populate!(ApparentWatt * h, 1, dec: 4, ApparentEnergy),
599                    0b000_0110 => {
600                        labels.push(CoefficientOfPerformance);
601                        decimal_scale_exponent = -1;
602                    }
603                    0b000_1000 => populate!(Joul, 1, dec: 8, Energy),
604                    0b000_1001 => populate!(Joul, 1, dec: 9, Energy),
605                    0b000_1100 => populate!(Calorie, 1, dec: 5, Energy),
606                    0b000_1101 => populate!(Calorie, 1, dec: 6, Energy),
607                    0b000_1110 => populate!(Calorie, 1, dec: 7, Energy),
608                    0b000_1111 => populate!(Calorie, 1, dec: 8, Energy),
609                    0b001_0000 => populate!(Meter, 3, dec: 2, Volume),
610                    0b001_0001 => populate!(Meter, 3, dec: 3, Volume),
611                    0b001_0100 => populate!(ReactiveWatt, 1, dec: 0, ReactivePower),
612                    0b001_0101 => populate!(ReactiveWatt, 1, dec: 1, ReactivePower),
613                    0b001_0110 => populate!(ReactiveWatt, 1, dec: 2, ReactivePower),
614                    0b001_0111 => populate!(ReactiveWatt, 1, dec: 3, ReactivePower),
615                    0b001_1000 => populate!(Tonne, 1, dec: 2, Mass),
616                    0b001_1001 => populate!(Tonne, 1, dec: 3, Mass),
617                    0b001_1010 => populate!(Percent, 1, dec: -1, RelativeHumidity),
618                    0b001_1011 => populate!(Percent, 1, dec: 0, RelativeHumidity),
619                    0b010_0000 => populate!(Feet, 3, dec: 0, Volume),
620                    0b010_0001 => populate!(Feet, 3, dec: -1, Volume),
621                    0b010_0011 => populate!(Degree, 1, dec: -1, PhaseItoU),
622                    0b010_1000 => populate!(Watt, 1, dec: 5, Power),
623                    0b010_1001 => populate!(Watt, 1, dec: 6, Power),
624                    0b010_1010 => populate!(Degree, 1, dec: -1, PhaseUtoU),
625                    0b010_1011 => populate!(Degree, 1, dec: -1, PhaseUtoI),
626                    0b010_1100 => populate!(Hertz, 1, dec: -3, Frequency),
627                    0b010_1101 => populate!(Hertz, 1, dec: -2, Frequency),
628                    0b010_1110 => populate!(Hertz, 1, dec: -1, Frequency),
629                    0b010_1111 => populate!(Hertz, 1, dec: 0, Frequency),
630                    0b011_0000 => populate!(Joul / h, 1, dec: 8, Power),
631                    0b011_0001 => populate!(Joul / h, 1, dec: 9, Power),
632                    0b011_0100 => populate!(ApparentWatt, 1, dec: 0, ApparentPower),
633                    0b011_0101 => populate!(ApparentWatt, 1, dec: 1, ApparentPower),
634                    0b011_0110 => populate!(ApparentWatt, 1, dec: 2, ApparentPower),
635                    0b011_0111 => populate!(ApparentWatt, 1, dec: 3, ApparentPower),
636                    0b101_1000 => populate!(Fahrenheit, 1, dec: -3, FlowTemperature),
637                    0b101_1001 => populate!(Fahrenheit, 1, dec: -2, FlowTemperature),
638                    0b101_1010 => populate!(Fahrenheit, 1, dec: -1, FlowTemperature),
639                    0b101_1011 => populate!(Fahrenheit, 1, dec: 0, FlowTemperature),
640                    0b101_1100 => populate!(Fahrenheit, 1, dec: -3, ReturnTemperature),
641                    0b101_1101 => populate!(Fahrenheit, 1, dec: -2, ReturnTemperature),
642                    0b101_1110 => populate!(Fahrenheit, 1, dec: -1, ReturnTemperature),
643                    0b101_1111 => populate!(Fahrenheit, 1, dec: 0, ReturnTemperature),
644                    0b110_0000 => populate!(Fahrenheit, 1, dec: -3, TemperatureDifference),
645                    0b110_0001 => populate!(Fahrenheit, 1, dec: -2, TemperatureDifference),
646                    0b110_0010 => populate!(Fahrenheit, 1, dec: -1, TemperatureDifference),
647                    0b110_0011 => populate!(Fahrenheit, 1, dec: 0, TemperatureDifference),
648                    0b110_0100 => populate!(Fahrenheit, 1, dec: -3, ExternalTemperature),
649                    0b110_0101 => populate!(Fahrenheit, 1, dec: -2, ExternalTemperature),
650                    0b110_0110 => populate!(Fahrenheit, 1, dec: -1, ExternalTemperature),
651                    0b110_0111 => populate!(Fahrenheit, 1, dec: 0, ExternalTemperature),
652                    0b111_0000 => populate!(Fahrenheit, 1, dec: -3, ColdWarmTemperatureLimit),
653                    0b111_0001 => populate!(Fahrenheit, 1, dec: -2, ColdWarmTemperatureLimit),
654                    0b111_0010 => populate!(Fahrenheit, 1, dec: -1, ColdWarmTemperatureLimit),
655                    0b111_0011 => populate!(Fahrenheit, 1, dec: 0, ColdWarmTemperatureLimit),
656                    0b111_0100 => populate!(Celsius, 1, dec: -3, ColdWarmTemperatureLimit),
657                    0b111_0101 => populate!(Celsius, 1, dec: -2, ColdWarmTemperatureLimit),
658                    0b111_0110 => populate!(Celsius, 1, dec: -1, ColdWarmTemperatureLimit),
659                    0b111_0111 => populate!(Celsius, 1, dec: 0, ColdWarmTemperatureLimit),
660                    0b111_1000 => populate!(Watt, 1, dec: -3, CumulativeMaximumOfActivePower),
661                    0b111_1001 => populate!(Watt, 1, dec: -2, CumulativeMaximumOfActivePower),
662                    0b111_1010 => populate!(Watt, 1, dec: -1, CumulativeMaximumOfActivePower),
663                    0b111_1011 => populate!(Watt, 1, dec: 0, CumulativeMaximumOfActivePower),
664                    0b111_1100 => populate!(Watt, 1, dec: 1, CumulativeMaximumOfActivePower),
665                    0b111_1101 => populate!(Watt, 1, dec: 2, CumulativeMaximumOfActivePower),
666                    0b111_1110 => populate!(Watt, 1, dec: 3, CumulativeMaximumOfActivePower),
667                    0b111_1111 => populate!(Watt, 1, dec: 4, CumulativeMaximumOfActivePower),
668                    0b110_1000 => populate!(HCAUnit, 1,dec: 0, ResultingRatingFactor),
669                    0b110_1001 => populate!(HCAUnit, 1,dec: 0, ThermalOutputRatingFactor),
670                    0b110_1010 => populate!(HCAUnit, 1,dec: 0, ThermalCouplingRatingFactorOverall),
671                    0b110_1011 => populate!(HCAUnit, 1,dec: 0, ThermalCouplingRatingRoomSide),
672                    0b110_1100 => {
673                        populate!(HCAUnit, 1,dec: 0, ThermalCouplingRatingFactorHeatingSide)
674                    }
675                    0b110_1101 => populate!(HCAUnit, 1,dec: 0, LowTemperatureRatingFactor),
676                    0b110_1110 => populate!(HCAUnit, 1,dec: 0, DisplayOutputScalingFactor),
677
678                    _ => labels.push(ValueLabel::Reserved),
679                };
680                // Skip vife_slice[0] — it's the true VIF (already consumed above)
681                consume_orthhogonal_vife(
682                    vife_slice.get(1..).unwrap_or(&[]),
683                    &mut labels,
684                    &mut units,
685                    &mut decimal_scale_exponent,
686                    &mut decimal_offset_exponent,
687                );
688            }
689            // we need to check if the next byte is equivalent to the length of the rest of the
690            // the data. In this case it is very likely that, this is how the payload is built up.
691            ValueInformationCoding::PlainText => {
692                labels.push(ValueLabel::PlainText);
693                consume_orthhogonal_vife(
694                    vife_slice,
695                    &mut labels,
696                    &mut units,
697                    &mut decimal_scale_exponent,
698                    &mut decimal_offset_exponent,
699                );
700            }
701            ValueInformationCoding::ManufacturerSpecific => {
702                labels.push(ValueLabel::ManufacturerSpecific)
703            }
704        }
705
706        Ok(Self {
707            decimal_offset_exponent,
708            labels,
709            decimal_scale_exponent,
710            units,
711        })
712    }
713}
714
715fn consume_orthhogonal_vife(
716    vife: &[ValueInformationFieldExtension],
717    labels: &mut ArrayVec<ValueLabel, 10>,
718    units: &mut ArrayVec<Unit, 10>,
719    decimal_scale_exponent: &mut isize,
720    decimal_offset_exponent: &mut isize,
721) {
722    let mut is_extension_of_combinable_orthogonal_vife = false;
723    for v in vife {
724        if v.data == 0xFC {
725            is_extension_of_combinable_orthogonal_vife = true;
726            continue;
727        }
728        if is_extension_of_combinable_orthogonal_vife {
729            is_extension_of_combinable_orthogonal_vife = false;
730            match v.data & 0x7F {
731                0x00 => labels.push(ValueLabel::Reserved),
732                0x01 => labels.push(ValueLabel::AtPhaseL1),
733                0x02 => labels.push(ValueLabel::AtPhaseL2),
734                0x03 => labels.push(ValueLabel::AtPhaseL3),
735                0x04 => labels.push(ValueLabel::AtNeutral),
736                0x05 => labels.push(ValueLabel::BetweenPhasesL1L2),
737                0x06 => labels.push(ValueLabel::BetweenPhasesL2L3),
738                0x07 => labels.push(ValueLabel::BetweenPhasesL3L1),
739                0x08 => labels.push(ValueLabel::AtQuadrant1),
740                0x09 => labels.push(ValueLabel::AtQuadrant2),
741                0x0A => labels.push(ValueLabel::AtQuadrant3),
742                0x0B => labels.push(ValueLabel::AtQuadrant4),
743                0x0C => labels.push(ValueLabel::DeltaBetweenImportAndExport),
744                0x0D => labels.push(ValueLabel::AlternativeNonMetricUnits),
745                0x0E => labels.push(ValueLabel::SecondarySensorMeasurement),
746                0x0F => labels.push(ValueLabel::HigherResolutionRegister),
747                0x10 => labels.push(
748                    ValueLabel::AccumulationOfAbsoluteValueBothPositiveAndNegativeContribution,
749                ),
750                0x11 => labels.push(ValueLabel::DataPresentedWithTypeC),
751                0x12 => labels.push(ValueLabel::DataPresentedWithTypeD),
752                0x13 => labels.push(ValueLabel::EndDate),
753                0x14 => labels.push(ValueLabel::DirectionFromCommunicationPartnerToMeter),
754                0x15 => labels.push(ValueLabel::DirectionFromMeterToCommunicationPartner),
755                _ => labels.push(ValueLabel::Reserved),
756            }
757        } else {
758            match v.data & 0x7F {
759                0x00..=0x0F => labels.push(ValueLabel::ReservedForObjectActions),
760                0x10..=0x11 => labels.push(ValueLabel::Reserved),
761                0x12 => labels.push(ValueLabel::Averaged),
762                0x13 => labels.push(ValueLabel::InverseCompactProfile),
763                0x14 => labels.push(ValueLabel::RelativeDeviation),
764                0x15..=0x1C => labels.push(ValueLabel::RecordErrorCodes),
765                0x1D => labels.push(ValueLabel::StandardConformDataContent),
766                0x1E => labels.push(ValueLabel::CompactProfileWithRegisterNumbers),
767                0x1F => labels.push(ValueLabel::CompactProfile),
768                0x20 => units.push(unit!(Second ^ -1)),
769                0x21 => units.push(unit!(Minute ^ -1)),
770                0x22 => units.push(unit!(Hour ^ -1)),
771                0x23 => units.push(unit!(Day ^ -1)),
772                0x24 => units.push(unit!(Week ^ -1)),
773                0x25 => units.push(unit!(Month ^ -1)),
774                0x26 => units.push(unit!(Year ^ -1)),
775                0x27 => units.push(unit!(Revolution ^ -1)),
776                0x28 => {
777                    units.push(unit!(Increment));
778                    units.push(unit!(InputPulseOnChannel0 ^ -1));
779                }
780                0x29 => {
781                    units.push(unit!(Increment));
782                    units.push(unit!(InputPulseOnChannel1 ^ -1));
783                }
784                0x2A => {
785                    units.push(unit!(Increment));
786                    units.push(unit!(OutputPulseOnChannel0 ^ -1));
787                }
788                0x2B => {
789                    units.push(unit!(Increment));
790                    units.push(unit!(OutputPulseOnChannel1 ^ -1));
791                }
792                0x2C => units.push(unit!(Liter)),
793                0x2D => units.push(unit!(Meter ^ -3)),
794                0x2E => units.push(unit!(Kilogram ^ -1)),
795                0x2F => units.push(unit!(Kelvin ^ -1)),
796                0x30 => {
797                    units.push(unit!(Watt ^ -1));
798                    units.push(unit!(Hour ^ -1));
799                    *decimal_scale_exponent -= 3;
800                }
801                0x31 => {
802                    units.push(unit!(Joul ^ -1));
803                    *decimal_scale_exponent += -9;
804                }
805                0x32 => {
806                    units.push(unit!(Watt ^ -1));
807                    *decimal_scale_exponent += -3;
808                }
809                0x33 => {
810                    units.push(unit!(Kelvin ^ -1));
811                    units.push(unit!(Liter ^ -1));
812                }
813                0x34 => units.push(unit!(Volt ^ -1)),
814                0x35 => units.push(unit!(Ampere ^ -1)),
815                0x36 => units.push(unit!(Second ^ 1)),
816                0x37 => {
817                    units.push(unit!(Second ^ 1));
818                    units.push(unit!(Volt ^ -1));
819                }
820                0x38 => {
821                    units.push(unit!(Second ^ 1));
822                    units.push(unit!(Ampere ^ -1));
823                }
824                0x39 => labels.push(ValueLabel::StartDateOf),
825                0x3A => labels.push(ValueLabel::VifContainsUncorrectedUnitOrValue),
826                0x3B => labels.push(ValueLabel::AccumulationOnlyIfValueIsPositive),
827                0x3C => labels.push(ValueLabel::AccumulationOnlyIfValueIsNegative),
828                0x3D => labels.push(ValueLabel::NonMetricUnits),
829                0x3E => labels.push(ValueLabel::ValueAtBaseConditions),
830                0x3F => labels.push(ValueLabel::ObisDeclaration),
831                // E100 u000 where u = 0: Lower; u = 1: Upper
832                0x40 => labels.push(ValueLabel::LowerLimitValue),
833                0x48 => labels.push(ValueLabel::UpperLimitValue),
834                // E100 u001 where u = 0: Lower; u = 1: Upper
835                0x41 => labels.push(ValueLabel::NumberOfExceedsOfLowerLimitValue),
836                0x49 => labels.push(ValueLabel::NumberOfExceedsOfUpperLimitValue),
837                /* E100 uf1b where
838                b = 0: Begin; b = 1: End
839                f = 0: First; b = 1: Last
840                u = 0: Lower; u = 1: Upper
841                */
842                0x42 => labels.push(ValueLabel::DateOfBeginFirstLowerLimitExceed),
843                0x43 => labels.push(ValueLabel::DateOfEndFirstLowerLimitExceed),
844                0x46 => labels.push(ValueLabel::DateOfBeginLastLowerLimitExceed),
845                0x47 => labels.push(ValueLabel::DateOfEndLastLowerLimitExceed),
846                0x4A => labels.push(ValueLabel::DateOfBeginFirstUpperLimitExceed),
847                0x4B => labels.push(ValueLabel::DateOfEndFirstUpperLimitExceed),
848                0x4E => labels.push(ValueLabel::DateOfBeginLastUpperLimitExceed),
849                0x4F => labels.push(ValueLabel::DateOfEndLastUpperLimitExceed),
850                0x50 => {
851                    labels.push(ValueLabel::DurationOfFirstLowerLimitExceed);
852                    units.push(unit!(Second));
853                }
854                0x51 => {
855                    labels.push(ValueLabel::DurationOfFirstLowerLimitExceed);
856                    units.push(unit!(Minute));
857                }
858                0x52 => {
859                    labels.push(ValueLabel::DurationOfFirstLowerLimitExceed);
860                    units.push(unit!(Hour));
861                }
862                0x53 => {
863                    labels.push(ValueLabel::DurationOfFirstLowerLimitExceed);
864                    units.push(unit!(Day));
865                }
866                0x54 => {
867                    labels.push(ValueLabel::DurationOfLastLowerLimitExceed);
868                    units.push(unit!(Second));
869                }
870                0x55 => {
871                    labels.push(ValueLabel::DurationOfLastLowerLimitExceed);
872                    units.push(unit!(Minute));
873                }
874                0x56 => {
875                    labels.push(ValueLabel::DurationOfLastLowerLimitExceed);
876                    units.push(unit!(Hour));
877                }
878                0x57 => {
879                    labels.push(ValueLabel::DurationOfLastLowerLimitExceed);
880                    units.push(unit!(Day));
881                }
882                0x58 => {
883                    labels.push(ValueLabel::DurationOfFirstUpperLimitExceed);
884                    units.push(unit!(Second));
885                }
886                0x59 => {
887                    labels.push(ValueLabel::DurationOfFirstUpperLimitExceed);
888                    units.push(unit!(Minute));
889                }
890                0x5A => {
891                    labels.push(ValueLabel::DurationOfFirstUpperLimitExceed);
892                    units.push(unit!(Hour));
893                }
894                0x5B => {
895                    labels.push(ValueLabel::DurationOfFirstUpperLimitExceed);
896                    units.push(unit!(Day));
897                }
898                0x5C => {
899                    labels.push(ValueLabel::DurationOfLastUpperLimitExceed);
900                    units.push(unit!(Second));
901                }
902                0x5D => {
903                    labels.push(ValueLabel::DurationOfLastUpperLimitExceed);
904                    units.push(unit!(Minute));
905                }
906                0x5E => {
907                    labels.push(ValueLabel::DurationOfLastUpperLimitExceed);
908                    units.push(unit!(Hour));
909                }
910                0x5F => {
911                    labels.push(ValueLabel::DurationOfLastUpperLimitExceed);
912                    units.push(unit!(Day));
913                }
914                0x60 => {
915                    labels.push(ValueLabel::DurationOfFirst);
916                    units.push(unit!(Second));
917                }
918                0x61 => {
919                    labels.push(ValueLabel::DurationOfFirst);
920                    units.push(unit!(Minute));
921                }
922                0x62 => {
923                    labels.push(ValueLabel::DurationOfFirst);
924                    units.push(unit!(Hour));
925                }
926                0x63 => {
927                    labels.push(ValueLabel::DurationOfFirst);
928                    units.push(unit!(Day));
929                }
930                0x64 => {
931                    labels.push(ValueLabel::DurationOfLast);
932                    units.push(unit!(Second));
933                }
934                0x65 => {
935                    labels.push(ValueLabel::DurationOfLast);
936                    units.push(unit!(Minute));
937                }
938                0x66 => {
939                    labels.push(ValueLabel::DurationOfLast);
940                    units.push(unit!(Hour));
941                }
942                0x67 => {
943                    labels.push(ValueLabel::DurationOfLast);
944                    units.push(unit!(Day));
945                }
946                0x68 => labels.push(ValueLabel::ValueDuringLowerValueExceed),
947                0x6C => labels.push(ValueLabel::ValueDuringUpperValueExceed),
948                0x69 => labels.push(ValueLabel::LeakageValues),
949                0x6D => labels.push(ValueLabel::OverflowValues),
950                0x6A => labels.push(ValueLabel::DateOfBeginFirst),
951                0x6B => labels.push(ValueLabel::DateOfBeginLast),
952                0x6E => labels.push(ValueLabel::DateOfEndLast),
953                0x6F => labels.push(ValueLabel::DateOfEndFirst),
954                0x70..=0x77 => {
955                    *decimal_scale_exponent += (v.data & 0b111) as isize - 6;
956                }
957                0x78..=0x7B => {
958                    *decimal_offset_exponent += (v.data & 0b11) as isize - 3;
959                }
960                0x7D => {
961                    *decimal_scale_exponent += 3;
962                }
963                0x7E => labels.push(ValueLabel::FutureValue),
964                0x7F => labels.push(ValueLabel::NextVIFEAndDataOfThisBlockAreManufacturerSpecific),
965                _ => labels.push(ValueLabel::Reserved),
966            };
967        }
968    }
969}
970
971#[derive(Debug, Clone, Copy, PartialEq)]
972#[cfg_attr(feature = "defmt", derive(defmt::Format))]
973#[non_exhaustive]
974pub enum ValueInformationError {
975    InvalidValueInformation,
976}
977
978impl From<u8> for ValueInformationField {
979    fn from(data: u8) -> Self {
980        Self { data }
981    }
982}
983/// This is the most important type of the this file and represents
984/// the whole information inside the value information block
985/// value(x) = (multiplier * value + offset) * units
986#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
987#[derive(Debug, PartialEq, Clone)]
988pub struct ValueInformation {
989    pub decimal_offset_exponent: isize,
990    pub labels: ArrayVec<ValueLabel, 10>,
991    pub decimal_scale_exponent: isize,
992    pub units: ArrayVec<Unit, 10>,
993}
994
995#[cfg(feature = "defmt")]
996impl defmt::Format for ValueInformation {
997    fn format(&self, f: defmt::Formatter) {
998        defmt::write!(
999            f,
1000            "ValueInformation{{ decimal_offset_exponent: {}, decimal_scale_exponent: {}",
1001            self.decimal_offset_exponent,
1002            self.decimal_scale_exponent
1003        );
1004        if !self.labels.is_empty() {
1005            defmt::write!(f, ", labels: [");
1006            for (i, label) in self.labels.iter().enumerate() {
1007                if i != 0 {
1008                    defmt::write!(f, ", ");
1009                }
1010                defmt::write!(f, "{:?}", label);
1011            }
1012            defmt::write!(f, "]");
1013        }
1014        if !self.units.is_empty() {
1015            defmt::write!(f, ", units: [");
1016            for (i, unit) in self.units.iter().enumerate() {
1017                if i != 0 {
1018                    defmt::write!(f, ", ");
1019                }
1020                defmt::write!(f, "{:?}", unit);
1021            }
1022            defmt::write!(f, "]");
1023        }
1024        defmt::write!(f, " }}");
1025    }
1026}
1027
1028#[cfg(feature = "std")]
1029impl fmt::Display for ValueInformation {
1030    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1031        if self.decimal_offset_exponent != 0 {
1032            write!(f, "+{})", self.decimal_offset_exponent)?;
1033        } else {
1034            write!(f, ")")?;
1035        }
1036        if self.decimal_scale_exponent != 0 {
1037            write!(f, "e{}", self.decimal_scale_exponent)?;
1038        }
1039        if !self.units.is_empty() {
1040            write!(f, "[")?;
1041            for unit in &self.units {
1042                write!(f, "{}", unit)?;
1043            }
1044            write!(f, "]")?;
1045        }
1046        if !self.labels.is_empty() {
1047            write!(f, "(")?;
1048            for (i, label) in self.labels.iter().enumerate() {
1049                write!(f, "{:?}", label)?;
1050                if i != self.labels.len() - 1 {
1051                    write!(f, ", ")?;
1052                }
1053            }
1054
1055            return write!(f, ")");
1056        }
1057        Ok(())
1058    }
1059}
1060#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1061#[derive(Debug, Clone, Copy, PartialEq)]
1062#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1063#[non_exhaustive]
1064pub enum ValueLabel {
1065    Instantaneous,
1066    ReservedForObjectActions,
1067    Reserved,
1068    Averaged,
1069    Integral,
1070    Parameter,
1071    InverseCompactProfile,
1072    RelativeDeviation,
1073    RecordErrorCodes,
1074    StandardConformDataContent,
1075    CompactProfileWithRegisterNumbers,
1076    CompactProfile,
1077    ActualityDuration,
1078    AveragingDuration,
1079    Date,
1080    Time,
1081    DateTime,
1082    DateTimeWithSeconds,
1083    FabricationNumber,
1084    EnhancedIdentification,
1085    Address,
1086    PlainText,
1087    RevolutionOrMeasurement,
1088    IncrementPerInputPulseOnChannelP,
1089    IncrementPerOutputPulseOnChannelP,
1090    HourMinuteSecond,
1091    DayMonthYear,
1092    StartDateOf,
1093    VifContainsUncorrectedUnitOrValue,
1094    AccumulationOnlyIfValueIsPositive,
1095    AccumulationOnlyIfValueIsNegative,
1096    NonMetricUnits,
1097    AlternativeNonMetricUnits,
1098    ValueAtBaseConditions,
1099    ObisDeclaration,
1100    UpperLimitValue,
1101    LowerLimitValue,
1102    NumberOfExceedsOfUpperLimitValue,
1103    NumberOfExceedsOfLowerLimitValue,
1104    DateOfBeginFirstLowerLimitExceed,
1105    DateOfBeginFirstUpperLimitExceed,
1106    DateOfBeginLastLowerLimitExceed,
1107    DateOfBeginLastUpperLimitExceed,
1108    DateOfEndLastLowerLimitExceed,
1109    DateOfEndLastUpperLimitExceed,
1110    DateOfEndFirstLowerLimitExceed,
1111    DateOfEndFirstUpperLimitExceed,
1112    DurationOfFirstLowerLimitExceed,
1113    DurationOfFirstUpperLimitExceed,
1114    DurationOfLastLowerLimitExceed,
1115    DurationOfLastUpperLimitExceed,
1116    DurationOfFirst,
1117    DurationOfLast,
1118    ValueDuringLowerValueExceed,
1119    ValueDuringUpperValueExceed,
1120    LeakageValues,
1121    OverflowValues,
1122    DateOfBeginLast,
1123    DateOfBeginFirst,
1124    DateOfEndLast,
1125    DateOfEndFirst,
1126    ExtensionOfCombinableOrthogonalVIFE,
1127    FutureValue,
1128    NextVIFEAndDataOfThisBlockAreManufacturerSpecific,
1129    Credit,
1130    Debit,
1131    UniqueMessageIdentificationOrAccessNumber,
1132    DeviceType,
1133    Manufacturer,
1134    ParameterSetIdentification,
1135    ModelOrVersion,
1136    HardwareVersion,
1137    MetrologyFirmwareVersion,
1138    OtherSoftwareVersion,
1139    CustomerLocation,
1140    Customer,
1141    AccessCodeUser,
1142    AccessCodeOperator,
1143    AccessCodeSystemOperator,
1144    AccessCodeDeveloper,
1145    Password,
1146    ErrorFlags,
1147    ErrorMask,
1148    SecurityKey,
1149    DigitalInput,
1150    DigitalOutput,
1151    Binary,
1152    BaudRate,
1153    ResponseDelayTime,
1154    Retry,
1155    RemoteControl,
1156    FirstStorageForCycleStorage,
1157    LastStorageForCycleStorage,
1158    SizeOfStorageBlock,
1159    DescriptionOfTariffAndSubunit,
1160    StorageInterval,
1161    Dimensionless,
1162    DimensionlessHCA,
1163    DataContainerForWmbusProtocol,
1164    PeriodOfNormalDataTransmission,
1165    ResetCounter,
1166    CumulationCounter,
1167    ControlSignal,
1168    DayOfWeek,
1169    WeekNumber,
1170    TimePointOfChangeOfTariff,
1171    StateOfParameterActivation,
1172    SpecialSupplierInformation,
1173    DurationSinceLastCumulation,
1174    OperatingTimeBattery,
1175    DateAndTimeOfBatteryChange,
1176    RFPowerLevel,
1177    DaylightSavingBeginningEndingDeviation,
1178    ListeningWindowManagementData,
1179    RemainingBatteryLifeTime,
1180    NumberOfTimesTheMeterWasStopped,
1181    DataContainerForManufacturerSpecificProtocol,
1182    CurrentlySelectedApplication,
1183    Energy,
1184    ReactiveEnergy,
1185    ApparentEnergy,
1186    CoefficientOfPerformance,
1187    ReactivePower,
1188    Frequency,
1189    ApparentPower,
1190    AtPhaseL1,
1191    AtPhaseL2,
1192    AtPhaseL3,
1193    AtNeutral,
1194    BetweenPhasesL1L2,
1195    BetweenPhasesL2L3,
1196    BetweenPhasesL3L1,
1197    AtQuadrant1,
1198    AtQuadrant2,
1199    AtQuadrant3,
1200    AtQuadrant4,
1201    DeltaBetweenImportAndExport,
1202    AccumulationOfAbsoluteValueBothPositiveAndNegativeContribution,
1203    SecondarySensorMeasurement,
1204    HigherResolutionRegister,
1205    DataPresentedWithTypeC,
1206    DataPresentedWithTypeD,
1207    EndDate,
1208    DirectionFromCommunicationPartnerToMeter,
1209    DirectionFromMeterToCommunicationPartner,
1210    RelativeHumidity,
1211    MoistureLevel,
1212    PhaseUtoU,
1213    PhaseUtoI,
1214    PhaseItoU,
1215    ColdWarmTemperatureLimit,
1216    CumulativeMaximumOfActivePower,
1217    ResultingRatingFactor,
1218    ThermalOutputRatingFactor,
1219    ThermalCouplingRatingFactorOverall,
1220    ThermalCouplingRatingRoomSide,
1221    ThermalCouplingRatingFactorHeatingSide,
1222    LowTemperatureRatingFactor,
1223    DisplayOutputScalingFactor,
1224    ManufacturerSpecific,
1225    OnTime,
1226    OperatingTime,
1227    Volume,
1228    Mass,
1229    Power,
1230    VolumeFlow,
1231    MassFlow,
1232    Pressure,
1233    Voltage,
1234    Current,
1235    FlowTemperature,
1236    ReturnTemperature,
1237    TemperatureDifference,
1238    ExternalTemperature,
1239}
1240
1241#[cfg(feature = "std")]
1242impl fmt::Display for Unit {
1243    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1244        let superscripts = ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
1245        let invalid_superscript = '⁻';
1246        match self.exponent {
1247            1 => write!(f, "{}", self.name),
1248            0..=9 => write!(
1249                f,
1250                "{}{}",
1251                self.name,
1252                superscripts
1253                    .get(self.exponent as usize)
1254                    .unwrap_or(&invalid_superscript)
1255            ),
1256            10..=19 => write!(
1257                f,
1258                "{}{}{}",
1259                self.name,
1260                superscripts.get(1).unwrap_or(&invalid_superscript),
1261                superscripts
1262                    .get(self.exponent as usize - 10)
1263                    .unwrap_or(&invalid_superscript)
1264            ),
1265            x if (-9..0).contains(&x) => {
1266                write!(
1267                    f,
1268                    "{}⁻{}",
1269                    self.name,
1270                    superscripts
1271                        .get((-x) as usize)
1272                        .unwrap_or(&invalid_superscript)
1273                )
1274            }
1275            x if (-19..0).contains(&x) => write!(
1276                f,
1277                "{}⁻{}{}",
1278                self.name,
1279                superscripts.get(1).unwrap_or(&invalid_superscript),
1280                superscripts
1281                    .get((-x) as usize - 10)
1282                    .unwrap_or(&invalid_superscript)
1283            ),
1284            x => write!(f, "{}^{}", self.name, x),
1285        }
1286    }
1287}
1288#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1289#[derive(Debug, Clone, Copy, PartialEq)]
1290#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1291#[non_exhaustive]
1292pub enum UnitName {
1293    Watt,
1294    ReactiveWatt,
1295    ApparentWatt,
1296    Joul,
1297    Kilogram,
1298    Tonne,
1299    Meter,
1300    Feet,
1301    Celsius,
1302    Kelvin,
1303    Bar,
1304    HCA,
1305    Reserved,
1306    WithoutUnits,
1307    Second,
1308    Minute,
1309    Hour,
1310    Day,
1311    Week,
1312    Month,
1313    Year,
1314    Revolution,
1315    Increment,
1316    InputPulseOnChannel0,
1317    OutputPulseOnChannel0,
1318    InputPulseOnChannel1,
1319    OutputPulseOnChannel1,
1320    Liter,
1321    Volt,
1322    Ampere,
1323    LocalMoneyCurrency,
1324    Symbol,
1325    BitTime,
1326    DecibelMilliWatt,
1327    Percent,
1328    Degree,
1329    Hertz,
1330    HCAUnit,
1331    Fahrenheit,
1332    AmericanGallon,
1333    Calorie,
1334}
1335
1336#[cfg(feature = "std")]
1337impl fmt::Display for UnitName {
1338    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1339        match self {
1340            UnitName::Watt => write!(f, "W"),
1341            UnitName::ReactiveWatt => write!(f, "W (reactive)"),
1342            UnitName::ApparentWatt => write!(f, "W (apparent)"),
1343            UnitName::Joul => write!(f, "J"),
1344            UnitName::Kilogram => write!(f, "Kg"),
1345            UnitName::Tonne => write!(f, "t"),
1346            UnitName::Meter => write!(f, "m"),
1347            UnitName::Feet => write!(f, "ft"),
1348            UnitName::Celsius => write!(f, "°C"),
1349            UnitName::Kelvin => write!(f, "°K"),
1350            UnitName::Bar => write!(f, "Bar"),
1351            UnitName::HCA => write!(f, "HCA"),
1352            UnitName::Reserved => write!(f, "Reserved"),
1353            UnitName::WithoutUnits => write!(f, "-"),
1354            UnitName::Second => write!(f, "s"),
1355            UnitName::Minute => write!(f, "min"),
1356            UnitName::Hour => write!(f, "h"),
1357            UnitName::Day => write!(f, "day"),
1358            UnitName::Week => write!(f, "week"),
1359            UnitName::Month => write!(f, "month"),
1360            UnitName::Year => write!(f, "year"),
1361            UnitName::Revolution => write!(f, "revolution"),
1362            UnitName::Increment => write!(f, "increment"),
1363            UnitName::InputPulseOnChannel0 => write!(f, "InputPulseOnChannel0"),
1364            UnitName::OutputPulseOnChannel0 => write!(f, "OutputPulseOnChannel0"),
1365            UnitName::InputPulseOnChannel1 => write!(f, "InputPulseOnChannel1"),
1366            UnitName::OutputPulseOnChannel1 => write!(f, "OutputPulseOnChannel1"),
1367            UnitName::Liter => write!(f, "l"),
1368            UnitName::Volt => write!(f, "V"),
1369            UnitName::Ampere => write!(f, "A"),
1370            UnitName::LocalMoneyCurrency => write!(f, "$ (local)"),
1371            UnitName::Symbol => write!(f, "Symbol"),
1372            UnitName::BitTime => write!(f, "BitTime"),
1373            UnitName::DecibelMilliWatt => write!(f, "dBmW"),
1374            UnitName::Percent => write!(f, "%"),
1375            UnitName::Degree => write!(f, "°"),
1376            UnitName::Hertz => write!(f, "Hz"),
1377            UnitName::HCAUnit => write!(f, "HCAUnit"),
1378            UnitName::Fahrenheit => write!(f, "°F"),
1379            UnitName::AmericanGallon => write!(f, "UsGal"),
1380            UnitName::Calorie => write!(f, "cal"),
1381        }
1382    }
1383}
1384
1385mod tests {
1386
1387    #[test]
1388    fn test_single_byte_primary_value_information_parsing() {
1389        use crate::value_information::UnitName;
1390        use crate::value_information::{
1391            Unit, ValueInformation, ValueInformationBlock, ValueInformationField, ValueLabel,
1392        };
1393        use arrayvec::ArrayVec;
1394
1395        /* VIB = 0x13 => m3^3*1e-3 */
1396        let data = [0x13];
1397        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1398        assert_eq!(
1399            result,
1400            ValueInformationBlock {
1401                value_information: ValueInformationField::from(0x13),
1402                value_information_extension: None,
1403                plaintext_vife: None
1404            }
1405        );
1406        assert_eq!(result.get_size(), 1);
1407        assert_eq!(
1408            ValueInformation::try_from(&result).unwrap(),
1409            ValueInformation {
1410                decimal_offset_exponent: 0,
1411                decimal_scale_exponent: -3,
1412                units: {
1413                    let mut x = ArrayVec::<Unit, 10>::new();
1414                    x.push(unit!(Meter ^ 3));
1415                    x
1416                },
1417                labels: {
1418                    let mut x = ArrayVec::<ValueLabel, 10>::new();
1419                    x.push(ValueLabel::Volume);
1420                    x
1421                }
1422            }
1423        );
1424
1425        /* VIB = 0x14 => m3^-3*1e-2 */
1426        let data = [0x14];
1427        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1428        assert_eq!(
1429            result,
1430            ValueInformationBlock {
1431                value_information: ValueInformationField::from(0x14),
1432                value_information_extension: None,
1433                plaintext_vife: None
1434            }
1435        );
1436        assert_eq!(result.get_size(), 1);
1437        assert_eq!(
1438            ValueInformation::try_from(&result).unwrap(),
1439            ValueInformation {
1440                decimal_offset_exponent: 0,
1441                decimal_scale_exponent: -2,
1442                units: {
1443                    let mut x = ArrayVec::<Unit, 10>::new();
1444                    x.push(unit!(Meter ^ 3));
1445                    x
1446                },
1447
1448                labels: {
1449                    let mut x = ArrayVec::<ValueLabel, 10>::new();
1450                    x.push(ValueLabel::Volume);
1451                    x
1452                }
1453            }
1454        );
1455
1456        /* VIB = 0x15 => m3^3*1e-2 */
1457        let data = [0x15];
1458        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1459        assert_eq!(
1460            result,
1461            ValueInformationBlock {
1462                value_information: ValueInformationField::from(0x15),
1463                value_information_extension: None,
1464                plaintext_vife: None
1465            }
1466        );
1467        assert_eq!(result.get_size(), 1);
1468        assert_eq!(
1469            ValueInformation::try_from(&result).unwrap(),
1470            ValueInformation {
1471                decimal_offset_exponent: 0,
1472                decimal_scale_exponent: -1,
1473                units: {
1474                    let mut x = ArrayVec::<Unit, 10>::new();
1475                    x.push(unit!(Meter ^ 3));
1476                    x
1477                },
1478                labels: {
1479                    let mut x = ArrayVec::<ValueLabel, 10>::new();
1480                    x.push(ValueLabel::Volume);
1481                    x
1482                }
1483            }
1484        );
1485
1486        /* VIB = 0x16 => m3^-3*1e-1 */
1487        let data = [0x16];
1488        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1489        assert_eq!(
1490            result,
1491            ValueInformationBlock {
1492                value_information: ValueInformationField::from(0x16),
1493                value_information_extension: None,
1494                plaintext_vife: None
1495            },
1496        );
1497        assert_eq!(result.get_size(), 1);
1498    }
1499
1500    #[test]
1501    fn test_multibyte_primary_value_information() {
1502        use crate::value_information::UnitName;
1503        use crate::value_information::{
1504            Unit, ValueInformation, ValueInformationBlock, ValueInformationField, ValueLabel,
1505        };
1506        use arrayvec::ArrayVec;
1507        /* 1 VIF, 1 - 10 orthogonal VIFE */
1508
1509        /* VIF 0x96 = 0x16 | 0x80  => m3^-3*1e-1 with extension*/
1510        /* VIFE 0x12 => Combinable Orthogonal VIFE meaning "averaged" */
1511        /* VIB = 0x96, 0x12 */
1512        let data = [0x96, 0x12];
1513        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1514        assert_eq!(result.get_size(), 2);
1515        assert_eq!(result.value_information, ValueInformationField::from(0x96));
1516        assert_eq!(ValueInformation::try_from(&result).unwrap().labels, {
1517            let mut x = ArrayVec::<ValueLabel, 10>::new();
1518            x.push(ValueLabel::Volume);
1519            x.push(ValueLabel::Averaged);
1520            x
1521        });
1522
1523        /* VIF 0x96 = 0x16 | 0x80  => m3^-3*1e-1 with extension*/
1524        /* VIFE 0x92 = 0x12 | 0x80  => Combinable Orthogonal VIFE meaning "averaged" with extension */
1525        /* VIFE 0x20 => Combinable Orthogonal VIFE meaning "per second" */
1526        /* VIB = 0x96, 0x92,0x20 */
1527
1528        let data = [0x96, 0x92, 0x20];
1529        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1530        assert_eq!(result.get_size(), 3);
1531        assert_eq!(result.value_information, ValueInformationField::from(0x96));
1532        assert_eq!(
1533            ValueInformation::try_from(&result).unwrap(),
1534            ValueInformation {
1535                labels: {
1536                    let mut x = ArrayVec::<ValueLabel, 10>::new();
1537                    x.push(ValueLabel::Volume);
1538                    x.push(ValueLabel::Averaged);
1539                    x
1540                },
1541                decimal_offset_exponent: 0,
1542                decimal_scale_exponent: 0,
1543                units: {
1544                    let mut x = ArrayVec::<Unit, 10>::new();
1545                    x.push(unit!(Meter ^ 3));
1546                    x.push(unit!(Second ^ -1));
1547                    x
1548                }
1549            }
1550        );
1551
1552        /* VIF 0x96 = 0x16 | 0x80  => m3^-3*1e-1 with extension*/
1553        /* VIFE 0x92 = 0x12 | 0x80  => Combinable Orthogonal VIFE meaning "averaged" with extension */
1554        /* VIFE 0xA0= 0x20 | 0x80 => Combinable Orthogonal VIFE meaning "per second" */
1555        /* VIFE 0x2D => Combinable Orthogonal VIFE meaning "per m3". This cancels out the VIF m3, which is useless
1556        but till a valid VIB */
1557        /* VIB = 0x96, 0x92,0xA0, 0x2D */
1558        let data = [0x96, 0x92, 0xA0, 0x2D];
1559        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1560        assert_eq!(result.get_size(), 4);
1561        assert_eq!(result.value_information, ValueInformationField::from(0x96));
1562        assert_eq!(
1563            ValueInformation::try_from(&result).unwrap(),
1564            ValueInformation {
1565                labels: {
1566                    let mut x = ArrayVec::<ValueLabel, 10>::new();
1567                    x.push(ValueLabel::Volume);
1568                    x.push(ValueLabel::Averaged);
1569                    x
1570                },
1571                decimal_offset_exponent: 0,
1572                decimal_scale_exponent: 0,
1573                units: {
1574                    let mut x = ArrayVec::<Unit, 10>::new();
1575                    x.push(unit!(Meter ^ 3));
1576                    x.push(unit!(Second ^ -1));
1577                    x.push(unit!(Meter ^ -3));
1578                    x
1579                }
1580            }
1581        );
1582    }
1583
1584    #[cfg(not(feature = "plaintext-before-extension"))]
1585    #[test]
1586    fn test_plain_text_vif_norm_conform() {
1587        use arrayvec::ArrayVec;
1588
1589        use crate::value_information::{Unit, ValueInformation, ValueLabel};
1590
1591        use crate::value_information::ValueInformationBlock;
1592        // This is the ascii conform method of encoding the VIF
1593        // VIF  VIFE  LEN(3) 'H'   'R'  '%'
1594        // 0xFC, 0x74, 0x03, 0x48, 0x52, 0x25,
1595        // %RH
1596        // Combinable (orthogonal) VIFE-Code extension table
1597        // VIFE = 0x74 => E111 0nnn Multiplicative correction factor for value (not unit): 10nnn–6 => 10^-2
1598        //
1599        // according to the Norm the LEN and ASCII is not part of the VIB however this makes parsing
1600        // cumbersome so we include it in the VIB
1601
1602        let data = [0xFC, 0x74, 0x03, 0x48, 0x52, 0x25];
1603        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1604        assert_eq!(result.get_size(), 6);
1605        assert_eq!(result.value_information.data, 0xFC);
1606        assert_eq!(
1607            ValueInformation::try_from(&result).unwrap(),
1608            ValueInformation {
1609                decimal_offset_exponent: 0,
1610                decimal_scale_exponent: -2,
1611                units: { ArrayVec::<Unit, 10>::new() },
1612                labels: {
1613                    let mut x = ArrayVec::<ValueLabel, 10>::new();
1614                    x.push(ValueLabel::PlainText);
1615                    x
1616                }
1617            }
1618        );
1619
1620        // This is how the VIF is encoded in the test vectors
1621        // VIF  LEN(3) 'R'   'H'  '%'    VIFE
1622        // 0xFC, 0x03, 0x48, 0x52, 0x25, 0x74,
1623        // %RH
1624        // VIFE = 0x74 => E111 0nnn Multiplicative correction factor for value (not unit): 10nnn–6 => 10^-2
1625        // when not following the norm the LEN and ASCII is part of the VIB
1626        // It is however none norm conform, see the next example which follows
1627        // the MBUS Norm which explicitly states that the VIIFE should be after the VIF
1628        // not aftter the ASCII plain text and its size
1629    }
1630
1631    #[test]
1632    fn test_short_vif_with_vife() {
1633        use crate::value_information::ValueInformationBlock;
1634        let data = [253, 27];
1635        let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1636        assert_eq!(result.get_size(), 2);
1637    }
1638
1639    #[test]
1640    fn test_vif_fd_voltage_and_ampere() {
1641        use crate::value_information::UnitName;
1642        use crate::value_information::{ValueInformation, ValueInformationBlock};
1643
1644        // VIF=0xFD VIFE=0x48: Voltage 10^(8-9) = 0.1 V
1645        let vi = ValueInformation::try_from(
1646            &ValueInformationBlock::try_from([0xFD, 0x48].as_slice()).unwrap(),
1647        )
1648        .unwrap();
1649        assert_eq!(vi.units[0].name, UnitName::Volt);
1650        assert_eq!(vi.decimal_scale_exponent, -1);
1651
1652        // VIF=0xFD VIFE=0x59: Ampere 10^(9-12) = 0.001 A
1653        let vi = ValueInformation::try_from(
1654            &ValueInformationBlock::try_from([0xFD, 0x59].as_slice()).unwrap(),
1655        )
1656        .unwrap();
1657        assert_eq!(vi.units[0].name, UnitName::Ampere);
1658        assert_eq!(vi.decimal_scale_exponent, -3);
1659    }
1660
1661    #[test]
1662    fn test_vif_fb_added_codes_and_reserved_fallback() {
1663        use crate::value_information::UnitName;
1664        use crate::value_information::{ValueInformation, ValueInformationBlock, ValueLabel};
1665
1666        // VIF=0xFB VIFE=0x20 (E010 0000): ft³, dec: 0
1667        let vi = ValueInformation::try_from(
1668            &ValueInformationBlock::try_from([0xFB, 0x20].as_slice()).unwrap(),
1669        )
1670        .unwrap();
1671        assert_eq!(vi.units[0].name, UnitName::Feet);
1672        assert_eq!(vi.units[0].exponent, 3);
1673        assert_eq!(vi.decimal_scale_exponent, 0);
1674        assert!(vi.labels.contains(&ValueLabel::Volume));
1675
1676        // VIF=0xFB VIFE=0x23 (E010 0011): Phase angle I-U, 0.1°
1677        let vi = ValueInformation::try_from(
1678            &ValueInformationBlock::try_from([0xFB, 0x23].as_slice()).unwrap(),
1679        )
1680        .unwrap();
1681        assert_eq!(vi.units[0].name, UnitName::Degree);
1682        assert_eq!(vi.decimal_scale_exponent, -1);
1683        assert!(vi.labels.contains(&ValueLabel::PhaseItoU));
1684
1685        // VIF=0xFB VIFE=0x70: °F cold/warm temp limit, 10^-3
1686        let vi = ValueInformation::try_from(
1687            &ValueInformationBlock::try_from([0xFB, 0x70].as_slice()).unwrap(),
1688        )
1689        .unwrap();
1690        assert_eq!(vi.units[0].name, UnitName::Fahrenheit);
1691        assert_eq!(vi.decimal_scale_exponent, -3);
1692
1693        // VIF=0xFB VIFE=0x22 (E010 0010): Reserved — should not error
1694        let vi = ValueInformation::try_from(
1695            &ValueInformationBlock::try_from([0xFB, 0x22].as_slice()).unwrap(),
1696        )
1697        .unwrap();
1698        assert!(vi.labels.contains(&ValueLabel::Reserved));
1699    }
1700
1701    #[test]
1702    fn test_primary_vif_on_time_and_operating_time_labels() {
1703        use crate::value_information::{
1704            UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
1705        };
1706
1707        // VIF 0x21 = 0010 0001 = On time (0x20-0x23), nn=01 => minutes
1708        let vi = ValueInformation::try_from(
1709            &ValueInformationBlock::try_from([0x21].as_slice()).unwrap(),
1710        )
1711        .unwrap();
1712        assert!(vi.labels.contains(&ValueLabel::OnTime));
1713        assert_eq!(vi.units[0].name, UnitName::Minute);
1714
1715        // VIF 0x27 = 0010 0111 = Operating time (0x24-0x27), nn=11 => days
1716        let vi = ValueInformation::try_from(
1717            &ValueInformationBlock::try_from([0x27].as_slice()).unwrap(),
1718        )
1719        .unwrap();
1720        assert!(vi.labels.contains(&ValueLabel::OperatingTime));
1721        assert_eq!(vi.units[0].name, UnitName::Day);
1722    }
1723
1724    #[test]
1725    fn test_fb_cumulative_maximum_of_active_power() {
1726        use crate::value_information::{
1727            UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
1728        };
1729
1730        // VIF=0xFB VIFE=0x78 (0b0111_1000): CumulativeMaximumOfActivePower, W 10^-3
1731        let vi = ValueInformation::try_from(
1732            &ValueInformationBlock::try_from([0xFB, 0x78].as_slice()).unwrap(),
1733        )
1734        .unwrap();
1735        assert!(vi
1736            .labels
1737            .contains(&ValueLabel::CumulativeMaximumOfActivePower));
1738        assert_eq!(vi.units[0].name, UnitName::Watt);
1739        assert_eq!(vi.decimal_scale_exponent, -3);
1740    }
1741
1742    #[test]
1743    fn test_fb_humidity_with_combinatorial_scale() {
1744        use crate::value_information::{
1745            UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
1746        };
1747
1748        // VIF=0xFB, VIFE=0x9B (0x1B + extension bit), VIFE2=0x74 (multiplicative 10^(4-6) = 10^-2)
1749        // OMS RH01: relative humidity 10^0 %, shifted to 10^-2 by combinatorial.
1750        let vi = ValueInformation::try_from(
1751            &ValueInformationBlock::try_from([0xFB, 0x9B, 0x74].as_slice()).unwrap(),
1752        )
1753        .unwrap();
1754
1755        assert!(vi.labels.contains(&ValueLabel::RelativeHumidity));
1756        assert_eq!(vi.units[0].name, UnitName::Percent);
1757        assert_eq!(vi.decimal_scale_exponent, -2);
1758    }
1759
1760    #[test]
1761    fn test_fd_ampere_with_phase_combinatorial() {
1762        use crate::value_information::{
1763            UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
1764        };
1765
1766        // VIF=0xFD, VIFE=0xD9 (0x59 + extension bit = Ampere 10^-3),
1767        // VIFE2=0xFC (combinatorial extension), VIFE3=0x01 (AtPhaseL1)
1768        // OMS CA01: per-phase current.
1769        let vi = ValueInformation::try_from(
1770            &ValueInformationBlock::try_from([0xFD, 0xD9, 0xFC, 0x01].as_slice()).unwrap(),
1771        )
1772        .unwrap();
1773
1774        assert_eq!(vi.units[0].name, UnitName::Ampere);
1775        assert_eq!(vi.decimal_scale_exponent, -3);
1776        assert!(vi.labels.contains(&ValueLabel::AtPhaseL1));
1777    }
1778
1779    #[test]
1780    fn test_primary_vif_combinatorial_not_skipped() {
1781        use crate::value_information::{
1782            UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
1783        };
1784
1785        // VIF=0xE5 (0x65 + extension bit = External temperature 10^-2),
1786        // VIFE=0x74 (multiplicative 10^(4-6) = 10^-2)
1787        // First VIFE must NOT be skipped for primary VIFs — total should be 10^-4.
1788        let vi = ValueInformation::try_from(
1789            &ValueInformationBlock::try_from([0xE5, 0x74].as_slice()).unwrap(),
1790        )
1791        .unwrap();
1792
1793        assert!(vi.labels.contains(&ValueLabel::ExternalTemperature));
1794        assert_eq!(vi.units[0].name, UnitName::Celsius);
1795        assert_eq!(vi.decimal_scale_exponent, -4);
1796    }
1797
1798    #[test]
1799    fn test_fd_moisture_level() {
1800        use crate::value_information::{
1801            UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
1802        };
1803
1804        // OMS RH03: VIF=0xFD, VIFE1=0xFD (0x7D + extension bit), VIFE2=0x3E
1805        // Moisture Level, % 10^0
1806        let vi = ValueInformation::try_from(
1807            &ValueInformationBlock::try_from([0xFD, 0xFD, 0x3E].as_slice()).unwrap(),
1808        )
1809        .unwrap();
1810
1811        assert!(vi.labels.contains(&ValueLabel::MoistureLevel));
1812        assert_eq!(vi.units[0].name, UnitName::Percent);
1813        assert_eq!(vi.decimal_scale_exponent, 0);
1814    }
1815
1816    #[test]
1817    fn test_vib_struct_layout() {
1818        use crate::value_information::ValueInformationBlock;
1819
1820        // FD extension with orthogonal VIFEs: Ampere 10^-3, AtPhaseL1
1821        // VIF=0xFD, VIFE[0]=0xD9 (true VIF), VIFE[1]=0xFC, VIFE[2]=0x01
1822        let vib = ValueInformationBlock::try_from([0xFD, 0xD9, 0xFC, 0x01].as_slice()).unwrap();
1823
1824        assert_eq!(vib.value_information.data, 0xFD);
1825        assert_eq!(vib.get_size(), 4);
1826        assert!(vib.plaintext_vife.is_none());
1827
1828        let ext = vib.value_information_extension.as_ref().unwrap();
1829        assert_eq!(ext.len(), 3);
1830        assert_eq!(ext[0].data, 0xD9);
1831        assert_eq!(ext[1].data, 0xFC);
1832        assert_eq!(ext[2].data, 0x01);
1833
1834        // Primary VIF with one orthogonal VIFE
1835        // VIF=0x96 (Volume + extension bit), VIFE=0x12 (Averaged)
1836        let vib = ValueInformationBlock::try_from([0x96, 0x12].as_slice()).unwrap();
1837
1838        assert_eq!(vib.value_information.data, 0x96);
1839        assert_eq!(vib.get_size(), 2);
1840
1841        let ext = vib.value_information_extension.as_ref().unwrap();
1842        assert_eq!(ext.len(), 1);
1843        assert_eq!(ext[0].data, 0x12);
1844
1845        // Single primary VIF, no extension
1846        let vib = ValueInformationBlock::try_from([0x13].as_slice()).unwrap();
1847
1848        assert_eq!(vib.value_information.data, 0x13);
1849        assert_eq!(vib.get_size(), 1);
1850        assert!(vib.value_information_extension.is_none());
1851    }
1852
1853    #[test]
1854    fn test_combinable_orthogonal_vife_limit_exceed_mappings() {
1855        use crate::value_information::{
1856            UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
1857        };
1858
1859        let parse_orthogonal_vife = |vife_byte: u8| -> ValueInformation {
1860            let data = [0x93, vife_byte];
1861            let vib = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1862            ValueInformation::try_from(&vib).unwrap()
1863        };
1864
1865        // (vife_byte, expected_label, optional expected unit name)
1866        let cases: &[(u8, ValueLabel, Option<UnitName>)] = &[
1867            (0x40, ValueLabel::LowerLimitValue, None),
1868            (0x48, ValueLabel::UpperLimitValue, None),
1869            (0x41, ValueLabel::NumberOfExceedsOfLowerLimitValue, None),
1870            (0x49, ValueLabel::NumberOfExceedsOfUpperLimitValue, None),
1871            // E100 uf1b: b=Begin/End, f=First/Last, u=Lower/Upper
1872            (0x42, ValueLabel::DateOfBeginFirstLowerLimitExceed, None),
1873            (0x43, ValueLabel::DateOfEndFirstLowerLimitExceed, None),
1874            (0x46, ValueLabel::DateOfBeginLastLowerLimitExceed, None),
1875            (0x47, ValueLabel::DateOfEndLastLowerLimitExceed, None),
1876            (0x4A, ValueLabel::DateOfBeginFirstUpperLimitExceed, None),
1877            (0x4B, ValueLabel::DateOfEndFirstUpperLimitExceed, None),
1878            (0x4E, ValueLabel::DateOfBeginLastUpperLimitExceed, None),
1879            (0x4F, ValueLabel::DateOfEndLastUpperLimitExceed, None),
1880            // Duration of first lower (0x50-0x53)
1881            (
1882                0x50,
1883                ValueLabel::DurationOfFirstLowerLimitExceed,
1884                Some(UnitName::Second),
1885            ),
1886            (
1887                0x51,
1888                ValueLabel::DurationOfFirstLowerLimitExceed,
1889                Some(UnitName::Minute),
1890            ),
1891            (
1892                0x52,
1893                ValueLabel::DurationOfFirstLowerLimitExceed,
1894                Some(UnitName::Hour),
1895            ),
1896            (
1897                0x53,
1898                ValueLabel::DurationOfFirstLowerLimitExceed,
1899                Some(UnitName::Day),
1900            ),
1901            // Duration of last lower (0x54-0x57)
1902            (
1903                0x54,
1904                ValueLabel::DurationOfLastLowerLimitExceed,
1905                Some(UnitName::Second),
1906            ),
1907            (
1908                0x55,
1909                ValueLabel::DurationOfLastLowerLimitExceed,
1910                Some(UnitName::Minute),
1911            ),
1912            (
1913                0x56,
1914                ValueLabel::DurationOfLastLowerLimitExceed,
1915                Some(UnitName::Hour),
1916            ),
1917            (
1918                0x57,
1919                ValueLabel::DurationOfLastLowerLimitExceed,
1920                Some(UnitName::Day),
1921            ),
1922            // Duration of first upper (0x58-0x5B)
1923            (
1924                0x58,
1925                ValueLabel::DurationOfFirstUpperLimitExceed,
1926                Some(UnitName::Second),
1927            ),
1928            (
1929                0x59,
1930                ValueLabel::DurationOfFirstUpperLimitExceed,
1931                Some(UnitName::Minute),
1932            ),
1933            (
1934                0x5A,
1935                ValueLabel::DurationOfFirstUpperLimitExceed,
1936                Some(UnitName::Hour),
1937            ),
1938            (
1939                0x5B,
1940                ValueLabel::DurationOfFirstUpperLimitExceed,
1941                Some(UnitName::Day),
1942            ),
1943            // Duration of last upper (0x5C-0x5F)
1944            (
1945                0x5C,
1946                ValueLabel::DurationOfLastUpperLimitExceed,
1947                Some(UnitName::Second),
1948            ),
1949            (
1950                0x5D,
1951                ValueLabel::DurationOfLastUpperLimitExceed,
1952                Some(UnitName::Minute),
1953            ),
1954            (
1955                0x5E,
1956                ValueLabel::DurationOfLastUpperLimitExceed,
1957                Some(UnitName::Hour),
1958            ),
1959            (
1960                0x5F,
1961                ValueLabel::DurationOfLastUpperLimitExceed,
1962                Some(UnitName::Day),
1963            ),
1964        ];
1965
1966        for (vife_byte, expected_label, expected_unit) in cases {
1967            let vi = parse_orthogonal_vife(*vife_byte);
1968            assert!(
1969                vi.labels.contains(expected_label),
1970                "VIFE 0x{vife_byte:02X}: expected label {expected_label:?}, got {:?}",
1971                vi.labels
1972            );
1973            if let Some(unit_name) = expected_unit {
1974                assert!(
1975                    vi.units.iter().any(|u| u.name == *unit_name),
1976                    "VIFE 0x{vife_byte:02X}: expected unit {unit_name:?}, got {:?}",
1977                    vi.units
1978                );
1979            }
1980        }
1981    }
1982
1983    #[test]
1984    fn test_combinable_orthogonal_vife_fc_extension_mappings() {
1985        use crate::value_information::{ValueInformation, ValueInformationBlock, ValueLabel};
1986
1987        let parse_fc_vife = |vife_byte: u8| -> ValueInformation {
1988            let data = [0x93, 0xFC, vife_byte];
1989            let vib = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1990            ValueInformation::try_from(&vib).unwrap()
1991        };
1992
1993        let cases: &[(u8, ValueLabel)] = &[
1994            (0x02, ValueLabel::AtPhaseL2),
1995            (0x0D, ValueLabel::AlternativeNonMetricUnits),
1996            (0x0E, ValueLabel::SecondarySensorMeasurement),
1997            (0x13, ValueLabel::EndDate),
1998        ];
1999
2000        for (vife_byte, expected_label) in cases {
2001            let vi = parse_fc_vife(*vife_byte);
2002            assert!(
2003                vi.labels.contains(expected_label),
2004                "FC VIFE 0x{vife_byte:02X}: expected {expected_label:?}, got {:?}",
2005                vi.labels
2006            );
2007        }
2008    }
2009}