#[cfg(feature = "std")]
use std::fmt;
use super::data_information::DataInformationError;
use arrayvec::ArrayVec;
const MAX_VIFE_RECORDS: usize = 10;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Unit {
pub name: UnitName,
pub exponent: i32,
}
macro_rules! unit {
($name:ident) => {
Unit {
name: UnitName::$name,
exponent: 1,
}
};
($name:ident ^ $exponent:literal) => {
Unit {
name: UnitName::$name,
exponent: $exponent,
}
};
}
impl TryFrom<&[u8]> for ValueInformationBlock {
type Error = DataInformationError;
fn try_from(data: &[u8]) -> Result<Self, DataInformationError> {
let mut vife = ArrayVec::<ValueInformationFieldExtension, MAX_VIFE_RECORDS>::new();
let vif =
ValueInformationField::from(*data.first().ok_or(DataInformationError::DataTooShort)?);
let mut plaintext_vife: Option<ArrayVec<char, 9>> = None;
#[cfg(not(feature = "plaintext-before-extension"))]
let standard_plaintex_vib = true;
#[cfg(feature = "plaintext-before-extension")]
let standard_plaintex_vib = false;
if !standard_plaintex_vib && vif.value_information_contains_ascii() {
plaintext_vife = Some(extract_plaintext_vife(
data.get(1..).ok_or(DataInformationError::DataTooShort)?,
)?);
}
if vif.has_extension() {
let mut offset = 1;
while offset < data.len() {
let vife_data = *data.get(offset).ok_or(DataInformationError::DataTooShort)?;
let current_vife = ValueInformationFieldExtension {
data: vife_data,
coding: match (offset, vife_data) {
(0, 0xFB) => ValueInformationFieldExtensionCoding::MainVIFCodeExtension,
(0, 0xFC) => {
ValueInformationFieldExtensionCoding::AlternateVIFCodeExtension
}
(0, 0xEF) => {
ValueInformationFieldExtensionCoding::ReservedAlternateVIFCodeExtension
}
_ => ValueInformationFieldExtensionCoding::ComninableOrthogonalVIFECodeExtension,
},
};
let has_extension = current_vife.has_extension();
vife.push(current_vife);
offset += 1;
if !has_extension {
break;
}
if vife.len() > MAX_VIFE_RECORDS {
return Err(DataInformationError::InvalidValueInformation);
}
}
if standard_plaintex_vib && vif.value_information_contains_ascii() {
plaintext_vife = Some(extract_plaintext_vife(
data.get(offset..)
.ok_or(DataInformationError::DataTooShort)?,
)?);
}
}
Ok(Self {
value_information: vif,
value_information_extension: if vife.is_empty() { None } else { Some(vife) },
plaintext_vife,
})
}
}
fn extract_plaintext_vife(data: &[u8]) -> Result<ArrayVec<char, 9>, DataInformationError> {
let ascii_length = *data.first().ok_or(DataInformationError::DataTooShort)? as usize;
let mut ascii = ArrayVec::<char, 9>::new();
for item in data
.get(1..=ascii_length)
.ok_or(DataInformationError::DataTooShort)?
{
ascii.push(*item as char);
}
Ok(ascii)
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ValueInformationBlock {
pub value_information: ValueInformationField,
pub value_information_extension:
Option<ArrayVec<ValueInformationFieldExtension, MAX_VIFE_RECORDS>>,
pub plaintext_vife: Option<ArrayVec<char, 9>>,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ValueInformationField {
pub data: u8,
}
impl ValueInformationField {
const fn value_information_contains_ascii(&self) -> bool {
self.data == 0x7C || self.data == 0xFC
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ValueInformationFieldExtension {
pub data: u8,
pub coding: ValueInformationFieldExtensionCoding,
}
impl From<&ValueInformationField> for ValueInformationCoding {
fn from(value_information: &ValueInformationField) -> Self {
match value_information.data {
0x00..=0x7B | 0x80..=0xFA => Self::Primary,
0x7C | 0xFC => Self::PlainText,
0xFD => Self::MainVIFExtension,
0xFB => Self::AlternateVIFExtension,
0x7E => Self::ManufacturerSpecific,
0xFE => Self::ManufacturerSpecific,
0x7F => Self::ManufacturerSpecific,
0xFF => Self::ManufacturerSpecific,
_ => unreachable!("Invalid value information: {:X}", value_information.data),
}
}
}
impl ValueInformationField {
const fn has_extension(&self) -> bool {
self.data & 0x80 != 0
}
}
impl ValueInformationFieldExtension {
const fn has_extension(&self) -> bool {
self.data & 0x80 != 0
}
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ValueInformationCoding {
Primary,
PlainText,
MainVIFExtension,
AlternateVIFExtension,
ManufacturerSpecific,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ValueInformationFieldExtensionCoding {
MainVIFCodeExtension,
AlternateVIFCodeExtension,
ReservedAlternateVIFCodeExtension,
ComninableOrthogonalVIFECodeExtension,
}
impl ValueInformationBlock {
#[must_use]
pub const fn get_size(&self) -> usize {
let mut size = 1;
if let Some(vife) = &self.value_information_extension {
size += vife.len();
}
if let Some(plaintext_vife) = &self.plaintext_vife {
size += plaintext_vife.len() + 1;
}
size
}
}
impl TryFrom<&ValueInformationBlock> for ValueInformation {
type Error = DataInformationError;
fn try_from(
value_information_block: &ValueInformationBlock,
) -> Result<Self, DataInformationError> {
let mut units = ArrayVec::<Unit, 10>::new();
let mut labels = ArrayVec::<ValueLabel, 10>::new();
let mut decimal_scale_exponent: isize = 0;
let mut decimal_offset_exponent = 0;
match ValueInformationCoding::from(&value_information_block.value_information) {
ValueInformationCoding::Primary => {
match value_information_block.value_information.data & 0x7F {
0x00..=0x07 => {
units.push(unit!(Watt));
units.push(unit!(Hour));
decimal_scale_exponent =
(value_information_block.value_information.data & 0b111) as isize - 3;
}
0x08..=0x0F => {
units.push(unit!(Joul));
decimal_scale_exponent =
(value_information_block.value_information.data & 0b111) as isize;
}
0x10..=0x17 => {
units.push(unit!(Meter ^ 3));
labels.push(ValueLabel::Volume);
decimal_scale_exponent =
(value_information_block.value_information.data & 0b111) as isize - 6;
}
0x18..=0x1F => {
units.push(unit!(Kilogram));
decimal_scale_exponent =
(value_information_block.value_information.data & 0b111) as isize - 3;
}
0x20 | 0x24 => {
units.push(unit!(Second));
}
0x21 | 0x25 => units.push(unit!(Meter)),
0x22 | 0x26 => units.push(unit!(Hour)),
0x23 | 0x27 => units.push(unit!(Day)),
0x28..=0x2F => {
units.push(unit!(Watt));
decimal_scale_exponent +=
(value_information_block.value_information.data & 0b111) as isize - 3;
}
0x30..=0x37 => {
units.push(unit!(Joul));
units.push(unit!(Hour ^ -1));
decimal_scale_exponent +=
(value_information_block.value_information.data & 0b111) as isize;
}
0x38..=0x3F => {
units.push(unit!(Meter ^ 3));
units.push(unit!(Hour ^ -1));
decimal_scale_exponent +=
(value_information_block.value_information.data & 0b111) as isize - 6;
}
0x40..=0x47 => {
units.push(unit!(Meter ^ 3));
units.push(unit!(Minute ^ -1));
decimal_scale_exponent +=
(value_information_block.value_information.data & 0b111) as isize - 7;
}
0x48..=0x4F => {
units.push(unit!(Meter ^ 3));
units.push(unit!(Second ^ -1));
decimal_scale_exponent +=
(value_information_block.value_information.data & 0b111) as isize - 9;
}
0x50..=0x57 => {
units.push(unit!(Kilogram ^ 3));
units.push(unit!(Hour ^ -1));
decimal_scale_exponent +=
(value_information_block.value_information.data & 0b111) as isize - 3;
}
0x58..=0x5F | 0x64..=0x67 => {
units.push(unit!(Celsius));
decimal_scale_exponent +=
(value_information_block.value_information.data & 0b11) as isize - 3;
}
0x60..=0x63 => {
units.push(unit!(Kelvin));
decimal_scale_exponent +=
(value_information_block.value_information.data & 0b11) as isize - 3;
}
0x68..=0x6B => {
units.push(unit!(Bar));
decimal_scale_exponent +=
(value_information_block.value_information.data & 0b11) as isize - 3;
}
0x6C => labels.push(ValueLabel::Date),
0x6D => labels.push(ValueLabel::DateTime),
0x6E => labels.push(ValueLabel::DimensionlessHCA),
0x70..=0x73 => labels.push(ValueLabel::AveragingDuration),
0x74..=0x77 => labels.push(ValueLabel::ActualityDuration),
0x78 => labels.push(ValueLabel::FabricationNumber),
0x79 => labels.push(ValueLabel::EnhancedIdentification),
0x7A => labels.push(ValueLabel::Address),
0x7B => {}
x => todo!("Implement the rest of the units: {:X?}", x),
};
consume_orthhogonal_vife(
value_information_block,
&mut labels,
&mut units,
&mut decimal_scale_exponent,
&mut decimal_offset_exponent,
);
}
ValueInformationCoding::MainVIFExtension => {
let vife = value_information_block
.value_information_extension
.as_ref()
.ok_or(Self::Error::InvalidValueInformation)?;
let first_vife_data = vife.first().ok_or(DataInformationError::DataTooShort)?.data;
let second_vife_data = vife.get(1).map(|s| s.data);
match first_vife_data & 0x7F {
0x00..=0x03 => {
units.push(unit!(LocalMoneyCurrency));
labels.push(ValueLabel::Credit);
decimal_scale_exponent = (first_vife_data & 0b11) as isize - 3;
}
0x04..=0x07 => {
units.push(unit!(LocalMoneyCurrency));
labels.push(ValueLabel::Debit);
decimal_scale_exponent = (first_vife_data & 0b11) as isize - 3;
}
0x08 => labels.push(ValueLabel::UniqueMessageIdentificationOrAccessNumber),
0x09 => labels.push(ValueLabel::DeviceType),
0x0A => labels.push(ValueLabel::Manufacturer),
0x0B => labels.push(ValueLabel::ParameterSetIdentification),
0x0C => labels.push(ValueLabel::ModelOrVersion),
0x0D => labels.push(ValueLabel::HardwareVersion),
0x0E => labels.push(ValueLabel::MetrologyFirmwareVersion),
0x0F => labels.push(ValueLabel::OtherSoftwareVersion),
0x10 => labels.push(ValueLabel::CustomerLocation),
0x11 => labels.push(ValueLabel::Customer),
0x12 => labels.push(ValueLabel::AccessCodeUser),
0x13 => labels.push(ValueLabel::AccessCodeOperator),
0x14 => labels.push(ValueLabel::AccessCodeSystemOperator),
0x15 => labels.push(ValueLabel::AccessCodeDeveloper),
0x16 => labels.push(ValueLabel::Password),
0x17 => labels.push(ValueLabel::ErrorFlags),
0x18 => labels.push(ValueLabel::ErrorMask),
0x19 => labels.push(ValueLabel::SecurityKey),
0x1A => {
labels.push(ValueLabel::DigitalOutput);
labels.push(ValueLabel::Binary);
}
0x1B => {
labels.push(ValueLabel::DigitalInput);
labels.push(ValueLabel::Binary);
}
0x1C => {
units.push(unit!(Symbol));
units.push(unit!(Second ^ -1));
labels.push(ValueLabel::BaudRate);
}
0x1D => {
units.push(unit!(BitTime));
labels.push(ValueLabel::ResponseDelayTime);
}
0x1E => labels.push(ValueLabel::Retry),
0x1F => labels.push(ValueLabel::RemoteControl),
0x20 => labels.push(ValueLabel::FirstStorageForCycleStorage),
0x21 => labels.push(ValueLabel::LastStorageForCycleStorage),
0x22 => labels.push(ValueLabel::SizeOfStorageBlock),
0x23 => labels.push(ValueLabel::DescripitonOfTariffAndSubunit),
0x24 => {
units.push(unit!(Second));
labels.push(ValueLabel::StorageInterval);
}
0x25 => {
units.push(unit!(Minute));
labels.push(ValueLabel::StorageInterval);
}
0x26 => {
units.push(unit!(Hour));
labels.push(ValueLabel::StorageInterval);
}
0x27 => {
units.push(unit!(Day));
labels.push(ValueLabel::StorageInterval);
}
0x28 => {
units.push(unit!(Month));
labels.push(ValueLabel::StorageInterval);
}
0x29 => {
units.push(unit!(Year));
labels.push(ValueLabel::StorageInterval);
}
0x30 => labels.push(ValueLabel::DimensionlessHCA),
0x31 => labels.push(ValueLabel::DataContainerForWmbusProtocol),
0x32 => {
units.push(unit!(Second));
labels.push(ValueLabel::PeriodOfNormalDataTransmition);
}
0x33 => {
units.push(unit!(Meter));
labels.push(ValueLabel::PeriodOfNormalDataTransmition);
}
0x34 => {
units.push(unit!(Hour));
labels.push(ValueLabel::PeriodOfNormalDataTransmition);
}
0x35 => {
units.push(unit!(Day));
labels.push(ValueLabel::PeriodOfNormalDataTransmition);
}
0x50..=0x5F => {
units.push(unit!(Volt));
decimal_scale_exponent = (first_vife_data & 0b1111) as isize - 9;
}
0x60 => labels.push(ValueLabel::ResetCounter),
0x61 => labels.push(ValueLabel::CumulationCounter),
0x62 => labels.push(ValueLabel::ControlSignal),
0x63 => labels.push(ValueLabel::DayOfWeek),
0x64 => labels.push(ValueLabel::WeekNumber),
0x65 => labels.push(ValueLabel::TimePointOfChangeOfTariff),
0x66 => labels.push(ValueLabel::StateOfParameterActivation),
0x67 => labels.push(ValueLabel::SpecialSupplierInformation),
0x68 => {
units.push(unit!(Hour));
labels.push(ValueLabel::DurationSinceLastCumulation);
}
0x69 => {
units.push(unit!(Day));
labels.push(ValueLabel::DurationSinceLastCumulation);
}
0x6A => {
units.push(unit!(Month));
labels.push(ValueLabel::DurationSinceLastCumulation);
}
0x6B => {
units.push(unit!(Year));
labels.push(ValueLabel::DurationSinceLastCumulation);
}
0x6C => {
units.push(unit!(Hour));
labels.push(ValueLabel::OperatingTimeBattery);
}
0x6D => {
units.push(unit!(Day));
labels.push(ValueLabel::OperatingTimeBattery);
}
0x6E => {
units.push(unit!(Month));
labels.push(ValueLabel::OperatingTimeBattery);
}
0x6F => {
units.push(unit!(Hour));
labels.push(ValueLabel::OperatingTimeBattery);
}
0x70 => {
units.push(unit!(Second));
labels.push(ValueLabel::DateAndTimeOfBatteryChange);
}
0x71 => {
units.push(unit!(DecibelMilliWatt));
labels.push(ValueLabel::RFPowerLevel);
}
0x72 => labels.push(ValueLabel::DaylightSavingBeginningEndingDeviation),
0x73 => labels.push(ValueLabel::ListeningWindowManagementData),
0x74 => labels.push(ValueLabel::RemainingBatteryLifeTime),
0x75 => labels.push(ValueLabel::NumberOfTimesTheMeterWasStopped),
0x76 => labels.push(ValueLabel::DataContainerForManufacturerSpecificProtocol),
0x7D => match second_vife_data.map(|s| s & 0x7F) {
Some(0x00) => labels.push(ValueLabel::CurrentlySelectedApplication),
Some(0x02) => {
units.push(unit!(Month));
labels.push(ValueLabel::RemainingBatteryLifeTime);
}
Some(0x03) => {
units.push(unit!(Year));
labels.push(ValueLabel::RemainingBatteryLifeTime);
}
_ => labels.push(ValueLabel::Reserved),
},
_ => labels.push(ValueLabel::Reserved),
}
}
ValueInformationCoding::AlternateVIFExtension => {
use UnitName::*;
use ValueLabel::*;
let mk_unit = |name, exponent| Unit { name, exponent };
macro_rules! populate {
(@trd) => {};
(@trd , $label:expr) => {{ labels.push($label); }};
(@snd dec: $decimal:literal $($rem:tt)*) => {{
decimal_scale_exponent = $decimal;
populate!(@trd $($rem)*);
}};
($name:ident / h, $exponent:expr, $($rem:tt)*) => {{
units.push(mk_unit($name, $exponent));
units.push(mk_unit(Hour, -1));
populate!(@snd $($rem)*)
}};
($name:ident * h, $exponent:expr, $($rem:tt)*) => {{
units.push(mk_unit($name, $exponent));
units.push(mk_unit(Hour, 1));
populate!(@snd $($rem)*)
}};
($name:ident, $exponent:expr, $($rem:tt)*) => {{
units.push(mk_unit($name, $exponent));
populate!(@snd $($rem)*)
}};
}
let vife = value_information_block
.value_information_extension
.as_ref()
.ok_or(Self::Error::InvalidValueInformation)?;
let first_vife_data = vife.first().ok_or(DataInformationError::DataTooShort)?.data;
match first_vife_data & 0x7F {
0b0 => populate!(Watt / h, 3, dec: 5, Energy),
0b000_0001 => populate!(Watt / h, 3, dec: 6, Energy),
0b000_0010 => populate!(ReactiveWatt * h, 1, dec: 3, Energy),
0b000_0011 => populate!(ReactiveWatt * h, 1, dec: 4, Energy),
0b000_1000 => populate!(Joul, 1, dec: 8, Energy),
0b000_1001 => populate!(Joul, 1, dec: 9, Energy),
0b000_1100 => populate!(Joul, 1, dec: 5, Energy),
0b000_1101 => populate!(Joul, 1, dec: 6, Energy),
0b000_1110 => populate!(Joul, 1, dec: 7, Energy),
0b000_1111 => populate!(Joul, 1, dec: 8, Energy),
0b001_0000 => populate!(Meter, 3, dec: 2),
0b001_0001 => populate!(Meter, 3, dec: 3),
0b001_0100 => populate!(ReactiveWatt, 1, dec: -3),
0b001_0101 => populate!(ReactiveWatt, 1, dec: -2),
0b001_0110 => populate!(ReactiveWatt, 1, dec: -1),
0b001_0111 => populate!(ReactiveWatt, 1, dec: 0),
0b001_1000 => populate!(Tonne, 1, dec: 2),
0b001_1001 => populate!(Tonne, 1, dec: 3),
0b001_1010 => populate!(Percent, 1, dec: -1, RelativeHumidity),
0b010_0000 => populate!(Feet, 3, dec: 0),
0b010_0001 => populate!(Feet, 3, dec: 1),
0b010_1000 => populate!(Watt, 1, dec: 5),
0b010_1001 => populate!(Watt, 1, dec: 6),
0b010_1010 => populate!(Degree, 1, dec: -1, PhaseUtoU),
0b010_1011 => populate!(Degree, 1, dec: -1, PhaseUtoI),
0b010_1100 => populate!(Hertz, 1, dec: -3),
0b010_1101 => populate!(Hertz, 1, dec: -2),
0b010_1110 => populate!(Hertz, 1, dec: -1),
0b010_1111 => populate!(Hertz, 1, dec: 0),
0b011_0000 => populate!(Joul / h, 1, dec: -8),
0b011_0001 => populate!(Joul / h, 1, dec: -7),
0b011_0100 => populate!(ApparentWatt / h, 1, dec: 0),
0b011_0101 => populate!(ApparentWatt / h, 1, dec: 1),
0b011_0110 => populate!(ApparentWatt / h, 1, dec: 2),
0b011_0111 => populate!(ApparentWatt / h, 1, dec: 3),
0b111_0100 => populate!(Celsius, 1, dec: -3, ColdWarmTemperatureLimit),
0b111_0101 => populate!(Celsius, 1, dec: -2, ColdWarmTemperatureLimit),
0b111_0110 => populate!(Celsius, 1, dec: -1, ColdWarmTemperatureLimit),
0b111_0111 => populate!(Celsius, 1, dec: 0, ColdWarmTemperatureLimit),
0b111_1000 => populate!(Watt, 1, dec: -3, CumaltiveMaximumOfActivePower),
0b111_1001 => populate!(Watt, 1, dec: -2, CumaltiveMaximumOfActivePower),
0b111_1010 => populate!(Watt, 1, dec: -1, CumaltiveMaximumOfActivePower),
0b111_1011 => populate!(Watt, 1, dec: 0, CumaltiveMaximumOfActivePower),
0b111_1100 => populate!(Watt, 1, dec: 1, CumaltiveMaximumOfActivePower),
0b111_1101 => populate!(Watt, 1, dec: 2, CumaltiveMaximumOfActivePower),
0b111_1110 => populate!(Watt, 1, dec: 3, CumaltiveMaximumOfActivePower),
0b111_1111 => populate!(Watt, 1, dec: 4, CumaltiveMaximumOfActivePower),
0b110_1000 => populate!(HCAUnit, 1,dec: 0, ResultingRatingFactor),
0b110_1001 => populate!(HCAUnit, 1,dec: 0, ThermalOutputRatingFactor),
0b110_1010 => populate!(HCAUnit, 1,dec: 0, ThermalCouplingRatingFactorOverall),
0b110_1011 => populate!(HCAUnit, 1,dec: 0, ThermalCouplingRatingRoomSide),
0b110_1100 => {
populate!(HCAUnit, 1,dec: 0, ThermalCouplingRatingFactorHeatingSide)
}
0b110_1101 => populate!(HCAUnit, 1,dec: 0, LowTemperatureRatingFactor),
0b110_1110 => populate!(HCAUnit, 1,dec: 0, DisplayOutputScalingFactor),
_ => todo!("Implement the rest of the units: {:X?}", first_vife_data),
};
}
ValueInformationCoding::PlainText => labels.push(ValueLabel::PlainText),
ValueInformationCoding::ManufacturerSpecific => {
labels.push(ValueLabel::ManufacturerSpecific)
}
}
Ok(Self {
decimal_offset_exponent,
labels,
decimal_scale_exponent,
units,
})
}
}
fn consume_orthhogonal_vife(
value_information_block: &ValueInformationBlock,
labels: &mut ArrayVec<ValueLabel, 10>,
units: &mut ArrayVec<Unit, 10>,
decimal_scale_exponent: &mut isize,
decimal_offset_exponent: &mut isize,
) {
if let Some(vife) = &value_information_block.value_information_extension {
let mut is_extension_of_combinable_orthogonal_vife = false;
for v in vife {
if v.data == 0xFC {
is_extension_of_combinable_orthogonal_vife = true;
continue;
}
if is_extension_of_combinable_orthogonal_vife {
is_extension_of_combinable_orthogonal_vife = false;
match v.data & 0x7F {
0x00 => labels.push(ValueLabel::Reserved),
0x01 => labels.push(ValueLabel::AtPhaseL1),
0x02 => labels.push(ValueLabel::AtPhaseL2),
0x03 => labels.push(ValueLabel::AtPhaseL3),
0x04 => labels.push(ValueLabel::AtNeutral),
0x05 => labels.push(ValueLabel::BetweenPhasesL1L2),
0x06 => labels.push(ValueLabel::BetweenPhasesL2L3),
0x07 => labels.push(ValueLabel::BetweenPhasesL3L1),
0x08 => labels.push(ValueLabel::AtQuadrant1),
0x09 => labels.push(ValueLabel::AtQuadrant2),
0x0A => labels.push(ValueLabel::AtQuadrant3),
0x0B => labels.push(ValueLabel::AtQuadrant4),
0x0C => labels.push(ValueLabel::DeltaBetweenImportAndExport),
0x0F => labels.push(
ValueLabel::AccumulationOfAbsoluteValueBothPositiveAndNegativeContribution,
),
0x11 => labels.push(ValueLabel::DataPresentedWithTypeC),
0x12 => labels.push(ValueLabel::DataPresentedWithTypeD),
0x13 => labels.push(ValueLabel::DirectionFromCommunicationPartnerToMeter),
0x14 => labels.push(ValueLabel::DirectionFromMeterToCommunicationPartner),
_ => labels.push(ValueLabel::Reserved),
}
} else {
match v.data & 0x7F {
0x00..=0x0F => labels.push(ValueLabel::ReservedForObjectActions),
0x10..=0x11 => labels.push(ValueLabel::Reserved),
0x12 => labels.push(ValueLabel::Averaged),
0x13 => labels.push(ValueLabel::InverseCompactProfile),
0x14 => labels.push(ValueLabel::RelativeDeviation),
0x15..=0x1C => labels.push(ValueLabel::RecoordErrorCodes),
0x1D => labels.push(ValueLabel::StandardConformDataContent),
0x1E => labels.push(ValueLabel::CompactProfileWithRegisterNumbers),
0x1F => labels.push(ValueLabel::CompactProfile),
0x20 => units.push(unit!(Second ^ -1)),
0x21 => units.push(unit!(Minute ^ -1)),
0x22 => units.push(unit!(Hour ^ -1)),
0x23 => units.push(unit!(Day ^ -1)),
0x24 => units.push(unit!(Week ^ -1)),
0x25 => units.push(unit!(Month ^ -1)),
0x26 => units.push(unit!(Year ^ -1)),
0x27 => units.push(unit!(Revolution ^ -1)),
0x28 => {
units.push(unit!(Increment));
units.push(unit!(InputPulseOnChannel0 ^ -1));
}
0x29 => {
units.push(unit!(Increment));
units.push(unit!(OutputPulseOnChannel0 ^ -1));
}
0x2A => {
units.push(unit!(Increment));
units.push(unit!(InputPulseOnChannel1 ^ -1));
}
0x2B => {
units.push(unit!(Increment));
units.push(unit!(OutputPulseOnChannel1 ^ -1));
}
0x2C => units.push(unit!(Liter)),
0x2D => units.push(unit!(Meter ^ -3)),
0x2E => units.push(unit!(Kilogram ^ -1)),
0x2F => units.push(unit!(Kelvin ^ -1)),
0x30 => {
units.push(unit!(Watt ^ -1));
units.push(unit!(Hour ^ -1));
*decimal_scale_exponent -= 3;
}
0x31 => {
units.push(unit!(Joul ^ -1));
*decimal_scale_exponent += -9;
}
0x32 => {
units.push(unit!(Watt ^ -1));
*decimal_scale_exponent += -3;
}
0x33 => {
units.push(unit!(Kelvin ^ -1));
units.push(unit!(Liter ^ -1));
}
0x34 => units.push(unit!(Volt ^ -1)),
0x35 => units.push(unit!(Ampere ^ -1)),
0x36 => units.push(unit!(Second ^ 1)),
0x37 => {
units.push(unit!(Second ^ 1));
units.push(unit!(Volt ^ -1));
}
0x38 => {
units.push(unit!(Second ^ 1));
units.push(unit!(Ampere ^ -1));
}
0x39 => labels.push(ValueLabel::StartDateOf),
0x3A => labels.push(ValueLabel::VifContinsUncorrectedUnitOrValue),
0x3B => labels.push(ValueLabel::AccumulationOnlyIfValueIsPositive),
0x3C => labels.push(ValueLabel::AccumulationOnlyIfValueIsNegative),
0x3D => labels.push(ValueLabel::NoneMetricUnits),
0x3E => labels.push(ValueLabel::ValueAtBaseConditions),
0x3F => labels.push(ValueLabel::ObisDecleration),
0x40 => labels.push(ValueLabel::UpperLimitValue),
0x48 => labels.push(ValueLabel::LowerLimitValue),
0x41 => labels.push(ValueLabel::NumberOfExceedsOfUpperLimitValue),
0x49 => labels.push(ValueLabel::NumberOfExceedsOfLowerLimitValue),
0x42 => labels.push(ValueLabel::DateOfBeginFirstLowerLimitExceed),
0x43 => labels.push(ValueLabel::DateOfBeginFirstUpperLimitExceed),
0x46 => labels.push(ValueLabel::DateOfBeginLastLowerLimitExceed),
0x47 => labels.push(ValueLabel::DateOfBeginLastUpperLimitExceed),
0x4A => labels.push(ValueLabel::DateOfEndLastLowerLimitExceed),
0x4B => labels.push(ValueLabel::DateOfEndLastUpperLimitExceed),
0x4E => labels.push(ValueLabel::DateOfEndFirstLowerLimitExceed),
0x4F => labels.push(ValueLabel::DateOfEndFirstUpperLimitExceed),
0x50 => {
labels.push(ValueLabel::DurationOfFirstLowerLimitExceed);
units.push(unit!(Second));
}
0x51 => {
labels.push(ValueLabel::DurationOfFirstLowerLimitExceed);
units.push(unit!(Minute));
}
0x52 => {
labels.push(ValueLabel::DurationOfFirstLowerLimitExceed);
units.push(unit!(Hour));
}
0x53 => {
labels.push(ValueLabel::DurationOfFirstLowerLimitExceed);
units.push(unit!(Day));
}
0x54 => {
labels.push(ValueLabel::DurationOfFirstUpperLimitExceed);
units.push(unit!(Second));
}
0x55 => {
labels.push(ValueLabel::DurationOfFirstUpperLimitExceed);
units.push(unit!(Minute));
}
0x56 => {
labels.push(ValueLabel::DurationOfFirstUpperLimitExceed);
units.push(unit!(Hour));
}
0x57 => {
labels.push(ValueLabel::DurationOfFirstUpperLimitExceed);
units.push(unit!(Day));
}
0x58 => {
labels.push(ValueLabel::DurationOfLastLowerLimitExceed);
units.push(unit!(Second));
}
0x59 => {
labels.push(ValueLabel::DurationOfLastLowerLimitExceed);
units.push(unit!(Minute));
}
0x5A => {
labels.push(ValueLabel::DurationOfLastLowerLimitExceed);
units.push(unit!(Hour));
}
0x5B => {
labels.push(ValueLabel::DurationOfLastLowerLimitExceed);
units.push(unit!(Day));
}
0x5C => {
labels.push(ValueLabel::DurationOfLastUpperLimitExceed);
units.push(unit!(Second));
}
0x5D => {
labels.push(ValueLabel::DurationOfLastUpperLimitExceed);
units.push(unit!(Minute));
}
0x5E => {
labels.push(ValueLabel::DurationOfLastUpperLimitExceed);
units.push(unit!(Hour));
}
0x5F => {
labels.push(ValueLabel::DurationOfLastUpperLimitExceed);
units.push(unit!(Day));
}
0x60 => {
labels.push(ValueLabel::DurationOfFirst);
units.push(unit!(Second));
}
0x61 => {
labels.push(ValueLabel::DurationOfFirst);
units.push(unit!(Minute));
}
0x62 => {
labels.push(ValueLabel::DurationOfFirst);
units.push(unit!(Hour));
}
0x63 => {
labels.push(ValueLabel::DurationOfFirst);
units.push(unit!(Day));
}
0x64 => {
labels.push(ValueLabel::DurationOfLast);
units.push(unit!(Second));
}
0x65 => {
labels.push(ValueLabel::DurationOfLast);
units.push(unit!(Minute));
}
0x66 => {
labels.push(ValueLabel::DurationOfLast);
units.push(unit!(Day));
}
0x68 => labels.push(ValueLabel::ValueDuringLowerValueExeed),
0x6C => labels.push(ValueLabel::ValueDuringUpperValueExceed),
0x69 => labels.push(ValueLabel::LeakageValues),
0x6D => labels.push(ValueLabel::OverflowValues),
0x6A => labels.push(ValueLabel::DateOfBeginFirst),
0x6B => labels.push(ValueLabel::DateOfBeginLast),
0x6E => labels.push(ValueLabel::DateOfEndLast),
0x6F => labels.push(ValueLabel::DateOfEndFirst),
0x70..=0x77 => {
*decimal_scale_exponent += (v.data & 0b111) as isize - 6;
}
0x78..=0x7B => {
*decimal_offset_exponent += (v.data & 0b11) as isize - 3;
}
0x7D => labels.push(ValueLabel::MultiplicativeCorrectionFactor103),
0x7E => labels.push(ValueLabel::FutureValue),
0x7F => {
labels.push(ValueLabel::NextVIFEAndDataOfThisBlockAreManufacturerSpecific)
}
_ => labels.push(ValueLabel::Reserved),
};
}
}
}
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ValueInformationError {
InvalidValueInformation,
}
impl From<u8> for ValueInformationField {
fn from(data: u8) -> Self {
Self { data }
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ValueInformation {
pub decimal_offset_exponent: isize,
pub labels: ArrayVec<ValueLabel, 10>,
pub decimal_scale_exponent: isize,
pub units: ArrayVec<Unit, 10>,
}
#[cfg(feature = "std")]
impl fmt::Display for ValueInformation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.decimal_offset_exponent != 0 {
write!(f, "+{})", self.decimal_offset_exponent)?;
} else {
write!(f, ")")?;
}
if self.decimal_scale_exponent != 0 {
write!(f, "e{}", self.decimal_scale_exponent)?;
}
if !self.units.is_empty() {
write!(f, "[")?;
for unit in &self.units {
write!(f, "{}", unit)?;
}
write!(f, "]")?;
}
if !self.labels.is_empty() {
write!(f, "(")?;
for (i, label) in self.labels.iter().enumerate() {
write!(f, "{:?}", label)?;
if i != self.labels.len() - 1 {
write!(f, ", ")?;
}
}
return write!(f, ")");
}
Ok(())
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ValueLabel {
Instantaneous,
ReservedForObjectActions,
Reserved,
Averaged,
Integral,
Parameter,
InverseCompactProfile,
RelativeDeviation,
RecoordErrorCodes,
StandardConformDataContent,
CompactProfileWithRegisterNumbers,
CompactProfile,
ActualityDuration,
AveragingDuration,
Date,
Time,
DateTime,
DateTimeWithSeconds,
FabricationNumber,
EnhancedIdentification,
Address,
PlainText,
RevolutionOrMeasurement,
IncrementPerInputPulseOnChannelP,
IncrementPerOutputPulseOnChannelP,
HourMinuteSecond,
DayMonthYear,
StartDateOf,
VifContinsUncorrectedUnitOrValue,
AccumulationOnlyIfValueIsPositive,
AccumulationOnlyIfValueIsNegative,
NoneMetricUnits,
ValueAtBaseConditions,
ObisDecleration,
UpperLimitValue,
LowerLimitValue,
NumberOfExceedsOfUpperLimitValue,
NumberOfExceedsOfLowerLimitValue,
DateOfBeginFirstLowerLimitExceed,
DateOfBeginFirstUpperLimitExceed,
DateOfBeginLastLowerLimitExceed,
DateOfBeginLastUpperLimitExceed,
DateOfEndLastLowerLimitExceed,
DateOfEndLastUpperLimitExceed,
DateOfEndFirstLowerLimitExceed,
DateOfEndFirstUpperLimitExceed,
DurationOfFirstLowerLimitExceed,
DurationOfFirstUpperLimitExceed,
DurationOfLastLowerLimitExceed,
DurationOfLastUpperLimitExceed,
DurationOfFirst,
DurationOfLast,
ValueDuringLowerValueExeed,
ValueDuringUpperValueExceed,
LeakageValues,
OverflowValues,
DateOfBeginLast,
DateOfBeginFirst,
DateOfEndLast,
DateOfEndFirst,
ExtensionOfCombinableOrthogonalVIFE,
MultiplicativeCorrectionFactor103,
FutureValue,
NextVIFEAndDataOfThisBlockAreManufacturerSpecific,
Credit,
Debit,
UniqueMessageIdentificationOrAccessNumber,
DeviceType,
Manufacturer,
ParameterSetIdentification,
ModelOrVersion,
HardwareVersion,
MetrologyFirmwareVersion,
OtherSoftwareVersion,
CustomerLocation,
Customer,
AccessCodeUser,
AccessCodeOperator,
AccessCodeSystemOperator,
AccessCodeDeveloper,
Password,
ErrorFlags,
ErrorMask,
SecurityKey,
DigitalInput,
DigitalOutput,
Binary,
BaudRate,
ResponseDelayTime,
Retry,
RemoteControl,
FirstStorageForCycleStorage,
LastStorageForCycleStorage,
SizeOfStorageBlock,
DescripitonOfTariffAndSubunit,
StorageInterval,
DimensionlessHCA,
DataContainerForWmbusProtocol,
PeriodOfNormalDataTransmition,
ResetCounter,
CumulationCounter,
ControlSignal,
DayOfWeek,
WeekNumber,
TimePointOfChangeOfTariff,
StateOfParameterActivation,
SpecialSupplierInformation,
DurationSinceLastCumulation,
OperatingTimeBattery,
DateAndTimeOfBatteryChange,
RFPowerLevel,
DaylightSavingBeginningEndingDeviation,
ListeningWindowManagementData,
RemainingBatteryLifeTime,
NumberOfTimesTheMeterWasStopped,
DataContainerForManufacturerSpecificProtocol,
CurrentlySelectedApplication,
Energy,
AtPhaseL1,
AtPhaseL2,
AtPhaseL3,
AtNeutral,
BetweenPhasesL1L2,
BetweenPhasesL2L3,
BetweenPhasesL3L1,
AtQuadrant1,
AtQuadrant2,
AtQuadrant3,
AtQuadrant4,
DeltaBetweenImportAndExport,
AccumulationOfAbsoluteValueBothPositiveAndNegativeContribution,
DataPresentedWithTypeC,
DataPresentedWithTypeD,
DirectionFromCommunicationPartnerToMeter,
DirectionFromMeterToCommunicationPartner,
RelativeHumidity,
PhaseUtoU,
PhaseUtoI,
ColdWarmTemperatureLimit,
CumaltiveMaximumOfActivePower,
ResultingRatingFactor,
ThermalOutputRatingFactor,
ThermalCouplingRatingFactorOverall,
ThermalCouplingRatingRoomSide,
ThermalCouplingRatingFactorHeatingSide,
LowTemperatureRatingFactor,
DisplayOutputScalingFactor,
ManufacturerSpecific,
Volume,
}
#[cfg(feature = "std")]
impl fmt::Display for Unit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let superscripts = ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
let invalid_superscript = '⁻';
match self.exponent {
1 => write!(f, "{}", self.name),
0..=9 => write!(
f,
"{}{}",
self.name,
superscripts
.get(self.exponent as usize)
.unwrap_or(&invalid_superscript)
),
10..=19 => write!(
f,
"{}{}{}",
self.name,
superscripts.get(1).unwrap_or(&invalid_superscript),
superscripts
.get(self.exponent as usize - 10)
.unwrap_or(&invalid_superscript)
),
x if (-9..0).contains(&x) => {
write!(
f,
"{}⁻{}",
self.name,
superscripts
.get((-x) as usize)
.unwrap_or(&invalid_superscript)
)
}
x if (-19..0).contains(&x) => write!(
f,
"{}⁻{}{}",
self.name,
superscripts.get(1).unwrap_or(&invalid_superscript),
superscripts
.get((-x) as usize - 10)
.unwrap_or(&invalid_superscript)
),
x => write!(f, "{}^{}", self.name, x),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum UnitName {
Watt,
ReactiveWatt,
ApparentWatt,
Joul,
Kilogram,
Tonne,
Meter,
Feet,
Celsius,
Kelvin,
Bar,
HCA,
Reserved,
WithoutUnits,
Second,
Minute,
Hour,
Day,
Week,
Month,
Year,
Revolution,
Increment,
InputPulseOnChannel0,
OutputPulseOnChannel0,
InputPulseOnChannel1,
OutputPulseOnChannel1,
Liter,
Volt,
Ampere,
LocalMoneyCurrency,
Symbol,
BitTime,
DecibelMilliWatt,
Percent,
Degree,
Hertz,
HCAUnit,
}
#[cfg(feature = "std")]
impl fmt::Display for UnitName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
UnitName::Watt => write!(f, "W"),
UnitName::ReactiveWatt => write!(f, "W (reactive)"),
UnitName::ApparentWatt => write!(f, "W (apparent)"),
UnitName::Joul => write!(f, "J"),
UnitName::Kilogram => write!(f, "Kg"),
UnitName::Tonne => write!(f, "t"),
UnitName::Meter => write!(f, "m"),
UnitName::Feet => write!(f, "ft"),
UnitName::Celsius => write!(f, "°C"),
UnitName::Kelvin => write!(f, "°K"),
UnitName::Bar => write!(f, "Bar"),
UnitName::HCA => write!(f, "HCA"),
UnitName::Reserved => write!(f, "Reserved"),
UnitName::WithoutUnits => write!(f, "-"),
UnitName::Second => write!(f, "s"),
UnitName::Minute => write!(f, "min"),
UnitName::Hour => write!(f, "h"),
UnitName::Day => write!(f, "day"),
UnitName::Week => write!(f, "week"),
UnitName::Month => write!(f, "month"),
UnitName::Year => write!(f, "year"),
UnitName::Revolution => write!(f, "revolution"),
UnitName::Increment => write!(f, "increment"),
UnitName::InputPulseOnChannel0 => write!(f, "InputPulseOnChannel0"),
UnitName::OutputPulseOnChannel0 => write!(f, "OutputPulseOnChannel0"),
UnitName::InputPulseOnChannel1 => write!(f, "InputPulseOnChannel1"),
UnitName::OutputPulseOnChannel1 => write!(f, "OutputPulseOnChannel1"),
UnitName::Liter => write!(f, "l"),
UnitName::Volt => write!(f, "A"),
UnitName::Ampere => write!(f, "A"),
UnitName::LocalMoneyCurrency => write!(f, "$ (local)"),
UnitName::Symbol => write!(f, "Symbol"),
UnitName::BitTime => write!(f, "BitTime"),
UnitName::DecibelMilliWatt => write!(f, "dBmW"),
UnitName::Percent => write!(f, "%"),
UnitName::Degree => write!(f, "°"),
UnitName::Hertz => write!(f, "Hz"),
UnitName::HCAUnit => write!(f, "HCAUnit"),
}
}
}
mod tests {
#[test]
fn test_single_byte_primary_value_information_parsing() {
use crate::user_data::value_information::UnitName;
use crate::user_data::value_information::{
Unit, ValueInformation, ValueInformationBlock, ValueInformationField, ValueLabel,
};
use arrayvec::ArrayVec;
let data = [0x13];
let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
assert_eq!(
result,
ValueInformationBlock {
value_information: ValueInformationField::from(0x13),
value_information_extension: None,
plaintext_vife: None
}
);
assert_eq!(result.get_size(), 1);
assert_eq!(
ValueInformation::try_from(&result).unwrap(),
ValueInformation {
decimal_offset_exponent: 0,
decimal_scale_exponent: -3,
units: {
let mut x = ArrayVec::<Unit, 10>::new();
x.push(unit!(Meter ^ 3));
x
},
labels: {
let mut x = ArrayVec::<ValueLabel, 10>::new();
x.push(ValueLabel::Volume);
x
}
}
);
let data = [0x14];
let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
assert_eq!(
result,
ValueInformationBlock {
value_information: ValueInformationField::from(0x14),
value_information_extension: None,
plaintext_vife: None
}
);
assert_eq!(result.get_size(), 1);
assert_eq!(
ValueInformation::try_from(&result).unwrap(),
ValueInformation {
decimal_offset_exponent: 0,
decimal_scale_exponent: -2,
units: {
let mut x = ArrayVec::<Unit, 10>::new();
x.push(unit!(Meter ^ 3));
x
},
labels: {
let mut x = ArrayVec::<ValueLabel, 10>::new();
x.push(ValueLabel::Volume);
x
}
}
);
let data = [0x15];
let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
assert_eq!(
result,
ValueInformationBlock {
value_information: ValueInformationField::from(0x15),
value_information_extension: None,
plaintext_vife: None
}
);
assert_eq!(result.get_size(), 1);
assert_eq!(
ValueInformation::try_from(&result).unwrap(),
ValueInformation {
decimal_offset_exponent: 0,
decimal_scale_exponent: -1,
units: {
let mut x = ArrayVec::<Unit, 10>::new();
x.push(unit!(Meter ^ 3));
x
},
labels: {
let mut x = ArrayVec::<ValueLabel, 10>::new();
x.push(ValueLabel::Volume);
x
}
}
);
let data = [0x16];
let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
assert_eq!(
result,
ValueInformationBlock {
value_information: ValueInformationField::from(0x16),
value_information_extension: None,
plaintext_vife: None
},
);
assert_eq!(result.get_size(), 1);
}
#[test]
fn test_multibyte_primary_value_information() {
use crate::user_data::value_information::UnitName;
use crate::user_data::value_information::{
Unit, ValueInformation, ValueInformationBlock, ValueInformationField, ValueLabel,
};
use arrayvec::ArrayVec;
let data = [0x96, 0x12];
let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
assert_eq!(result.get_size(), 2);
assert_eq!(result.value_information, ValueInformationField::from(0x96));
assert_eq!(ValueInformation::try_from(&result).unwrap().labels, {
let mut x = ArrayVec::<ValueLabel, 10>::new();
x.push(ValueLabel::Volume);
x.push(ValueLabel::Averaged);
x
});
let data = [0x96, 0x92, 0x20];
let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
assert_eq!(result.get_size(), 3);
assert_eq!(result.value_information, ValueInformationField::from(0x96));
assert_eq!(
ValueInformation::try_from(&result).unwrap(),
ValueInformation {
labels: {
let mut x = ArrayVec::<ValueLabel, 10>::new();
x.push(ValueLabel::Volume);
x.push(ValueLabel::Averaged);
x
},
decimal_offset_exponent: 0,
decimal_scale_exponent: 0,
units: {
let mut x = ArrayVec::<Unit, 10>::new();
x.push(unit!(Meter ^ 3));
x.push(unit!(Second ^ -1));
x
}
}
);
let data = [0x96, 0x92, 0xA0, 0x2D];
let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
assert_eq!(result.get_size(), 4);
assert_eq!(result.value_information, ValueInformationField::from(0x96));
assert_eq!(
ValueInformation::try_from(&result).unwrap(),
ValueInformation {
labels: {
let mut x = ArrayVec::<ValueLabel, 10>::new();
x.push(ValueLabel::Volume);
x.push(ValueLabel::Averaged);
x
},
decimal_offset_exponent: 0,
decimal_scale_exponent: 0,
units: {
let mut x = ArrayVec::<Unit, 10>::new();
x.push(unit!(Meter ^ 3));
x.push(unit!(Second ^ -1));
x.push(unit!(Meter ^ -3));
x
}
}
);
}
#[cfg(not(feature = "plaintext-before-extension"))]
#[test]
fn test_plain_text_vif_norm_conform() {
use arrayvec::ArrayVec;
use crate::user_data::value_information::{Unit, ValueInformation, ValueLabel};
use crate::user_data::value_information::ValueInformationBlock;
let data = [0xFC, 0x74, 0x03, 0x52, 0x48, 0x25];
let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
assert_eq!(result.get_size(), 6);
assert_eq!(result.value_information.data, 0xFC);
assert_eq!(
ValueInformation::try_from(&result).unwrap(),
ValueInformation {
decimal_offset_exponent: 0,
decimal_scale_exponent: 0,
units: { ArrayVec::<Unit, 10>::new() },
labels: {
let mut x = ArrayVec::<ValueLabel, 10>::new();
x.push(ValueLabel::PlainText);
x
}
}
);
}
#[test]
fn test_short_vif_with_vife() {
use crate::user_data::value_information::ValueInformationBlock;
let data = [253, 27];
let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
assert_eq!(result.get_size(), 2);
}
}