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