1#[cfg(feature = "std")]
2use std::fmt;
3
4use super::data_information::DataInformationError;
5
6#[derive(Clone, Copy, Debug, PartialEq)]
7struct VifInfo {
8 labels: &'static [ValueLabel],
9 units: &'static [Unit],
10 scale: isize,
11 offset: isize,
12}
13impl VifInfo {
14 const EMPTY: Self = Self {
15 labels: &[],
16 units: &[],
17 scale: 0,
18 offset: 0,
19 };
20}
21macro_rules! labels {
22 ($($label:expr),+ $(,)?) => { VifInfo { labels: &[$($label),+], ..VifInfo::EMPTY } };
23}
24macro_rules! units {
25 ($($unit:expr),+ $(,)?) => { VifInfo { units: &[$($unit),+], ..VifInfo::EMPTY } };
26}
27
28const MAX_VIFE_RECORDS: usize = 10;
29
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31#[derive(Debug, PartialEq, Copy, Clone)]
32#[cfg_attr(feature = "defmt", derive(defmt::Format))]
33pub struct Unit {
34 pub name: UnitName,
35 pub exponent: i32,
36}
37macro_rules! unit {
38 ($name:ident) => {
39 Unit {
40 name: UnitName::$name,
41 exponent: 1,
42 }
43 };
44 ($name:ident ^ $exponent:literal) => {
45 Unit {
46 name: UnitName::$name,
47 exponent: $exponent,
48 }
49 };
50}
51
52impl<'a> TryFrom<&'a [u8]> for ValueInformationBlock<'a> {
53 type Error = DataInformationError;
54
55 fn try_from(data: &'a [u8]) -> Result<Self, DataInformationError> {
56 let vif =
57 ValueInformationField::from(*data.first().ok_or(DataInformationError::DataTooShort)?);
58 let mut offset = 1;
59 let mut value_information_extension = None;
60 let mut plaintext_vife = None;
61
62 #[cfg(feature = "plaintext-before-extension")]
63 if vif.value_information_contains_ascii() {
64 let plaintext = PlainTextValueInformationExtension::new(
65 data.get(offset..)
66 .ok_or(DataInformationError::DataTooShort)?,
67 )?;
68 offset += plaintext.ascii_len() + 1;
69 plaintext_vife = Some(plaintext);
70 }
71
72 if vif.has_extension() {
73 let extensions = ValueInformationFieldExtensions::new(
76 data.get(offset..)
77 .ok_or(DataInformationError::DataTooShort)?,
78 )?;
79 #[cfg(not(feature = "plaintext-before-extension"))]
80 {
81 offset += extensions.len();
82 }
83 value_information_extension = Some(extensions);
84 }
85
86 #[cfg(not(feature = "plaintext-before-extension"))]
87 if vif.value_information_contains_ascii() {
88 plaintext_vife = Some(PlainTextValueInformationExtension::new(
89 data.get(offset..)
90 .ok_or(DataInformationError::DataTooShort)?,
91 )?);
92 }
93
94 Ok(Self {
95 value_information: vif,
96 value_information_extension,
97 plaintext_vife,
98 })
99 }
100}
101
102#[cfg_attr(feature = "serde", derive(serde::Serialize))]
103#[derive(Debug, PartialEq, Clone)]
104pub struct ValueInformationBlock<'a> {
105 pub value_information: ValueInformationField,
106 pub value_information_extension: Option<ValueInformationFieldExtensions<'a>>,
107 pub plaintext_vife: Option<PlainTextValueInformationExtension<'a>>,
108}
109
110#[cfg(feature = "defmt")]
111impl<'a> defmt::Format for ValueInformationBlock<'a> {
112 fn format(&self, f: defmt::Formatter) {
113 defmt::write!(
114 f,
115 "ValueInformationBlock{{ value_information: {:?}",
116 self.value_information
117 );
118 if let Some(ext) = &self.value_information_extension {
119 defmt::write!(f, ", value_information_extension: [");
120 ext.iter().for_each(|x| defmt::write!(f, "{},", x));
121 defmt::write!(f, "]");
122 }
123 if let Some(text) = &self.plaintext_vife {
124 defmt::write!(f, ", plaintext_vife: {}", text.as_ascii_str());
125 }
126 defmt::write!(f, " }}");
127 }
128}
129#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
130#[derive(Debug, PartialEq, Clone)]
131#[cfg_attr(feature = "defmt", derive(defmt::Format))]
132pub struct ValueInformationField {
133 pub data: u8,
134}
135
136impl ValueInformationField {
137 const fn value_information_contains_ascii(&self) -> bool {
138 self.data == 0x7C || self.data == 0xFC
139 }
140}
141
142#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
143#[derive(Clone, Debug, PartialEq)]
144#[cfg_attr(feature = "defmt", derive(defmt::Format))]
145pub struct ValueInformationFieldExtensions<'a>(&'a [u8]);
146
147#[cfg(feature = "serde")]
148impl serde::Serialize for ValueInformationFieldExtensions<'_> {
149 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
150 where
151 S: serde::Serializer,
152 {
153 serializer.collect_seq(self.iter())
154 }
155}
156
157impl<'a> ValueInformationFieldExtensions<'a> {
158 fn new(data: &'a [u8]) -> Result<Self, DataInformationError> {
159 let Some(last_index) = data
160 .iter()
161 .take(MAX_VIFE_RECORDS + 1)
162 .position(|byte| byte & 0x80 == 0)
163 else {
164 return Err(if data.len() > MAX_VIFE_RECORDS {
165 DataInformationError::InvalidValueInformation
166 } else {
167 DataInformationError::DataTooShort
168 });
169 };
170
171 let length = last_index + 1;
172 if length > MAX_VIFE_RECORDS {
173 return Err(DataInformationError::InvalidValueInformation);
174 }
175
176 Ok(Self(
177 data.get(..length)
178 .ok_or(DataInformationError::DataTooShort)?,
179 ))
180 }
181}
182
183impl Iterator for ValueInformationFieldExtensions<'_> {
184 type Item = ValueInformationFieldExtension;
185 fn next(&mut self) -> Option<Self::Item> {
186 let (head, tail) = self.0.split_first()?;
187 self.0 = tail;
188 Some(ValueInformationFieldExtension { data: *head })
189 }
190 fn size_hint(&self) -> (usize, Option<usize>) {
191 (self.0.len(), Some(self.0.len()))
192 }
193}
194
195impl ExactSizeIterator for ValueInformationFieldExtensions<'_> {}
196impl DoubleEndedIterator for ValueInformationFieldExtensions<'_> {
197 fn next_back(&mut self) -> Option<Self::Item> {
198 let (end, start) = self.0.split_last()?;
199 self.0 = start;
200 Some(ValueInformationFieldExtension { data: *end })
201 }
202}
203
204impl<'a> ValueInformationFieldExtensions<'a> {
205 pub fn iter(
206 &self,
207 ) -> impl DoubleEndedIterator<Item = ValueInformationFieldExtension> + ExactSizeIterator + '_
208 {
209 self.0
210 .iter()
211 .copied()
212 .map(|data| ValueInformationFieldExtension { data })
213 }
214}
215
216#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
217#[derive(Clone, Debug, PartialEq)]
218#[cfg_attr(feature = "defmt", derive(defmt::Format))]
219pub struct PlainTextValueInformationExtension<'a>(&'a [u8]);
220
221#[cfg(feature = "serde")]
222impl serde::Serialize for PlainTextValueInformationExtension<'_> {
223 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
224 where
225 S: serde::Serializer,
226 {
227 let plaintext = self.as_ascii_str().ok_or_else(|| {
228 <S::Error as serde::ser::Error>::custom("invalid plaintext VIFE encoding")
229 })?;
230
231 serializer.collect_seq(plaintext.chars())
232 }
233}
234
235impl<'a> PlainTextValueInformationExtension<'a> {
236 fn new(data: &'a [u8]) -> Result<Self, DataInformationError> {
237 let ascii_len = usize::from(*data.first().ok_or(DataInformationError::DataTooShort)?);
238
239 if ascii_len > 9 {
240 return Err(DataInformationError::InvalidValueInformation);
241 }
242
243 let encoded = data
244 .get(..ascii_len + 1)
245 .ok_or(DataInformationError::DataTooShort)?;
246
247 if !encoded[1..].is_ascii() {
248 return Err(DataInformationError::InvalidValueInformation);
249 }
250
251 Ok(Self(encoded))
252 }
253
254 pub const fn ascii_len(&self) -> usize {
255 if let Some(x) = self.0.first() {
256 *x as usize
257 } else {
258 0
259 }
260 }
261
262 pub fn as_ascii_str(&self) -> Option<&str> {
263 core::str::from_utf8(self.0.get(1..)?).ok()
264 }
265}
266
267#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
268#[derive(Debug, PartialEq, Clone)]
269#[cfg_attr(feature = "defmt", derive(defmt::Format))]
270pub struct ValueInformationFieldExtension {
271 pub data: u8,
272}
273
274impl From<&ValueInformationField> for ValueInformationCoding {
275 fn from(value_information: &ValueInformationField) -> Self {
276 match value_information.data {
277 0x00..=0x7B | 0x80..=0xFA => Self::Primary,
278 0x7C | 0xFC => Self::PlainText,
279 0xFD => Self::MainVIFExtension,
280 0xFB => Self::AlternateVIFExtension,
281 0x7E => Self::ManufacturerSpecific,
282 0xFE => Self::ManufacturerSpecific,
283 0x7F => Self::ManufacturerSpecific,
284 0xFF => Self::ManufacturerSpecific,
285 _ => unreachable!("Invalid value information: {:X}", value_information.data),
286 }
287 }
288}
289
290impl ValueInformationField {
291 const fn has_extension(&self) -> bool {
292 self.data & 0x80 != 0
293 }
294}
295
296#[derive(Debug, Clone, Copy, PartialEq)]
297#[cfg_attr(feature = "defmt", derive(defmt::Format))]
298#[non_exhaustive]
299pub enum ValueInformationCoding {
300 Primary,
301 PlainText,
302 MainVIFExtension,
303 AlternateVIFExtension,
304 ManufacturerSpecific,
305}
306
307impl<'a> ValueInformationBlock<'a> {
308 pub fn new(
309 value_information: ValueInformationField,
310 value_information_extension: Option<ValueInformationFieldExtensions<'a>>,
311 plaintext_vife: Option<PlainTextValueInformationExtension<'a>>,
312 ) -> Self {
313 Self {
314 value_information,
315 value_information_extension,
316 plaintext_vife,
317 }
318 }
319
320 #[must_use]
321 pub fn get_size(&self) -> usize {
322 let mut size = 1;
323 if let Some(vife) = &self.value_information_extension {
324 size += vife.iter().count();
325 }
326 if let Some(plaintext_vife) = &self.plaintext_vife {
327 size += plaintext_vife.ascii_len() + 1;
329 }
330 size
331 }
332}
333
334fn head_vif_info(
335 vif: ValueInformationField,
336 first_vife: Option<u8>,
337 second_vife_data: Option<u8>,
338) -> Result<VifInfo, DataInformationError> {
339 Ok(match ValueInformationCoding::from(&vif) {
340 ValueInformationCoding::Primary => match vif.data & 0x7F {
341 0x00..=0x07 => VifInfo {
342 labels: &[ValueLabel::Energy],
343 units: &[unit!(Watt), unit!(Hour)],
344 scale: (vif.data & 0b111) as isize - 3,
345 ..VifInfo::EMPTY
346 },
347 0x08..=0x0F => VifInfo {
348 labels: &[ValueLabel::Energy],
349 units: &[unit!(Joul)],
350 scale: (vif.data & 0b111) as isize,
351 ..VifInfo::EMPTY
352 },
353 0x10..=0x17 => VifInfo {
354 labels: &[ValueLabel::Volume],
355 units: &[unit!(Meter ^ 3)],
356 scale: (vif.data & 0b111) as isize - 6,
357 ..VifInfo::EMPTY
358 },
359 0x18..=0x1F => VifInfo {
360 labels: &[ValueLabel::Mass],
361 units: &[unit!(Kilogram)],
362 scale: (vif.data & 0b111) as isize - 3,
363 ..VifInfo::EMPTY
364 },
365 0x20..=0x23 => {
366 return Ok(VifInfo {
367 labels: &[ValueLabel::OnTime],
368 units: match vif.data & 3 {
369 0 => &[unit!(Second)],
370 1 => &[unit!(Minute)],
371 2 => &[unit!(Hour)],
372 _ => &[unit!(Day)],
373 },
374 ..VifInfo::EMPTY
375 });
376 }
377 0x24..=0x27 => {
378 return Ok(VifInfo {
379 labels: &[ValueLabel::OperatingTime],
380 units: match vif.data & 3 {
381 0 => &[unit!(Second)],
382 1 => &[unit!(Minute)],
383 2 => &[unit!(Hour)],
384 _ => &[unit!(Day)],
385 },
386 ..VifInfo::EMPTY
387 });
388 }
389 0x28..=0x2F => VifInfo {
390 labels: &[ValueLabel::Power],
391 units: &[unit!(Watt)],
392 scale: (vif.data & 0b111) as isize - 3,
393 ..VifInfo::EMPTY
394 },
395 0x30..=0x37 => VifInfo {
396 labels: &[ValueLabel::Power],
397 units: &[unit!(Joul), unit!(Hour ^ -1)],
398 scale: (vif.data & 0b111) as isize,
399 ..VifInfo::EMPTY
400 },
401 0x38..=0x3F => VifInfo {
402 labels: &[ValueLabel::VolumeFlow],
403 units: &[unit!(Meter ^ 3), unit!(Hour ^ -1)],
404 scale: (vif.data & 0b111) as isize - 6,
405 ..VifInfo::EMPTY
406 },
407 0x40..=0x47 => VifInfo {
408 labels: &[ValueLabel::VolumeFlow],
409 units: &[unit!(Meter ^ 3), unit!(Minute ^ -1)],
410 scale: (vif.data & 0b111) as isize - 7,
411 ..VifInfo::EMPTY
412 },
413 0x48..=0x4F => VifInfo {
414 labels: &[ValueLabel::VolumeFlow],
415 units: &[unit!(Meter ^ 3), unit!(Second ^ -1)],
416 scale: (vif.data & 0b111) as isize - 9,
417 ..VifInfo::EMPTY
418 },
419 0x50..=0x57 => VifInfo {
420 labels: &[ValueLabel::MassFlow],
421 units: &[unit!(Kilogram), unit!(Hour ^ -1)],
422 scale: (vif.data & 0b111) as isize - 3,
423 ..VifInfo::EMPTY
424 },
425 0x58..=0x5B => VifInfo {
426 labels: &[ValueLabel::FlowTemperature],
427 units: &[unit!(Celsius)],
428 scale: (vif.data & 0b11) as isize - 3,
429 ..VifInfo::EMPTY
430 },
431 0x5C..=0x5F => VifInfo {
432 labels: &[ValueLabel::ReturnTemperature],
433 units: &[unit!(Celsius)],
434 scale: (vif.data & 0b11) as isize - 3,
435 ..VifInfo::EMPTY
436 },
437 0x60..=0x63 => VifInfo {
438 labels: &[ValueLabel::TemperatureDifference],
439 units: &[unit!(Kelvin)],
440 scale: (vif.data & 0b11) as isize - 3,
441 ..VifInfo::EMPTY
442 },
443 0x64..=0x67 => VifInfo {
444 labels: &[ValueLabel::ExternalTemperature],
445 units: &[unit!(Celsius)],
446 scale: (vif.data & 0b11) as isize - 3,
447 ..VifInfo::EMPTY
448 },
449 0x68..=0x6B => VifInfo {
450 labels: &[ValueLabel::Pressure],
451 units: &[unit!(Bar)],
452 scale: (vif.data & 0b11) as isize - 3,
453 ..VifInfo::EMPTY
454 },
455 0x6C => labels!(ValueLabel::Date),
456 0x6D => labels!(ValueLabel::DateTime),
457 0x6E => labels!(ValueLabel::DimensionlessHCA),
458 0x70..=0x73 => labels!(ValueLabel::AveragingDuration),
459 0x74..=0x77 => labels!(ValueLabel::ActualityDuration),
460 0x78 => labels!(ValueLabel::FabricationNumber),
461 0x79 => labels!(ValueLabel::EnhancedIdentification),
462 0x7A => labels!(ValueLabel::Address),
463 0x7B => VifInfo::EMPTY,
464
465 _ => {
466 return Err(DataInformationError::Unimplemented {
467 feature: "Primary value information unit codes (partial)",
468 })
469 }
470 },
471 ValueInformationCoding::MainVIFExtension => {
472 let Some(first_vife_data) = first_vife else {
473 return Ok(VifInfo::EMPTY);
474 };
475 match first_vife_data & 0x7F {
476 0x00..=0x03 => VifInfo {
477 labels: &[ValueLabel::Credit],
478 units: &[unit!(LocalMoneyCurrency)],
479 scale: (first_vife_data & 0b11) as isize - 3,
480 ..VifInfo::EMPTY
481 },
482 0x04..=0x07 => VifInfo {
483 labels: &[ValueLabel::Debit],
484 units: &[unit!(LocalMoneyCurrency)],
485 scale: (first_vife_data & 0b11) as isize - 3,
486 ..VifInfo::EMPTY
487 },
488 0x08 => labels!(ValueLabel::UniqueMessageIdentificationOrAccessNumber),
489 0x09 => labels!(ValueLabel::DeviceType),
490 0x0A => labels!(ValueLabel::Manufacturer),
491 0x0B => labels!(ValueLabel::ParameterSetIdentification),
492 0x0C => labels!(ValueLabel::ModelOrVersion),
493 0x0D => labels!(ValueLabel::HardwareVersion),
494 0x0E => labels!(ValueLabel::MetrologyFirmwareVersion),
495 0x0F => labels!(ValueLabel::OtherSoftwareVersion),
496 0x10 => labels!(ValueLabel::CustomerLocation),
497 0x11 => labels!(ValueLabel::Customer),
498 0x12 => labels!(ValueLabel::AccessCodeUser),
499 0x13 => labels!(ValueLabel::AccessCodeOperator),
500 0x14 => labels!(ValueLabel::AccessCodeSystemOperator),
501 0x15 => labels!(ValueLabel::AccessCodeDeveloper),
502 0x16 => labels!(ValueLabel::Password),
503 0x17 => labels!(ValueLabel::ErrorFlags),
504 0x18 => labels!(ValueLabel::ErrorMask),
505 0x19 => labels!(ValueLabel::SecurityKey),
506 0x1A => VifInfo {
507 labels: &[ValueLabel::DigitalOutput, ValueLabel::Binary],
508 ..VifInfo::EMPTY
509 },
510 0x1B => VifInfo {
511 labels: &[ValueLabel::DigitalInput, ValueLabel::Binary],
512 ..VifInfo::EMPTY
513 },
514 0x1C => VifInfo {
515 labels: &[ValueLabel::BaudRate],
516 units: &[unit!(Symbol), unit!(Second ^ -1)],
517 ..VifInfo::EMPTY
518 },
519 0x1D => VifInfo {
520 labels: &[ValueLabel::ResponseDelayTime],
521 units: &[unit!(BitTime)],
522 ..VifInfo::EMPTY
523 },
524 0x1E => labels!(ValueLabel::Retry),
525 0x1F => labels!(ValueLabel::RemoteControl),
526 0x20 => labels!(ValueLabel::FirstStorageForCycleStorage),
527 0x21 => labels!(ValueLabel::LastStorageForCycleStorage),
528 0x22 => labels!(ValueLabel::SizeOfStorageBlock),
529 0x23 => labels!(ValueLabel::DescriptionOfTariffAndSubunit),
530 0x24 => VifInfo {
531 labels: &[ValueLabel::StorageInterval],
532 units: &[unit!(Second)],
533 ..VifInfo::EMPTY
534 },
535 0x25 => VifInfo {
536 labels: &[ValueLabel::StorageInterval],
537 units: &[unit!(Minute)],
538 ..VifInfo::EMPTY
539 },
540 0x26 => VifInfo {
541 labels: &[ValueLabel::StorageInterval],
542 units: &[unit!(Hour)],
543 ..VifInfo::EMPTY
544 },
545 0x27 => VifInfo {
546 labels: &[ValueLabel::StorageInterval],
547 units: &[unit!(Day)],
548 ..VifInfo::EMPTY
549 },
550 0x28 => VifInfo {
551 labels: &[ValueLabel::StorageInterval],
552 units: &[unit!(Month)],
553 ..VifInfo::EMPTY
554 },
555 0x29 => VifInfo {
556 labels: &[ValueLabel::StorageInterval],
557 units: &[unit!(Year)],
558 ..VifInfo::EMPTY
559 },
560 0x30 => labels!(ValueLabel::DimensionlessHCA),
561 0x31 => labels!(ValueLabel::DataContainerForWmbusProtocol),
562 0x32 => VifInfo {
563 labels: &[ValueLabel::PeriodOfNormalDataTransmission],
564 units: &[unit!(Second)],
565 ..VifInfo::EMPTY
566 },
567 0x33 => VifInfo {
568 labels: &[ValueLabel::PeriodOfNormalDataTransmission],
569 units: &[unit!(Meter)],
570 ..VifInfo::EMPTY
571 },
572 0x34 => VifInfo {
573 labels: &[ValueLabel::PeriodOfNormalDataTransmission],
574 units: &[unit!(Hour)],
575 ..VifInfo::EMPTY
576 },
577 0x35 => VifInfo {
578 labels: &[ValueLabel::PeriodOfNormalDataTransmission],
579 units: &[unit!(Day)],
580 ..VifInfo::EMPTY
581 },
582 0x3A => labels!(ValueLabel::Dimensionless),
583 0x40..=0x4F => VifInfo {
584 labels: &[ValueLabel::Voltage],
585 units: &[unit!(Volt)],
586 scale: (first_vife_data & 0b1111) as isize - 9,
587 ..VifInfo::EMPTY
588 },
589 0x50..=0x5F => VifInfo {
590 labels: &[ValueLabel::Current],
591 units: &[unit!(Ampere)],
592 scale: (first_vife_data & 0b1111) as isize - 12,
593 ..VifInfo::EMPTY
594 },
595 0x60 => labels!(ValueLabel::ResetCounter),
596 0x61 => labels!(ValueLabel::CumulationCounter),
597 0x62 => labels!(ValueLabel::ControlSignal),
598 0x63 => labels!(ValueLabel::DayOfWeek),
599 0x64 => labels!(ValueLabel::WeekNumber),
600 0x65 => labels!(ValueLabel::TimePointOfChangeOfTariff),
601 0x66 => labels!(ValueLabel::StateOfParameterActivation),
602 0x67 => labels!(ValueLabel::SpecialSupplierInformation),
603 0x68 => VifInfo {
604 labels: &[ValueLabel::DurationSinceLastCumulation],
605 units: &[unit!(Hour)],
606 ..VifInfo::EMPTY
607 },
608 0x69 => VifInfo {
609 labels: &[ValueLabel::DurationSinceLastCumulation],
610 units: &[unit!(Day)],
611 ..VifInfo::EMPTY
612 },
613 0x6A => VifInfo {
614 labels: &[ValueLabel::DurationSinceLastCumulation],
615 units: &[unit!(Month)],
616 ..VifInfo::EMPTY
617 },
618 0x6B => VifInfo {
619 labels: &[ValueLabel::DurationSinceLastCumulation],
620 units: &[unit!(Year)],
621 ..VifInfo::EMPTY
622 },
623 0x6C => VifInfo {
624 labels: &[ValueLabel::OperatingTimeBattery],
625 units: &[unit!(Hour)],
626 ..VifInfo::EMPTY
627 },
628 0x6D => VifInfo {
629 labels: &[ValueLabel::OperatingTimeBattery],
630 units: &[unit!(Day)],
631 ..VifInfo::EMPTY
632 },
633 0x6E => VifInfo {
634 labels: &[ValueLabel::OperatingTimeBattery],
635 units: &[unit!(Month)],
636 ..VifInfo::EMPTY
637 },
638 0x6F => VifInfo {
639 labels: &[ValueLabel::OperatingTimeBattery],
640 units: &[unit!(Hour)],
641 ..VifInfo::EMPTY
642 },
643 0x70 => VifInfo {
644 labels: &[ValueLabel::DateAndTimeOfBatteryChange],
645 units: &[unit!(Second)],
646 ..VifInfo::EMPTY
647 },
648 0x71 => VifInfo {
649 labels: &[ValueLabel::RFPowerLevel],
650 units: &[unit!(DecibelMilliWatt)],
651 ..VifInfo::EMPTY
652 },
653 0x72 => labels!(ValueLabel::DaylightSavingBeginningEndingDeviation),
654 0x73 => labels!(ValueLabel::ListeningWindowManagementData),
655 0x74 => labels!(ValueLabel::RemainingBatteryLifeTime),
656 0x75 => labels!(ValueLabel::NumberOfTimesTheMeterWasStopped),
657 0x76 => VifInfo {
658 labels: &[ValueLabel::DataContainerForManufacturerSpecificProtocol],
659 ..VifInfo::EMPTY
660 },
661 0x7D => match second_vife_data.map(|s| s & 0x7F) {
662 Some(0x00) => labels!(ValueLabel::CurrentlySelectedApplication),
663 Some(0x02) => VifInfo {
664 labels: &[ValueLabel::RemainingBatteryLifeTime],
665 units: &[unit!(Month)],
666 ..VifInfo::EMPTY
667 },
668 Some(0x03) => VifInfo {
669 labels: &[ValueLabel::RemainingBatteryLifeTime],
670 units: &[unit!(Year)],
671 ..VifInfo::EMPTY
672 },
673 Some(0x3E) => VifInfo {
674 labels: &[ValueLabel::MoistureLevel],
675 units: &[unit!(Percent)],
676 ..VifInfo::EMPTY
677 },
678 _ => labels!(ValueLabel::Reserved),
679 },
680 _ => labels!(ValueLabel::Reserved),
681 }
682 }
683 ValueInformationCoding::AlternateVIFExtension => {
684 use UnitName::*;
685 use ValueLabel::*;
686 macro_rules! populate {
687 ($name:ident / h, $exp:expr, dec: $d:literal, $label:expr) => {
688 VifInfo {
689 units: &[
690 Unit {
691 name: $name,
692 exponent: $exp,
693 },
694 Unit {
695 name: Hour,
696 exponent: -1,
697 },
698 ],
699 labels: &[$label],
700 scale: $d,
701 offset: 0,
702 }
703 };
704 ($name:ident / min, $exp:expr, dec: $d:literal, $label:expr) => {
705 VifInfo {
706 units: &[
707 Unit {
708 name: $name,
709 exponent: $exp,
710 },
711 Unit {
712 name: Minute,
713 exponent: -1,
714 },
715 ],
716 labels: &[$label],
717 scale: $d,
718 offset: 0,
719 }
720 };
721 ($name:ident * h, $exp:expr, dec: $d:literal, $label:expr) => {
722 VifInfo {
723 units: &[
724 Unit {
725 name: $name,
726 exponent: $exp,
727 },
728 Unit {
729 name: Hour,
730 exponent: 1,
731 },
732 ],
733 labels: &[$label],
734 scale: $d,
735 offset: 0,
736 }
737 };
738 ($name:ident , $exp:expr, dec: $d:literal, $label:expr) => {
739 VifInfo {
740 units: &[Unit {
741 name: $name,
742 exponent: $exp,
743 }],
744 labels: &[$label],
745 scale: $d,
746 offset: 0,
747 }
748 };
749 }
750
751 let Some(first_vife_data) = first_vife else {
752 return Ok(VifInfo::EMPTY);
753 };
754 match first_vife_data & 0x7F {
755 0b0 => populate!(Watt / h, 3, dec: 5, Energy),
756 0b000_0001 => populate!(Watt / h, 3, dec: 6, Energy),
757 0b000_0010 => populate!(ReactiveWatt * h, 1, dec: 3, ReactiveEnergy),
758 0b000_0011 => populate!(ReactiveWatt * h, 1, dec: 4, ReactiveEnergy),
759 0b000_0100 => populate!(ApparentWatt * h, 1, dec: 3, ApparentEnergy),
760 0b000_0101 => populate!(ApparentWatt * h, 1, dec: 4, ApparentEnergy),
761 0b000_0110 => VifInfo {
762 labels: &[CoefficientOfPerformance],
763 scale: -1,
764 ..VifInfo::EMPTY
765 },
766 0b000_1000 => populate!(Joul, 1, dec: 8, Energy),
767 0b000_1001 => populate!(Joul, 1, dec: 9, Energy),
768 0b000_1100 => populate!(Calorie, 1, dec: 5, Energy),
769 0b000_1101 => populate!(Calorie, 1, dec: 6, Energy),
770 0b000_1110 => populate!(Calorie, 1, dec: 7, Energy),
771 0b000_1111 => populate!(Calorie, 1, dec: 8, Energy),
772 0b001_0000 => populate!(Meter, 3, dec: 2, Volume),
773 0b001_0001 => populate!(Meter, 3, dec: 3, Volume),
774 0b001_0100 => populate!(ReactiveWatt, 1, dec: 0, ReactivePower),
775 0b001_0101 => populate!(ReactiveWatt, 1, dec: 1, ReactivePower),
776 0b001_0110 => populate!(ReactiveWatt, 1, dec: 2, ReactivePower),
777 0b001_0111 => populate!(ReactiveWatt, 1, dec: 3, ReactivePower),
778 0b001_1000 => populate!(Tonne, 1, dec: 2, Mass),
779 0b001_1001 => populate!(Tonne, 1, dec: 3, Mass),
780 0b001_1010 => populate!(Percent, 1, dec: -1, RelativeHumidity),
781 0b001_1011 => populate!(Percent, 1, dec: 0, RelativeHumidity),
782 0b010_0000 => populate!(Feet, 3, dec: 0, Volume),
783 0b010_0001 => populate!(Feet, 3, dec: -1, Volume),
784 0b010_0011 => populate!(Degree, 1, dec: -1, PhaseItoU),
785 0b010_1000 => populate!(Watt, 1, dec: 5, Power),
786 0b010_1001 => populate!(Watt, 1, dec: 6, Power),
787 0b010_1010 => populate!(Degree, 1, dec: -1, PhaseUtoU),
788 0b010_1011 => populate!(Degree, 1, dec: -1, PhaseUtoI),
789 0b010_1100 => populate!(Hertz, 1, dec: -3, Frequency),
790 0b010_1101 => populate!(Hertz, 1, dec: -2, Frequency),
791 0b010_1110 => populate!(Hertz, 1, dec: -1, Frequency),
792 0b010_1111 => populate!(Hertz, 1, dec: 0, Frequency),
793 0b011_0000 => populate!(Joul / h, 1, dec: 8, Power),
794 0b011_0001 => populate!(Joul / h, 1, dec: 9, Power),
795 0b011_0100 => populate!(ApparentWatt, 1, dec: 0, ApparentPower),
796 0b011_0101 => populate!(ApparentWatt, 1, dec: 1, ApparentPower),
797 0b011_0110 => populate!(ApparentWatt, 1, dec: 2, ApparentPower),
798 0b011_0111 => populate!(ApparentWatt, 1, dec: 3, ApparentPower),
799 0b101_1000 => populate!(Fahrenheit, 1, dec: -3, FlowTemperature),
800 0b101_1001 => populate!(Fahrenheit, 1, dec: -2, FlowTemperature),
801 0b101_1010 => populate!(Fahrenheit, 1, dec: -1, FlowTemperature),
802 0b101_1011 => populate!(Fahrenheit, 1, dec: 0, FlowTemperature),
803 0b101_1100 => populate!(Fahrenheit, 1, dec: -3, ReturnTemperature),
804 0b101_1101 => populate!(Fahrenheit, 1, dec: -2, ReturnTemperature),
805 0b101_1110 => populate!(Fahrenheit, 1, dec: -1, ReturnTemperature),
806 0b101_1111 => populate!(Fahrenheit, 1, dec: 0, ReturnTemperature),
807 0b110_0000 => populate!(Fahrenheit, 1, dec: -3, TemperatureDifference),
808 0b110_0001 => populate!(Fahrenheit, 1, dec: -2, TemperatureDifference),
809 0b110_0010 => populate!(Fahrenheit, 1, dec: -1, TemperatureDifference),
810 0b110_0011 => populate!(Fahrenheit, 1, dec: 0, TemperatureDifference),
811 0b110_0100 => populate!(Fahrenheit, 1, dec: -3, ExternalTemperature),
812 0b110_0101 => populate!(Fahrenheit, 1, dec: -2, ExternalTemperature),
813 0b110_0110 => populate!(Fahrenheit, 1, dec: -1, ExternalTemperature),
814 0b110_0111 => populate!(Fahrenheit, 1, dec: 0, ExternalTemperature),
815 0b111_0000 => populate!(Fahrenheit, 1, dec: -3, ColdWarmTemperatureLimit),
816 0b111_0001 => populate!(Fahrenheit, 1, dec: -2, ColdWarmTemperatureLimit),
817 0b111_0010 => populate!(Fahrenheit, 1, dec: -1, ColdWarmTemperatureLimit),
818 0b111_0011 => populate!(Fahrenheit, 1, dec: 0, ColdWarmTemperatureLimit),
819 0b111_0100 => populate!(Celsius, 1, dec: -3, ColdWarmTemperatureLimit),
820 0b111_0101 => populate!(Celsius, 1, dec: -2, ColdWarmTemperatureLimit),
821 0b111_0110 => populate!(Celsius, 1, dec: -1, ColdWarmTemperatureLimit),
822 0b111_0111 => populate!(Celsius, 1, dec: 0, ColdWarmTemperatureLimit),
823 0b111_1000 => populate!(Watt, 1, dec: -3, CumulativeMaximumOfActivePower),
824 0b111_1001 => populate!(Watt, 1, dec: -2, CumulativeMaximumOfActivePower),
825 0b111_1010 => populate!(Watt, 1, dec: -1, CumulativeMaximumOfActivePower),
826 0b111_1011 => populate!(Watt, 1, dec: 0, CumulativeMaximumOfActivePower),
827 0b111_1100 => populate!(Watt, 1, dec: 1, CumulativeMaximumOfActivePower),
828 0b111_1101 => populate!(Watt, 1, dec: 2, CumulativeMaximumOfActivePower),
829 0b111_1110 => populate!(Watt, 1, dec: 3, CumulativeMaximumOfActivePower),
830 0b111_1111 => populate!(Watt, 1, dec: 4, CumulativeMaximumOfActivePower),
831 0b110_1000 => populate!(HCAUnit, 1,dec: 0, ResultingRatingFactor),
832 0b110_1001 => populate!(HCAUnit, 1,dec: 0, ThermalOutputRatingFactor),
833 0b110_1010 => {
834 populate!(HCAUnit, 1,dec: 0, ThermalCouplingRatingFactorOverall)
835 }
836 0b110_1011 => populate!(HCAUnit, 1,dec: 0, ThermalCouplingRatingRoomSide),
837 0b110_1100 => {
838 populate!(HCAUnit, 1,dec: 0, ThermalCouplingRatingFactorHeatingSide)
839 }
840 0b110_1101 => populate!(HCAUnit, 1,dec: 0, LowTemperatureRatingFactor),
841 0b110_1110 => populate!(HCAUnit, 1,dec: 0, DisplayOutputScalingFactor),
842
843 _ => labels!(ValueLabel::Reserved),
844 }
845 }
846 ValueInformationCoding::PlainText => labels!(ValueLabel::PlainText),
847 ValueInformationCoding::ManufacturerSpecific => labels!(ValueLabel::ManufacturerSpecific),
848 })
849}
850fn orthogonal_vife_info(data: u8, combinable_ext: bool) -> VifInfo {
851 if combinable_ext {
852 match data & 0x7F {
853 0x00 => labels!(ValueLabel::Reserved),
854 0x01 => labels!(ValueLabel::AtPhaseL1),
855 0x02 => labels!(ValueLabel::AtPhaseL2),
856 0x03 => labels!(ValueLabel::AtPhaseL3),
857 0x04 => labels!(ValueLabel::AtNeutral),
858 0x05 => labels!(ValueLabel::BetweenPhasesL1L2),
859 0x06 => labels!(ValueLabel::BetweenPhasesL2L3),
860 0x07 => labels!(ValueLabel::BetweenPhasesL3L1),
861 0x08 => labels!(ValueLabel::AtQuadrant1),
862 0x09 => labels!(ValueLabel::AtQuadrant2),
863 0x0A => labels!(ValueLabel::AtQuadrant3),
864 0x0B => labels!(ValueLabel::AtQuadrant4),
865 0x0C => labels!(ValueLabel::DeltaBetweenImportAndExport),
866 0x0D => labels!(ValueLabel::AlternativeNonMetricUnits),
867 0x0E => labels!(ValueLabel::SecondarySensorMeasurement),
868 0x0F => labels!(ValueLabel::HigherResolutionRegister),
869 0x10 => {
870 labels!(ValueLabel::AccumulationOfAbsoluteValueBothPositiveAndNegativeContribution)
871 }
872 0x11 => labels!(ValueLabel::DataPresentedWithTypeC),
873 0x12 => labels!(ValueLabel::DataPresentedWithTypeD),
874 0x13 => labels!(ValueLabel::EndDate),
875 0x14 => labels!(ValueLabel::DirectionFromCommunicationPartnerToMeter),
876 0x15 => labels!(ValueLabel::DirectionFromMeterToCommunicationPartner),
877 _ => labels!(ValueLabel::Reserved),
878 }
879 } else {
880 match data & 0x7F {
881 0x00..=0x0F => labels!(ValueLabel::ReservedForObjectActions),
882 0x10..=0x11 => labels!(ValueLabel::Reserved),
883 0x12 => labels!(ValueLabel::Averaged),
884 0x13 => labels!(ValueLabel::InverseCompactProfile),
885 0x14 => labels!(ValueLabel::RelativeDeviation),
886 0x15..=0x1C => labels!(ValueLabel::RecordErrorCodes),
887 0x1D => labels!(ValueLabel::StandardConformDataContent),
888 0x1E => labels!(ValueLabel::CompactProfileWithRegisterNumbers),
889 0x1F => labels!(ValueLabel::CompactProfile),
890 0x20 => units!(unit!(Second ^ -1)),
891 0x21 => units!(unit!(Minute ^ -1)),
892 0x22 => units!(unit!(Hour ^ -1)),
893 0x23 => units!(unit!(Day ^ -1)),
894 0x24 => units!(unit!(Week ^ -1)),
895 0x25 => units!(unit!(Month ^ -1)),
896 0x26 => units!(unit!(Year ^ -1)),
897 0x27 => units!(unit!(Revolution ^ -1)),
898 0x28 => VifInfo {
899 units: &[unit!(Increment), unit!(InputPulseOnChannel0 ^ -1)],
900 ..VifInfo::EMPTY
901 },
902 0x29 => VifInfo {
903 units: &[unit!(Increment), unit!(InputPulseOnChannel1 ^ -1)],
904 ..VifInfo::EMPTY
905 },
906 0x2A => VifInfo {
907 units: &[unit!(Increment), unit!(OutputPulseOnChannel0 ^ -1)],
908 ..VifInfo::EMPTY
909 },
910 0x2B => VifInfo {
911 units: &[unit!(Increment), unit!(OutputPulseOnChannel1 ^ -1)],
912 ..VifInfo::EMPTY
913 },
914 0x2C => units!(unit!(Liter)),
915 0x2D => units!(unit!(Meter ^ -3)),
916 0x2E => units!(unit!(Kilogram ^ -1)),
917 0x2F => units!(unit!(Kelvin ^ -1)),
918 0x30 => VifInfo {
919 units: &[unit!(Watt ^ -1), unit!(Hour ^ -1)],
920 scale: -(3),
921 ..VifInfo::EMPTY
922 },
923 0x31 => VifInfo {
924 units: &[unit!(Joul ^ -1)],
925 scale: -9,
926 ..VifInfo::EMPTY
927 },
928 0x32 => VifInfo {
929 units: &[unit!(Watt ^ -1)],
930 scale: -3,
931 ..VifInfo::EMPTY
932 },
933 0x33 => VifInfo {
934 units: &[unit!(Kelvin ^ -1), unit!(Liter ^ -1)],
935 ..VifInfo::EMPTY
936 },
937 0x34 => units!(unit!(Volt ^ -1)),
938 0x35 => units!(unit!(Ampere ^ -1)),
939 0x36 => units!(unit!(Second ^ 1)),
940 0x37 => VifInfo {
941 units: &[unit!(Second ^ 1), unit!(Volt ^ -1)],
942 ..VifInfo::EMPTY
943 },
944 0x38 => VifInfo {
945 units: &[unit!(Second ^ 1), unit!(Ampere ^ -1)],
946 ..VifInfo::EMPTY
947 },
948 0x39 => labels!(ValueLabel::StartDateOf),
949 0x3A => labels!(ValueLabel::VifContainsUncorrectedUnitOrValue),
950 0x3B => labels!(ValueLabel::AccumulationOnlyIfValueIsPositive),
951 0x3C => labels!(ValueLabel::AccumulationOnlyIfValueIsNegative),
952 0x3D => labels!(ValueLabel::NonMetricUnits),
953 0x3E => labels!(ValueLabel::ValueAtBaseConditions),
954 0x3F => labels!(ValueLabel::ObisDeclaration),
955 0x40 => labels!(ValueLabel::LowerLimitValue),
957 0x48 => labels!(ValueLabel::UpperLimitValue),
958 0x41 => labels!(ValueLabel::NumberOfExceedsOfLowerLimitValue),
960 0x49 => labels!(ValueLabel::NumberOfExceedsOfUpperLimitValue),
961 0x42 => labels!(ValueLabel::DateOfBeginFirstLowerLimitExceed),
967 0x43 => labels!(ValueLabel::DateOfEndFirstLowerLimitExceed),
968 0x46 => labels!(ValueLabel::DateOfBeginLastLowerLimitExceed),
969 0x47 => labels!(ValueLabel::DateOfEndLastLowerLimitExceed),
970 0x4A => labels!(ValueLabel::DateOfBeginFirstUpperLimitExceed),
971 0x4B => labels!(ValueLabel::DateOfEndFirstUpperLimitExceed),
972 0x4E => labels!(ValueLabel::DateOfBeginLastUpperLimitExceed),
973 0x4F => labels!(ValueLabel::DateOfEndLastUpperLimitExceed),
974 0x50 => VifInfo {
975 labels: &[ValueLabel::DurationOfFirstLowerLimitExceed],
976 units: &[unit!(Second)],
977 ..VifInfo::EMPTY
978 },
979 0x51 => VifInfo {
980 labels: &[ValueLabel::DurationOfFirstLowerLimitExceed],
981 units: &[unit!(Minute)],
982 ..VifInfo::EMPTY
983 },
984 0x52 => VifInfo {
985 labels: &[ValueLabel::DurationOfFirstLowerLimitExceed],
986 units: &[unit!(Hour)],
987 ..VifInfo::EMPTY
988 },
989 0x53 => VifInfo {
990 labels: &[ValueLabel::DurationOfFirstLowerLimitExceed],
991 units: &[unit!(Day)],
992 ..VifInfo::EMPTY
993 },
994 0x54 => VifInfo {
995 labels: &[ValueLabel::DurationOfLastLowerLimitExceed],
996 units: &[unit!(Second)],
997 ..VifInfo::EMPTY
998 },
999 0x55 => VifInfo {
1000 labels: &[ValueLabel::DurationOfLastLowerLimitExceed],
1001 units: &[unit!(Minute)],
1002 ..VifInfo::EMPTY
1003 },
1004 0x56 => VifInfo {
1005 labels: &[ValueLabel::DurationOfLastLowerLimitExceed],
1006 units: &[unit!(Hour)],
1007 ..VifInfo::EMPTY
1008 },
1009 0x57 => VifInfo {
1010 labels: &[ValueLabel::DurationOfLastLowerLimitExceed],
1011 units: &[unit!(Day)],
1012 ..VifInfo::EMPTY
1013 },
1014 0x58 => VifInfo {
1015 labels: &[ValueLabel::DurationOfFirstUpperLimitExceed],
1016 units: &[unit!(Second)],
1017 ..VifInfo::EMPTY
1018 },
1019 0x59 => VifInfo {
1020 labels: &[ValueLabel::DurationOfFirstUpperLimitExceed],
1021 units: &[unit!(Minute)],
1022 ..VifInfo::EMPTY
1023 },
1024 0x5A => VifInfo {
1025 labels: &[ValueLabel::DurationOfFirstUpperLimitExceed],
1026 units: &[unit!(Hour)],
1027 ..VifInfo::EMPTY
1028 },
1029 0x5B => VifInfo {
1030 labels: &[ValueLabel::DurationOfFirstUpperLimitExceed],
1031 units: &[unit!(Day)],
1032 ..VifInfo::EMPTY
1033 },
1034 0x5C => VifInfo {
1035 labels: &[ValueLabel::DurationOfLastUpperLimitExceed],
1036 units: &[unit!(Second)],
1037 ..VifInfo::EMPTY
1038 },
1039 0x5D => VifInfo {
1040 labels: &[ValueLabel::DurationOfLastUpperLimitExceed],
1041 units: &[unit!(Minute)],
1042 ..VifInfo::EMPTY
1043 },
1044 0x5E => VifInfo {
1045 labels: &[ValueLabel::DurationOfLastUpperLimitExceed],
1046 units: &[unit!(Hour)],
1047 ..VifInfo::EMPTY
1048 },
1049 0x5F => VifInfo {
1050 labels: &[ValueLabel::DurationOfLastUpperLimitExceed],
1051 units: &[unit!(Day)],
1052 ..VifInfo::EMPTY
1053 },
1054 0x60 => VifInfo {
1055 labels: &[ValueLabel::DurationOfFirst],
1056 units: &[unit!(Second)],
1057 ..VifInfo::EMPTY
1058 },
1059 0x61 => VifInfo {
1060 labels: &[ValueLabel::DurationOfFirst],
1061 units: &[unit!(Minute)],
1062 ..VifInfo::EMPTY
1063 },
1064 0x62 => VifInfo {
1065 labels: &[ValueLabel::DurationOfFirst],
1066 units: &[unit!(Hour)],
1067 ..VifInfo::EMPTY
1068 },
1069 0x63 => VifInfo {
1070 labels: &[ValueLabel::DurationOfFirst],
1071 units: &[unit!(Day)],
1072 ..VifInfo::EMPTY
1073 },
1074 0x64 => VifInfo {
1075 labels: &[ValueLabel::DurationOfLast],
1076 units: &[unit!(Second)],
1077 ..VifInfo::EMPTY
1078 },
1079 0x65 => VifInfo {
1080 labels: &[ValueLabel::DurationOfLast],
1081 units: &[unit!(Minute)],
1082 ..VifInfo::EMPTY
1083 },
1084 0x66 => VifInfo {
1085 labels: &[ValueLabel::DurationOfLast],
1086 units: &[unit!(Hour)],
1087 ..VifInfo::EMPTY
1088 },
1089 0x67 => VifInfo {
1090 labels: &[ValueLabel::DurationOfLast],
1091 units: &[unit!(Day)],
1092 ..VifInfo::EMPTY
1093 },
1094 0x68 => labels!(ValueLabel::ValueDuringLowerValueExceed),
1095 0x6C => labels!(ValueLabel::ValueDuringUpperValueExceed),
1096 0x69 => labels!(ValueLabel::LeakageValues),
1097 0x6D => labels!(ValueLabel::OverflowValues),
1098 0x6A => labels!(ValueLabel::DateOfBeginFirst),
1099 0x6B => labels!(ValueLabel::DateOfBeginLast),
1100 0x6E => labels!(ValueLabel::DateOfEndLast),
1101 0x6F => labels!(ValueLabel::DateOfEndFirst),
1102 0x70..=0x77 => VifInfo {
1103 scale: (data & 0b111) as isize - 6,
1104 ..VifInfo::EMPTY
1105 },
1106 0x78..=0x7B => VifInfo {
1107 offset: (data & 0b11) as isize - 3,
1108 ..VifInfo::EMPTY
1109 },
1110 0x7D => VifInfo {
1111 scale: 3,
1112 ..VifInfo::EMPTY
1113 },
1114 0x7E => labels!(ValueLabel::FutureValue),
1115 0x7F => labels!(ValueLabel::NextVIFEAndDataOfThisBlockAreManufacturerSpecific),
1116 _ => labels!(ValueLabel::Reserved),
1117 }
1118 }
1119}
1120
1121#[derive(Debug, Clone, Copy, PartialEq)]
1122#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1123#[non_exhaustive]
1124pub enum ValueInformationError {
1125 InvalidValueInformation,
1126 DataTooShort,
1127}
1128
1129impl From<u8> for ValueInformationField {
1130 fn from(data: u8) -> Self {
1131 Self { data }
1132 }
1133}
1134fn orthogonal_chain(
1141 coding: ValueInformationCoding,
1142 ext: Option<ValueInformationFieldExtensions<'_>>,
1143) -> ValueInformationFieldExtensions<'_> {
1144 let mut chain = ext.unwrap_or(ValueInformationFieldExtensions(&[]));
1145 match coding {
1146 ValueInformationCoding::MainVIFExtension
1147 | ValueInformationCoding::AlternateVIFExtension => {
1148 chain.next();
1149 }
1150 ValueInformationCoding::ManufacturerSpecific => return ValueInformationFieldExtensions(&[]),
1151 _ => {}
1152 }
1153 chain
1154}
1155
1156#[derive(Clone)]
1157struct OrthogonalVifes<'a> {
1158 vife: ValueInformationFieldExtensions<'a>,
1159 combinable_ext: bool,
1160}
1161impl Iterator for OrthogonalVifes<'_> {
1162 type Item = VifInfo;
1163 fn next(&mut self) -> Option<Self::Item> {
1164 loop {
1165 let v = self.vife.next()?;
1166 if v.data == 0xFC {
1169 self.combinable_ext = true;
1170 continue;
1171 }
1172 let ext = core::mem::replace(&mut self.combinable_ext, false);
1173 if !ext && v.data & 0x7F == 0x7F {
1174 self.vife = ValueInformationFieldExtensions(&[]);
1177 }
1178 return Some(orthogonal_vife_info(v.data, ext));
1179 }
1180 }
1181}
1182
1183#[derive(Clone)]
1188pub struct ValueInformation<'a> {
1189 head_labels: &'static [ValueLabel],
1190 head_units: &'static [Unit],
1191 orthogonal: ValueInformationFieldExtensions<'a>,
1192 pub decimal_scale_exponent: isize,
1193 pub decimal_offset_exponent: isize,
1194}
1195impl<'a> ValueInformation<'a> {
1196 #[must_use]
1198 pub fn labels(&self) -> ValueLabels<'a> {
1199 ValueLabels {
1200 current: self.head_labels,
1201 rest: OrthogonalVifes {
1202 vife: self.orthogonal.clone(),
1203 combinable_ext: false,
1204 },
1205 }
1206 }
1207 #[must_use]
1209 pub fn units(&self) -> Units<'a> {
1210 Units {
1211 current: self.head_units,
1212 rest: OrthogonalVifes {
1213 vife: self.orthogonal.clone(),
1214 combinable_ext: false,
1215 },
1216 }
1217 }
1218 #[must_use]
1219 pub fn has_label(&self, label: ValueLabel) -> bool {
1220 self.labels().any(|item| item == label)
1221 }
1222 #[must_use]
1223 pub fn first_unit(&self) -> Option<Unit> {
1224 self.units().next()
1225 }
1226}
1227impl<'a> TryFrom<&ValueInformationBlock<'a>> for ValueInformation<'a> {
1228 type Error = DataInformationError;
1229 fn try_from(block: &ValueInformationBlock<'a>) -> Result<Self, Self::Error> {
1230 let coding = ValueInformationCoding::from(&block.value_information);
1231 let ext = block.value_information_extension.clone();
1232 let bytes = ext.as_ref().map_or(&[][..], |ext| ext.0);
1235 let first = bytes.first().copied();
1236 let second = bytes.get(1).copied();
1237 if matches!(
1240 coding,
1241 ValueInformationCoding::MainVIFExtension
1242 | ValueInformationCoding::AlternateVIFExtension
1243 ) && ext.is_some()
1244 && first.is_none()
1245 {
1246 return Err(DataInformationError::DataTooShort);
1247 }
1248 let head = head_vif_info(block.value_information.clone(), first, second)?;
1249 let orthogonal = OrthogonalVifes {
1250 vife: orthogonal_chain(coding, ext),
1251 combinable_ext: false,
1252 };
1253 let (scale, offset) = orthogonal
1254 .clone()
1255 .fold((head.scale, head.offset), |(s, o), v| {
1256 (s + v.scale, o + v.offset)
1257 });
1258 Ok(Self {
1259 head_labels: head.labels,
1260 head_units: head.units,
1261 orthogonal: orthogonal.vife,
1262 decimal_scale_exponent: scale,
1263 decimal_offset_exponent: offset,
1264 })
1265 }
1266}
1267impl PartialEq for ValueInformation<'_> {
1268 fn eq(&self, other: &Self) -> bool {
1269 self.decimal_scale_exponent == other.decimal_scale_exponent
1270 && self.decimal_offset_exponent == other.decimal_offset_exponent
1271 && self.labels().eq(other.labels())
1272 && self.units().eq(other.units())
1273 }
1274}
1275impl core::fmt::Debug for ValueInformation<'_> {
1276 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1277 f.debug_struct("ValueInformation")
1278 .field("decimal_offset_exponent", &self.decimal_offset_exponent)
1279 .field("labels", &self.labels())
1280 .field("decimal_scale_exponent", &self.decimal_scale_exponent)
1281 .field("units", &self.units())
1282 .finish()
1283 }
1284}
1285#[cfg(feature = "serde")]
1286impl serde::Serialize for ValueInformation<'_> {
1287 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1288 use serde::ser::SerializeStruct;
1289 let mut state = serializer.serialize_struct("ValueInformation", 4)?;
1290 state.serialize_field("decimal_offset_exponent", &self.decimal_offset_exponent)?;
1291 state.serialize_field("labels", &self.labels())?;
1292 state.serialize_field("decimal_scale_exponent", &self.decimal_scale_exponent)?;
1293 state.serialize_field("units", &self.units())?;
1294 state.end()
1295 }
1296}
1297
1298#[cfg(feature = "serde")]
1301fn serialize_counted_sequence<I, S>(items: I, serializer: S) -> Result<S::Ok, S::Error>
1302where
1303 I: Iterator + Clone,
1304 I::Item: serde::Serialize,
1305 S: serde::Serializer,
1306{
1307 use serde::ser::SerializeSeq;
1308 let mut sequence = serializer.serialize_seq(Some(items.clone().count()))?;
1309 for item in items {
1310 sequence.serialize_element(&item)?;
1311 }
1312 sequence.end()
1313}
1314
1315#[derive(Clone)]
1317pub struct ValueLabels<'a> {
1318 current: &'static [ValueLabel],
1319 rest: OrthogonalVifes<'a>,
1320}
1321impl Iterator for ValueLabels<'_> {
1322 type Item = ValueLabel;
1323 fn next(&mut self) -> Option<Self::Item> {
1324 loop {
1325 if let Some((first, tail)) = self.current.split_first() {
1326 self.current = tail;
1327 return Some(*first);
1328 }
1329 self.current = self.rest.next()?.labels;
1330 }
1331 }
1332}
1333impl core::fmt::Debug for ValueLabels<'_> {
1334 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1335 f.debug_list().entries(self.clone()).finish()
1336 }
1337}
1338#[cfg(feature = "serde")]
1339impl serde::Serialize for ValueLabels<'_> {
1340 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1341 serialize_counted_sequence(self.clone(), serializer)
1342 }
1343}
1344
1345#[derive(Clone)]
1347pub struct Units<'a> {
1348 current: &'static [Unit],
1349 rest: OrthogonalVifes<'a>,
1350}
1351impl Iterator for Units<'_> {
1352 type Item = Unit;
1353 fn next(&mut self) -> Option<Self::Item> {
1354 loop {
1355 if let Some((first, tail)) = self.current.split_first() {
1356 self.current = tail;
1357 return Some(*first);
1358 }
1359 self.current = self.rest.next()?.units;
1360 }
1361 }
1362}
1363impl core::fmt::Debug for Units<'_> {
1364 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1365 f.debug_list().entries(self.clone()).finish()
1366 }
1367}
1368#[cfg(feature = "serde")]
1369impl serde::Serialize for Units<'_> {
1370 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1371 serialize_counted_sequence(self.clone(), serializer)
1372 }
1373}
1374#[cfg(feature = "defmt")]
1375impl defmt::Format for ValueInformation<'_> {
1376 fn format(&self, f: defmt::Formatter) {
1377 defmt::write!(
1378 f,
1379 "ValueInformation{{ decimal_offset_exponent: {}, decimal_scale_exponent: {}",
1380 self.decimal_offset_exponent,
1381 self.decimal_scale_exponent
1382 );
1383 let mut labels = self.labels().peekable();
1384 if labels.peek().is_some() {
1385 defmt::write!(f, ", labels: [");
1386 for (i, label) in labels.enumerate() {
1387 if i != 0 {
1388 defmt::write!(f, ", ");
1389 }
1390 defmt::write!(f, "{:?}", label);
1391 }
1392 defmt::write!(f, "]");
1393 }
1394 let mut units = self.units().peekable();
1395 if units.peek().is_some() {
1396 defmt::write!(f, ", units: [");
1397 for (i, unit) in units.enumerate() {
1398 if i != 0 {
1399 defmt::write!(f, ", ");
1400 }
1401 defmt::write!(f, "{:?}", unit);
1402 }
1403 defmt::write!(f, "]");
1404 }
1405 defmt::write!(f, " }}");
1406 }
1407}
1408
1409#[cfg(feature = "std")]
1410impl fmt::Display for ValueInformation<'_> {
1411 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1412 if self.decimal_offset_exponent != 0 {
1413 write!(f, "+{})", self.decimal_offset_exponent)?;
1414 } else {
1415 write!(f, ")")?;
1416 }
1417 if self.decimal_scale_exponent != 0 {
1418 write!(f, "e{}", self.decimal_scale_exponent)?;
1419 }
1420 let mut units = self.units().peekable();
1421 if units.peek().is_some() {
1422 write!(f, "[")?;
1423 for unit in units {
1424 write!(f, "{}", unit)?;
1425 }
1426 write!(f, "]")?;
1427 }
1428 let mut labels = self.labels().peekable();
1429 if labels.peek().is_some() {
1430 write!(f, "(")?;
1431 for (i, label) in labels.enumerate() {
1432 if i != 0 {
1433 write!(f, ", ")?;
1434 }
1435 write!(f, "{:?}", label)?;
1436 }
1437
1438 return write!(f, ")");
1439 }
1440 Ok(())
1441 }
1442}
1443#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1444#[derive(Debug, Clone, Copy, PartialEq)]
1445#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1446#[non_exhaustive]
1447pub enum ValueLabel {
1448 Instantaneous,
1449 ReservedForObjectActions,
1450 Reserved,
1451 Averaged,
1452 Integral,
1453 Parameter,
1454 InverseCompactProfile,
1455 RelativeDeviation,
1456 RecordErrorCodes,
1457 StandardConformDataContent,
1458 CompactProfileWithRegisterNumbers,
1459 CompactProfile,
1460 ActualityDuration,
1461 AveragingDuration,
1462 Date,
1463 Time,
1464 DateTime,
1465 DateTimeWithSeconds,
1466 FabricationNumber,
1467 EnhancedIdentification,
1468 Address,
1469 PlainText,
1470 RevolutionOrMeasurement,
1471 IncrementPerInputPulseOnChannelP,
1472 IncrementPerOutputPulseOnChannelP,
1473 HourMinuteSecond,
1474 DayMonthYear,
1475 StartDateOf,
1476 VifContainsUncorrectedUnitOrValue,
1477 AccumulationOnlyIfValueIsPositive,
1478 AccumulationOnlyIfValueIsNegative,
1479 NonMetricUnits,
1480 AlternativeNonMetricUnits,
1481 ValueAtBaseConditions,
1482 ObisDeclaration,
1483 UpperLimitValue,
1484 LowerLimitValue,
1485 NumberOfExceedsOfUpperLimitValue,
1486 NumberOfExceedsOfLowerLimitValue,
1487 DateOfBeginFirstLowerLimitExceed,
1488 DateOfBeginFirstUpperLimitExceed,
1489 DateOfBeginLastLowerLimitExceed,
1490 DateOfBeginLastUpperLimitExceed,
1491 DateOfEndLastLowerLimitExceed,
1492 DateOfEndLastUpperLimitExceed,
1493 DateOfEndFirstLowerLimitExceed,
1494 DateOfEndFirstUpperLimitExceed,
1495 DurationOfFirstLowerLimitExceed,
1496 DurationOfFirstUpperLimitExceed,
1497 DurationOfLastLowerLimitExceed,
1498 DurationOfLastUpperLimitExceed,
1499 DurationOfFirst,
1500 DurationOfLast,
1501 ValueDuringLowerValueExceed,
1502 ValueDuringUpperValueExceed,
1503 LeakageValues,
1504 OverflowValues,
1505 DateOfBeginLast,
1506 DateOfBeginFirst,
1507 DateOfEndLast,
1508 DateOfEndFirst,
1509 ExtensionOfCombinableOrthogonalVIFE,
1510 FutureValue,
1511 NextVIFEAndDataOfThisBlockAreManufacturerSpecific,
1512 Credit,
1513 Debit,
1514 UniqueMessageIdentificationOrAccessNumber,
1515 DeviceType,
1516 Manufacturer,
1517 ParameterSetIdentification,
1518 ModelOrVersion,
1519 HardwareVersion,
1520 MetrologyFirmwareVersion,
1521 OtherSoftwareVersion,
1522 CustomerLocation,
1523 Customer,
1524 AccessCodeUser,
1525 AccessCodeOperator,
1526 AccessCodeSystemOperator,
1527 AccessCodeDeveloper,
1528 Password,
1529 ErrorFlags,
1530 ErrorMask,
1531 SecurityKey,
1532 DigitalInput,
1533 DigitalOutput,
1534 Binary,
1535 BaudRate,
1536 ResponseDelayTime,
1537 Retry,
1538 RemoteControl,
1539 FirstStorageForCycleStorage,
1540 LastStorageForCycleStorage,
1541 SizeOfStorageBlock,
1542 DescriptionOfTariffAndSubunit,
1543 StorageInterval,
1544 Dimensionless,
1545 DimensionlessHCA,
1546 DataContainerForWmbusProtocol,
1547 PeriodOfNormalDataTransmission,
1548 ResetCounter,
1549 CumulationCounter,
1550 ControlSignal,
1551 DayOfWeek,
1552 WeekNumber,
1553 TimePointOfChangeOfTariff,
1554 StateOfParameterActivation,
1555 SpecialSupplierInformation,
1556 DurationSinceLastCumulation,
1557 OperatingTimeBattery,
1558 DateAndTimeOfBatteryChange,
1559 RFPowerLevel,
1560 DaylightSavingBeginningEndingDeviation,
1561 ListeningWindowManagementData,
1562 RemainingBatteryLifeTime,
1563 NumberOfTimesTheMeterWasStopped,
1564 DataContainerForManufacturerSpecificProtocol,
1565 CurrentlySelectedApplication,
1566 Energy,
1567 ReactiveEnergy,
1568 ApparentEnergy,
1569 CoefficientOfPerformance,
1570 ReactivePower,
1571 Frequency,
1572 ApparentPower,
1573 AtPhaseL1,
1574 AtPhaseL2,
1575 AtPhaseL3,
1576 AtNeutral,
1577 BetweenPhasesL1L2,
1578 BetweenPhasesL2L3,
1579 BetweenPhasesL3L1,
1580 AtQuadrant1,
1581 AtQuadrant2,
1582 AtQuadrant3,
1583 AtQuadrant4,
1584 DeltaBetweenImportAndExport,
1585 AccumulationOfAbsoluteValueBothPositiveAndNegativeContribution,
1586 SecondarySensorMeasurement,
1587 HigherResolutionRegister,
1588 DataPresentedWithTypeC,
1589 DataPresentedWithTypeD,
1590 EndDate,
1591 DirectionFromCommunicationPartnerToMeter,
1592 DirectionFromMeterToCommunicationPartner,
1593 RelativeHumidity,
1594 MoistureLevel,
1595 PhaseUtoU,
1596 PhaseUtoI,
1597 PhaseItoU,
1598 ColdWarmTemperatureLimit,
1599 CumulativeMaximumOfActivePower,
1600 ResultingRatingFactor,
1601 ThermalOutputRatingFactor,
1602 ThermalCouplingRatingFactorOverall,
1603 ThermalCouplingRatingRoomSide,
1604 ThermalCouplingRatingFactorHeatingSide,
1605 LowTemperatureRatingFactor,
1606 DisplayOutputScalingFactor,
1607 ManufacturerSpecific,
1608 OnTime,
1609 OperatingTime,
1610 Volume,
1611 Mass,
1612 Power,
1613 VolumeFlow,
1614 MassFlow,
1615 Pressure,
1616 Voltage,
1617 Current,
1618 FlowTemperature,
1619 ReturnTemperature,
1620 TemperatureDifference,
1621 ExternalTemperature,
1622}
1623
1624#[cfg(feature = "std")]
1625impl fmt::Display for Unit {
1626 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1627 let superscripts = ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
1628 let invalid_superscript = '⁻';
1629 match self.exponent {
1630 1 => write!(f, "{}", self.name),
1631 0..=9 => write!(
1632 f,
1633 "{}{}",
1634 self.name,
1635 superscripts
1636 .get(self.exponent as usize)
1637 .unwrap_or(&invalid_superscript)
1638 ),
1639 10..=19 => write!(
1640 f,
1641 "{}{}{}",
1642 self.name,
1643 superscripts.get(1).unwrap_or(&invalid_superscript),
1644 superscripts
1645 .get(self.exponent as usize - 10)
1646 .unwrap_or(&invalid_superscript)
1647 ),
1648 x if (-9..0).contains(&x) => {
1649 write!(
1650 f,
1651 "{}⁻{}",
1652 self.name,
1653 superscripts
1654 .get((-x) as usize)
1655 .unwrap_or(&invalid_superscript)
1656 )
1657 }
1658 x if (-19..0).contains(&x) => write!(
1659 f,
1660 "{}⁻{}{}",
1661 self.name,
1662 superscripts.get(1).unwrap_or(&invalid_superscript),
1663 superscripts
1664 .get((-x) as usize - 10)
1665 .unwrap_or(&invalid_superscript)
1666 ),
1667 x => write!(f, "{}^{}", self.name, x),
1668 }
1669 }
1670}
1671#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1672#[derive(Debug, Clone, Copy, PartialEq)]
1673#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1674#[non_exhaustive]
1675pub enum UnitName {
1676 Watt,
1677 ReactiveWatt,
1678 ApparentWatt,
1679 Joul,
1680 Kilogram,
1681 Tonne,
1682 Meter,
1683 Feet,
1684 Celsius,
1685 Kelvin,
1686 Bar,
1687 HCA,
1688 Reserved,
1689 WithoutUnits,
1690 Second,
1691 Minute,
1692 Hour,
1693 Day,
1694 Week,
1695 Month,
1696 Year,
1697 Revolution,
1698 Increment,
1699 InputPulseOnChannel0,
1700 OutputPulseOnChannel0,
1701 InputPulseOnChannel1,
1702 OutputPulseOnChannel1,
1703 Liter,
1704 Volt,
1705 Ampere,
1706 LocalMoneyCurrency,
1707 Symbol,
1708 BitTime,
1709 DecibelMilliWatt,
1710 Percent,
1711 Degree,
1712 Hertz,
1713 HCAUnit,
1714 Fahrenheit,
1715 AmericanGallon,
1716 Calorie,
1717}
1718
1719#[cfg(feature = "std")]
1720impl fmt::Display for UnitName {
1721 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1722 match self {
1723 UnitName::Watt => write!(f, "W"),
1724 UnitName::ReactiveWatt => write!(f, "W (reactive)"),
1725 UnitName::ApparentWatt => write!(f, "W (apparent)"),
1726 UnitName::Joul => write!(f, "J"),
1727 UnitName::Kilogram => write!(f, "Kg"),
1728 UnitName::Tonne => write!(f, "t"),
1729 UnitName::Meter => write!(f, "m"),
1730 UnitName::Feet => write!(f, "ft"),
1731 UnitName::Celsius => write!(f, "°C"),
1732 UnitName::Kelvin => write!(f, "°K"),
1733 UnitName::Bar => write!(f, "Bar"),
1734 UnitName::HCA => write!(f, "HCA"),
1735 UnitName::Reserved => write!(f, "Reserved"),
1736 UnitName::WithoutUnits => write!(f, "-"),
1737 UnitName::Second => write!(f, "s"),
1738 UnitName::Minute => write!(f, "min"),
1739 UnitName::Hour => write!(f, "h"),
1740 UnitName::Day => write!(f, "day"),
1741 UnitName::Week => write!(f, "week"),
1742 UnitName::Month => write!(f, "month"),
1743 UnitName::Year => write!(f, "year"),
1744 UnitName::Revolution => write!(f, "revolution"),
1745 UnitName::Increment => write!(f, "increment"),
1746 UnitName::InputPulseOnChannel0 => write!(f, "InputPulseOnChannel0"),
1747 UnitName::OutputPulseOnChannel0 => write!(f, "OutputPulseOnChannel0"),
1748 UnitName::InputPulseOnChannel1 => write!(f, "InputPulseOnChannel1"),
1749 UnitName::OutputPulseOnChannel1 => write!(f, "OutputPulseOnChannel1"),
1750 UnitName::Liter => write!(f, "l"),
1751 UnitName::Volt => write!(f, "V"),
1752 UnitName::Ampere => write!(f, "A"),
1753 UnitName::LocalMoneyCurrency => write!(f, "$ (local)"),
1754 UnitName::Symbol => write!(f, "Symbol"),
1755 UnitName::BitTime => write!(f, "BitTime"),
1756 UnitName::DecibelMilliWatt => write!(f, "dBmW"),
1757 UnitName::Percent => write!(f, "%"),
1758 UnitName::Degree => write!(f, "°"),
1759 UnitName::Hertz => write!(f, "Hz"),
1760 UnitName::HCAUnit => write!(f, "HCAUnit"),
1761 UnitName::Fahrenheit => write!(f, "°F"),
1762 UnitName::AmericanGallon => write!(f, "UsGal"),
1763 UnitName::Calorie => write!(f, "cal"),
1764 }
1765 }
1766}
1767
1768#[cfg(test)]
1769mod tests {
1770 fn assert_information(
1771 actual: super::ValueInformation<'_>,
1772 offset: isize,
1773 scale: isize,
1774 labels: &[super::ValueLabel],
1775 units: &[super::Unit],
1776 ) {
1777 assert_eq!(actual.decimal_offset_exponent, offset);
1778 assert_eq!(actual.decimal_scale_exponent, scale);
1779 assert!(actual.labels().eq(labels.iter().copied()));
1780 assert!(actual.units().eq(units.iter().copied()));
1781 }
1782
1783 #[test]
1784 fn value_information_peeks_remaining_extension_bytes_without_consuming_them() {
1785 use super::{
1786 ValueInformation, ValueInformationBlock, ValueInformationFieldExtensions, ValueLabel,
1787 };
1788 let bytes = [0x80, 0xfd, 0x3e];
1789 let mut extensions = ValueInformationFieldExtensions::new(&bytes).unwrap();
1790 assert_eq!(extensions.next().unwrap().data, 0x80);
1791 let block = ValueInformationBlock::new(0xfd.into(), Some(extensions), None);
1792 let info = ValueInformation::try_from(&block).unwrap();
1793 assert!(info.has_label(ValueLabel::MoistureLevel));
1794 assert_eq!(
1795 info,
1796 ValueInformation::try_from(
1797 &ValueInformationBlock::try_from([0xfd, 0xfd, 0x3e].as_slice()).unwrap()
1798 )
1799 .unwrap()
1800 );
1801 assert_eq!(block.value_information_extension.as_ref().unwrap().len(), 2);
1802 assert_eq!(ValueInformation::try_from(&block).unwrap(), info);
1803 }
1804
1805 #[test]
1806 fn test_single_byte_primary_value_information_parsing() {
1807 use crate::value_information::UnitName;
1808 use crate::value_information::{
1809 Unit, ValueInformation, ValueInformationBlock, ValueInformationField, ValueLabel,
1810 };
1811
1812 let data = [0x13];
1814 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1815 assert_eq!(
1816 result,
1817 ValueInformationBlock {
1818 value_information: ValueInformationField::from(0x13),
1819 value_information_extension: None,
1820 plaintext_vife: None
1821 }
1822 );
1823 assert_eq!(result.get_size(), 1);
1824 assert_information(
1825 ValueInformation::try_from(&result).unwrap(),
1826 0,
1827 -3,
1828 &[ValueLabel::Volume],
1829 &[unit!(Meter ^ 3)],
1830 );
1831
1832 let data = [0x14];
1834 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1835 assert_eq!(
1836 result,
1837 ValueInformationBlock {
1838 value_information: ValueInformationField::from(0x14),
1839 value_information_extension: None,
1840 plaintext_vife: None
1841 }
1842 );
1843 assert_eq!(result.get_size(), 1);
1844 assert_information(
1845 ValueInformation::try_from(&result).unwrap(),
1846 0,
1847 -2,
1848 &[ValueLabel::Volume],
1849 &[unit!(Meter ^ 3)],
1850 );
1851
1852 let data = [0x15];
1854 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1855 assert_eq!(
1856 result,
1857 ValueInformationBlock {
1858 value_information: ValueInformationField::from(0x15),
1859 value_information_extension: None,
1860 plaintext_vife: None
1861 }
1862 );
1863 assert_eq!(result.get_size(), 1);
1864 assert_information(
1865 ValueInformation::try_from(&result).unwrap(),
1866 0,
1867 -1,
1868 &[ValueLabel::Volume],
1869 &[unit!(Meter ^ 3)],
1870 );
1871
1872 let data = [0x16];
1874 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1875 assert_eq!(
1876 result,
1877 ValueInformationBlock {
1878 value_information: ValueInformationField::from(0x16),
1879 value_information_extension: None,
1880 plaintext_vife: None
1881 },
1882 );
1883 assert_eq!(result.get_size(), 1);
1884 }
1885
1886 #[test]
1887 fn test_multibyte_primary_value_information() {
1888 use crate::value_information::UnitName;
1889 use crate::value_information::{
1890 Unit, ValueInformation, ValueInformationBlock, ValueInformationField, ValueLabel,
1891 };
1892
1893 let data = [0x96, 0x12];
1899 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1900 assert_eq!(result.get_size(), 2);
1901 assert_eq!(result.value_information, ValueInformationField::from(0x96));
1902 assert!(ValueInformation::try_from(&result)
1903 .unwrap()
1904 .labels()
1905 .eq([ValueLabel::Volume, ValueLabel::Averaged]));
1906
1907 let data = [0x96, 0x92, 0x20];
1913 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1914 assert_eq!(result.get_size(), 3);
1915 assert_eq!(result.value_information, ValueInformationField::from(0x96));
1916 assert_information(
1917 ValueInformation::try_from(&result).unwrap(),
1918 0,
1919 0,
1920 &[ValueLabel::Volume, ValueLabel::Averaged],
1921 &[unit!(Meter ^ 3), unit!(Second ^ -1)],
1922 );
1923
1924 let data = [0x96, 0x92, 0xA0, 0x2D];
1931 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1932 assert_eq!(result.get_size(), 4);
1933 assert_eq!(result.value_information, ValueInformationField::from(0x96));
1934 assert_information(
1935 ValueInformation::try_from(&result).unwrap(),
1936 0,
1937 0,
1938 &[ValueLabel::Volume, ValueLabel::Averaged],
1939 &[unit!(Meter ^ 3), unit!(Second ^ -1), unit!(Meter ^ -3)],
1940 );
1941 }
1942
1943 #[cfg(not(feature = "plaintext-before-extension"))]
1944 #[test]
1945 fn test_plain_text_vif_norm_conform() {
1946 use crate::value_information::{ValueInformation, ValueLabel};
1947
1948 use crate::value_information::ValueInformationBlock;
1949 let data = [0xFC, 0x74, 0x03, 0x48, 0x52, 0x25];
1960 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1961 assert_eq!(result.get_size(), 6);
1962 assert_eq!(result.value_information.data, 0xFC);
1963 assert_information(
1964 ValueInformation::try_from(&result).unwrap(),
1965 0,
1966 -2,
1967 &[ValueLabel::PlainText],
1968 &[],
1969 );
1970
1971 }
1981
1982 #[test]
1983 fn test_short_vif_with_vife() {
1984 use crate::value_information::ValueInformationBlock;
1985 let data = [253, 27];
1986 let result = ValueInformationBlock::try_from(data.as_slice()).unwrap();
1987 assert_eq!(result.get_size(), 2);
1988 }
1989
1990 #[test]
1991 fn test_vif_fd_voltage_and_ampere() {
1992 use crate::value_information::UnitName;
1993 use crate::value_information::{ValueInformation, ValueInformationBlock};
1994
1995 let vi = ValueInformation::try_from(
1997 &ValueInformationBlock::try_from([0xFD, 0x48].as_slice()).unwrap(),
1998 )
1999 .unwrap();
2000 assert_eq!(vi.first_unit().unwrap().name, UnitName::Volt);
2001 assert_eq!(vi.decimal_scale_exponent, -1);
2002
2003 let vi = ValueInformation::try_from(
2005 &ValueInformationBlock::try_from([0xFD, 0x59].as_slice()).unwrap(),
2006 )
2007 .unwrap();
2008 assert_eq!(vi.first_unit().unwrap().name, UnitName::Ampere);
2009 assert_eq!(vi.decimal_scale_exponent, -3);
2010 }
2011
2012 #[test]
2013 fn test_vif_fb_added_codes_and_reserved_fallback() {
2014 use crate::value_information::UnitName;
2015 use crate::value_information::{ValueInformation, ValueInformationBlock, ValueLabel};
2016
2017 let vi = ValueInformation::try_from(
2019 &ValueInformationBlock::try_from([0xFB, 0x20].as_slice()).unwrap(),
2020 )
2021 .unwrap();
2022 assert_eq!(vi.first_unit().unwrap().name, UnitName::Feet);
2023 assert_eq!(vi.first_unit().unwrap().exponent, 3);
2024 assert_eq!(vi.decimal_scale_exponent, 0);
2025 assert!(vi.has_label(ValueLabel::Volume));
2026
2027 let vi = ValueInformation::try_from(
2029 &ValueInformationBlock::try_from([0xFB, 0x23].as_slice()).unwrap(),
2030 )
2031 .unwrap();
2032 assert_eq!(vi.first_unit().unwrap().name, UnitName::Degree);
2033 assert_eq!(vi.decimal_scale_exponent, -1);
2034 assert!(vi.has_label(ValueLabel::PhaseItoU));
2035
2036 let vi = ValueInformation::try_from(
2038 &ValueInformationBlock::try_from([0xFB, 0x70].as_slice()).unwrap(),
2039 )
2040 .unwrap();
2041 assert_eq!(vi.first_unit().unwrap().name, UnitName::Fahrenheit);
2042 assert_eq!(vi.decimal_scale_exponent, -3);
2043
2044 let vi = ValueInformation::try_from(
2046 &ValueInformationBlock::try_from([0xFB, 0x22].as_slice()).unwrap(),
2047 )
2048 .unwrap();
2049 assert!(vi.has_label(ValueLabel::Reserved));
2050 }
2051
2052 #[test]
2053 fn test_primary_vif_on_time_and_operating_time_labels() {
2054 use crate::value_information::{
2055 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2056 };
2057
2058 let vi = ValueInformation::try_from(
2060 &ValueInformationBlock::try_from([0x21].as_slice()).unwrap(),
2061 )
2062 .unwrap();
2063 assert!(vi.has_label(ValueLabel::OnTime));
2064 assert_eq!(vi.first_unit().unwrap().name, UnitName::Minute);
2065
2066 let vi = ValueInformation::try_from(
2068 &ValueInformationBlock::try_from([0x27].as_slice()).unwrap(),
2069 )
2070 .unwrap();
2071 assert!(vi.has_label(ValueLabel::OperatingTime));
2072 assert_eq!(vi.first_unit().unwrap().name, UnitName::Day);
2073 }
2074
2075 #[test]
2076 fn test_fb_cumulative_maximum_of_active_power() {
2077 use crate::value_information::{
2078 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2079 };
2080
2081 let vi = ValueInformation::try_from(
2083 &ValueInformationBlock::try_from([0xFB, 0x78].as_slice()).unwrap(),
2084 )
2085 .unwrap();
2086 assert!(vi.has_label(ValueLabel::CumulativeMaximumOfActivePower));
2087 assert_eq!(vi.first_unit().unwrap().name, UnitName::Watt);
2088 assert_eq!(vi.decimal_scale_exponent, -3);
2089 }
2090
2091 #[test]
2092 fn test_fb_humidity_with_combinatorial_scale() {
2093 use crate::value_information::{
2094 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2095 };
2096
2097 let vi = ValueInformation::try_from(
2100 &ValueInformationBlock::try_from([0xFB, 0x9B, 0x74].as_slice()).unwrap(),
2101 )
2102 .unwrap();
2103
2104 assert!(vi.has_label(ValueLabel::RelativeHumidity));
2105 assert_eq!(vi.first_unit().unwrap().name, UnitName::Percent);
2106 assert_eq!(vi.decimal_scale_exponent, -2);
2107 }
2108
2109 #[test]
2110 fn test_fd_ampere_with_phase_combinatorial() {
2111 use crate::value_information::{
2112 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2113 };
2114
2115 let vi = ValueInformation::try_from(
2119 &ValueInformationBlock::try_from([0xFD, 0xD9, 0xFC, 0x01].as_slice()).unwrap(),
2120 )
2121 .unwrap();
2122
2123 assert_eq!(vi.first_unit().unwrap().name, UnitName::Ampere);
2124 assert_eq!(vi.decimal_scale_exponent, -3);
2125 assert!(vi.has_label(ValueLabel::AtPhaseL1));
2126 }
2127
2128 #[test]
2129 fn test_primary_vif_combinatorial_not_skipped() {
2130 use crate::value_information::{
2131 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2132 };
2133
2134 let vi = ValueInformation::try_from(
2138 &ValueInformationBlock::try_from([0xE5, 0x74].as_slice()).unwrap(),
2139 )
2140 .unwrap();
2141
2142 assert!(vi.has_label(ValueLabel::ExternalTemperature));
2143 assert_eq!(vi.first_unit().unwrap().name, UnitName::Celsius);
2144 assert_eq!(vi.decimal_scale_exponent, -4);
2145 }
2146
2147 #[test]
2148 fn test_fd_moisture_level() {
2149 use crate::value_information::{
2150 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2151 };
2152
2153 let vi = ValueInformation::try_from(
2156 &ValueInformationBlock::try_from([0xFD, 0xFD, 0x3E].as_slice()).unwrap(),
2157 )
2158 .unwrap();
2159
2160 assert!(vi.has_label(ValueLabel::MoistureLevel));
2161 assert_eq!(vi.first_unit().unwrap().name, UnitName::Percent);
2162 assert_eq!(vi.decimal_scale_exponent, 0);
2163 }
2164
2165 #[test]
2166 fn test_vib_struct_layout() {
2167 use crate::value_information::ValueInformationBlock;
2168
2169 let vib = ValueInformationBlock::try_from([0xFD, 0xD9, 0xFC, 0x01].as_slice()).unwrap();
2172
2173 assert_eq!(vib.value_information.data, 0xFD);
2174 assert_eq!(vib.get_size(), 4);
2175 assert!(vib.plaintext_vife.is_none());
2176
2177 let mut ext = vib.value_information_extension.unwrap();
2178 assert_eq!(ext.len(), 3);
2179 assert_eq!(ext.next().unwrap().data, 0xD9);
2180 assert_eq!(ext.next().unwrap().data, 0xFC);
2181 assert_eq!(ext.next().unwrap().data, 0x01);
2182
2183 let vib = ValueInformationBlock::try_from([0x96, 0x12].as_slice()).unwrap();
2186
2187 assert_eq!(vib.value_information.data, 0x96);
2188 assert_eq!(vib.get_size(), 2);
2189
2190 let mut ext = vib.value_information_extension.unwrap();
2191 assert_eq!(ext.len(), 1);
2192 assert_eq!(ext.next().unwrap().data, 0x12);
2193
2194 let vib = ValueInformationBlock::try_from([0x13].as_slice()).unwrap();
2196
2197 assert_eq!(vib.value_information.data, 0x13);
2198 assert_eq!(vib.get_size(), 1);
2199 assert!(vib.value_information_extension.is_none());
2200 }
2201
2202 #[test]
2203 fn test_combinable_orthogonal_vife_limit_exceed_mappings() {
2204 use crate::value_information::{
2205 UnitName, ValueInformation, ValueInformationBlock, ValueLabel,
2206 };
2207
2208 let cases: &[(u8, ValueLabel, Option<UnitName>)] = &[
2210 (0x40, ValueLabel::LowerLimitValue, None),
2211 (0x48, ValueLabel::UpperLimitValue, None),
2212 (0x41, ValueLabel::NumberOfExceedsOfLowerLimitValue, None),
2213 (0x49, ValueLabel::NumberOfExceedsOfUpperLimitValue, None),
2214 (0x42, ValueLabel::DateOfBeginFirstLowerLimitExceed, None),
2216 (0x43, ValueLabel::DateOfEndFirstLowerLimitExceed, None),
2217 (0x46, ValueLabel::DateOfBeginLastLowerLimitExceed, None),
2218 (0x47, ValueLabel::DateOfEndLastLowerLimitExceed, None),
2219 (0x4A, ValueLabel::DateOfBeginFirstUpperLimitExceed, None),
2220 (0x4B, ValueLabel::DateOfEndFirstUpperLimitExceed, None),
2221 (0x4E, ValueLabel::DateOfBeginLastUpperLimitExceed, None),
2222 (0x4F, ValueLabel::DateOfEndLastUpperLimitExceed, None),
2223 (
2225 0x50,
2226 ValueLabel::DurationOfFirstLowerLimitExceed,
2227 Some(UnitName::Second),
2228 ),
2229 (
2230 0x51,
2231 ValueLabel::DurationOfFirstLowerLimitExceed,
2232 Some(UnitName::Minute),
2233 ),
2234 (
2235 0x52,
2236 ValueLabel::DurationOfFirstLowerLimitExceed,
2237 Some(UnitName::Hour),
2238 ),
2239 (
2240 0x53,
2241 ValueLabel::DurationOfFirstLowerLimitExceed,
2242 Some(UnitName::Day),
2243 ),
2244 (
2246 0x54,
2247 ValueLabel::DurationOfLastLowerLimitExceed,
2248 Some(UnitName::Second),
2249 ),
2250 (
2251 0x55,
2252 ValueLabel::DurationOfLastLowerLimitExceed,
2253 Some(UnitName::Minute),
2254 ),
2255 (
2256 0x56,
2257 ValueLabel::DurationOfLastLowerLimitExceed,
2258 Some(UnitName::Hour),
2259 ),
2260 (
2261 0x57,
2262 ValueLabel::DurationOfLastLowerLimitExceed,
2263 Some(UnitName::Day),
2264 ),
2265 (
2267 0x58,
2268 ValueLabel::DurationOfFirstUpperLimitExceed,
2269 Some(UnitName::Second),
2270 ),
2271 (
2272 0x59,
2273 ValueLabel::DurationOfFirstUpperLimitExceed,
2274 Some(UnitName::Minute),
2275 ),
2276 (
2277 0x5A,
2278 ValueLabel::DurationOfFirstUpperLimitExceed,
2279 Some(UnitName::Hour),
2280 ),
2281 (
2282 0x5B,
2283 ValueLabel::DurationOfFirstUpperLimitExceed,
2284 Some(UnitName::Day),
2285 ),
2286 (
2288 0x5C,
2289 ValueLabel::DurationOfLastUpperLimitExceed,
2290 Some(UnitName::Second),
2291 ),
2292 (
2293 0x5D,
2294 ValueLabel::DurationOfLastUpperLimitExceed,
2295 Some(UnitName::Minute),
2296 ),
2297 (
2298 0x5E,
2299 ValueLabel::DurationOfLastUpperLimitExceed,
2300 Some(UnitName::Hour),
2301 ),
2302 (
2303 0x5F,
2304 ValueLabel::DurationOfLastUpperLimitExceed,
2305 Some(UnitName::Day),
2306 ),
2307 ];
2308
2309 for (vife_byte, expected_label, expected_unit) in cases {
2310 let data = [0x93, *vife_byte];
2311 let vib = ValueInformationBlock::try_from(data.as_slice()).unwrap();
2312 let vi = ValueInformation::try_from(&vib).unwrap();
2313 assert!(
2314 vi.has_label(*expected_label),
2315 "VIFE 0x{vife_byte:02X}: expected label {expected_label:?}, got {:?}",
2316 vi.labels()
2317 );
2318 if let Some(unit_name) = expected_unit {
2319 assert!(
2320 vi.units().any(|u| u.name == *unit_name),
2321 "VIFE 0x{vife_byte:02X}: expected unit {unit_name:?}, got {:?}",
2322 vi.units()
2323 );
2324 }
2325 }
2326 }
2327
2328 #[test]
2329 fn test_combinable_orthogonal_vife_fc_extension_mappings() {
2330 use crate::value_information::{ValueInformation, ValueInformationBlock, ValueLabel};
2331
2332 let cases: &[(u8, ValueLabel)] = &[
2333 (0x02, ValueLabel::AtPhaseL2),
2334 (0x0D, ValueLabel::AlternativeNonMetricUnits),
2335 (0x0E, ValueLabel::SecondarySensorMeasurement),
2336 (0x13, ValueLabel::EndDate),
2337 ];
2338
2339 for (vife_byte, expected_label) in cases {
2340 let data = [0x93, 0xFC, *vife_byte];
2341 let vib = ValueInformationBlock::try_from(data.as_slice()).unwrap();
2342 let vi = ValueInformation::try_from(&vib).unwrap();
2343 assert!(
2344 vi.has_label(*expected_label),
2345 "FC VIFE 0x{vife_byte:02X}: expected {expected_label:?}, got {:?}",
2346 vi.labels()
2347 );
2348 }
2349 }
2350 #[test]
2351 fn units_exceed_old_capacity() {
2352 use super::*;
2353 let block =
2354 ValueInformationBlock::try_from([0xB8, 0xA8, 0xA8, 0xA8, 0xA8, 0x28].as_slice())
2355 .unwrap();
2356 let vi = ValueInformation::try_from(&block).unwrap();
2357 assert!(vi
2358 .units()
2359 .eq([unit!(Meter ^ 3), unit!(Hour ^ -1)].into_iter().chain(
2360 core::iter::repeat_n([unit!(Increment), unit!(InputPulseOnChannel0 ^ -1)], 5)
2361 .flatten()
2362 )));
2363 assert_eq!(vi.units().count(), 12);
2364 }
2365
2366 #[test]
2367 fn labels_exceed_old_capacity() {
2368 use super::*;
2369 let block = ValueInformationBlock::try_from(
2370 [
2371 0xFD, 0x9A, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x12,
2372 ]
2373 .as_slice(),
2374 )
2375 .unwrap();
2376 let vi = ValueInformation::try_from(&block).unwrap();
2377 assert!(vi
2378 .labels()
2379 .eq([ValueLabel::DigitalOutput, ValueLabel::Binary]
2380 .into_iter()
2381 .chain(core::iter::repeat_n(ValueLabel::Averaged, 9))));
2382 assert_eq!(vi.labels().count(), 11);
2383 }
2384
2385 #[test]
2386 fn repeated_fc_and_terminal_7c_preserve_table_selection() {
2387 use super::*;
2388 for (bytes, expected) in [
2389 (
2390 &[0x93, 0xFC, 0xFC, 0x01][..],
2391 &[ValueLabel::Volume, ValueLabel::AtPhaseL1][..],
2392 ),
2393 (
2394 &[0x93, 0x7C][..],
2395 &[ValueLabel::Volume, ValueLabel::Reserved][..],
2396 ),
2397 (
2398 &[0x93, 0xFC, 0x81, 0x12][..],
2399 &[
2400 ValueLabel::Volume,
2401 ValueLabel::AtPhaseL1,
2402 ValueLabel::Averaged,
2403 ][..],
2404 ),
2405 ] {
2406 let block = ValueInformationBlock::try_from(bytes).unwrap();
2407 assert!(ValueInformation::try_from(&block)
2408 .unwrap()
2409 .labels()
2410 .eq(expected.iter().copied()));
2411 }
2412 }
2413
2414 #[test]
2415 fn main_extension_subcode_is_also_orthogonal() {
2416 use super::*;
2417 let block = ValueInformationBlock::try_from([0xFD, 0xFD, 0x3E].as_slice()).unwrap();
2418 let vi = ValueInformation::try_from(&block).unwrap();
2419 assert!(vi
2420 .labels()
2421 .eq([ValueLabel::MoistureLevel, ValueLabel::ValueAtBaseConditions]));
2422 assert!(vi.units().eq([unit!(Percent)]));
2423 }
2424
2425 #[test]
2426 fn equality_is_semantic_and_iterators_outlive_the_view() {
2427 use super::*;
2428 let short = ValueInformationBlock::try_from([0x13].as_slice()).unwrap();
2429 let equivalent = ValueInformationBlock::try_from([0x93, 0x76].as_slice()).unwrap();
2431 assert_eq!(
2432 ValueInformation::try_from(&short).unwrap(),
2433 ValueInformation::try_from(&equivalent).unwrap()
2434 );
2435 let (mut labels, mut units) = {
2436 let vi = ValueInformation::try_from(&short).unwrap();
2437 (vi.labels(), vi.units())
2438 };
2439 assert_eq!(labels.next(), Some(ValueLabel::Volume));
2440 assert_eq!(units.next(), Some(unit!(Meter ^ 3)));
2441 assert!(labels.clone().eq(labels));
2442 assert!(units.clone().eq(units));
2443 }
2444
2445 #[test]
2446 fn manufacturer_escape_stops_standard_vife_decoding() {
2447 use super::*;
2448 let block = ValueInformationBlock::try_from([0x84, 0xFF, 0xF2, 0x00].as_slice()).unwrap();
2452 let vi = ValueInformation::try_from(&block).unwrap();
2453 assert_eq!(vi.decimal_scale_exponent, 1);
2454 let mut labels = vi.labels();
2455 assert_eq!(labels.next(), Some(ValueLabel::Energy));
2456 assert_eq!(
2457 labels.next(),
2458 Some(ValueLabel::NextVIFEAndDataOfThisBlockAreManufacturerSpecific)
2459 );
2460 assert_eq!(labels.next(), None);
2461 }
2462
2463 #[test]
2464 fn exponents_accumulate_and_iterators_clone_mid_chain() {
2465 use super::*;
2466 let block =
2467 ValueInformationBlock::try_from([0x93, 0x92, 0xA8, 0xF5, 0x78].as_slice()).unwrap();
2468 let vi = ValueInformation::try_from(&block).unwrap();
2469 assert_eq!(vi.decimal_scale_exponent, -4);
2470 assert_eq!(vi.decimal_offset_exponent, -3);
2471 let mut units = vi.units();
2472 assert_eq!(units.next(), Some(unit!(Meter ^ 3)));
2473 assert_eq!(units.next(), Some(unit!(Increment)));
2474 assert!(units.clone().eq(units));
2475 let mut labels = vi.labels();
2476 assert_eq!(labels.next(), Some(ValueLabel::Volume));
2477 assert!(labels.clone().eq(labels));
2478 }
2479
2480 #[test]
2481 fn head_table_and_missing_extensions() {
2482 use super::*;
2483 let cases = [
2484 (
2485 0x13,
2486 None,
2487 None,
2488 VifInfo {
2489 labels: &[ValueLabel::Volume],
2490 units: &[unit!(Meter ^ 3)],
2491 scale: -3,
2492 ..VifInfo::EMPTY
2493 },
2494 ),
2495 (
2496 0xFD,
2497 Some(0x1A),
2498 None,
2499 labels!(ValueLabel::DigitalOutput, ValueLabel::Binary),
2500 ),
2501 (
2502 0xFB,
2503 Some(0x1A),
2504 None,
2505 VifInfo {
2506 labels: &[ValueLabel::RelativeHumidity],
2507 units: &[unit!(Percent)],
2508 scale: -1,
2509 ..VifInfo::EMPTY
2510 },
2511 ),
2512 (
2513 0xFD,
2514 Some(0x7D),
2515 Some(0x3E),
2516 VifInfo {
2517 labels: &[ValueLabel::MoistureLevel],
2518 units: &[unit!(Percent)],
2519 ..VifInfo::EMPTY
2520 },
2521 ),
2522 (0x7C, None, None, labels!(ValueLabel::PlainText)),
2523 (0x7F, None, None, labels!(ValueLabel::ManufacturerSpecific)),
2524 ];
2525 for (vif, first, second, expected) in cases {
2526 assert_eq!(head_vif_info(vif.into(), first, second).unwrap(), expected);
2527 }
2528 assert!(matches!(
2529 head_vif_info(0x6F.into(), None, None),
2530 Err(DataInformationError::Unimplemented { .. })
2531 ));
2532 for vif in [0xFD, 0xFB] {
2533 let mut block = ValueInformationBlock {
2534 value_information: vif.into(),
2535 value_information_extension: None,
2536 plaintext_vife: None,
2537 };
2538 assert!(ValueInformation::try_from(&block)
2539 .unwrap()
2540 .labels()
2541 .next()
2542 .is_none());
2543 block.value_information_extension = Some(ValueInformationFieldExtensions(&[]));
2544 assert_eq!(
2545 ValueInformation::try_from(&block),
2546 Err(DataInformationError::DataTooShort)
2547 );
2548 }
2549 }
2550}