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