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