Skip to content

Commit 8627d05

Browse files
committedSep 14, 2024··
Implement all the remaining VIF parsing
Comes with a ton of confusion because 0xFB is the "first extension" but actually refers to Table 14 "Alternate extended VIF-code table" while 0xFD is the "second extension" but refers to Table 12 "Main VIFE-code extension table" which sounds like it should actually be the first extension. Lots of debugging and anger on that one. However, I ended up fixing that, adding extra debug info and updating the macro to also support `p` in place of `n`. Fun fun fun. Only 22 tests failing from the test suite now!
1 parent d32b780 commit 8627d05

File tree

2 files changed

+127
-11
lines changed

2 files changed

+127
-11
lines changed
 

‎libmbus_macros/src/lib.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use winnow::ascii;
33
use winnow::combinator::repeat;
44
use winnow::error::InputError;
55
use winnow::prelude::*;
6+
use winnow::token::one_of;
67
use winnow::Str;
78

89
#[proc_macro]
@@ -11,10 +12,17 @@ pub fn vif(input: TokenStream) -> TokenStream {
1112

1213
let (_, upper_bits, _, lower_bits, ns) = (
1314
'E'.void(),
14-
ascii::digit1::<_, InputError<Str>>.map(|s| u8::from_str_radix(s, 2).unwrap()),
15+
ascii::digit1::<_, InputError<Str>>
16+
.map(|s| u8::from_str_radix(s, 2).expect("upper must be a valid binary expression")),
1517
' '.void(),
16-
ascii::digit0.map(|s| u8::from_str_radix(s, 2).unwrap()),
17-
repeat::<_, _, String, _, _>(0..=4, 'n'),
18+
ascii::digit0.map(|s: &str| {
19+
if !s.is_empty() {
20+
u8::from_str_radix(s, 2).expect("lower must be a valid binary expression")
21+
} else {
22+
0
23+
}
24+
}),
25+
repeat::<_, _, String, _, _>(0..=4, one_of(('n', 'p'))),
1826
)
1927
.parse(raw_input.as_str())
2028
.unwrap();

‎src/parse/application_layer/vib.rs

+116-8
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,13 @@ impl ValueInfoBlock {
5757
.context(StrContext::Label("initial VIF"))
5858
.parse_next(input)?;
5959

60+
let mut vif_context = "VIF";
61+
6062
let value_type = match raw_value {
61-
value if value <= 0b0111_1010 => parse_table_10(value),
63+
value if value <= 0b0111_1010 => {
64+
vif_context = "VIF in table 10";
65+
parse_table_10(value)
66+
}
6267
VIF_EXTENSION_1 | VIF_EXTENSION_2 => {
6368
if !extension {
6469
return Err(
@@ -74,7 +79,7 @@ impl ValueInfoBlock {
7479
(extension, value) = parse_vif_byte
7580
.context(StrContext::Label("VIF extension byte"))
7681
.parse_next(input)?;
77-
if raw_value == VIF_EXTENSION_1 && value == VIF_EXTENSION_2 {
82+
if raw_value == VIF_EXTENSION_2 && value == VIF_EXTENSION_2 {
7883
if !extension {
7984
return Err(ErrMode::from_error_kind(input, ErrorKind::Verify)
8085
.add_context(
@@ -87,10 +92,13 @@ impl ValueInfoBlock {
8792
(extension, value) = parse_vif_byte
8893
.context(StrContext::Label("VIF extension layer 2 byte"))
8994
.parse_next(input)?;
95+
vif_context = "VIF in table 13";
9096
parse_table_13(value)
91-
} else if raw_value == VIF_EXTENSION_1 {
97+
} else if raw_value == VIF_EXTENSION_2 {
98+
vif_context = "VIF in table 12";
9299
parse_table_12(value)
93100
} else {
101+
vif_context = "VIF in table 14";
94102
parse_table_14(value)
95103
}
96104
}
@@ -109,7 +117,7 @@ impl ValueInfoBlock {
109117
ErrMode::from_error_kind(input, ErrorKind::Verify).add_context(
110118
input,
111119
&vif_checkpoint,
112-
StrContext::Label("reserved vif"),
120+
StrContext::Label(vif_context),
113121
),
114122
);
115123
};
@@ -175,21 +183,119 @@ fn parse_table_10(value: u8) -> Option<ValueType> {
175183

176184
fn parse_table_12(value: u8) -> Option<ValueType> {
177185
Some(match value {
186+
vif!(E000 00nn) => ValueType::Credit(exp(MASK_NN, value, -3)),
187+
vif!(E000 01nn) => ValueType::Debit(exp(MASK_NN, value, -3)),
188+
vif!(E000 1000) => ValueType::UniqueMessageIdentification,
189+
vif!(E000 1001) => ValueType::DeviceType,
190+
vif!(E000 1010) => ValueType::Manufacturer,
191+
vif!(E000 1011) => ValueType::ParameterSetIdentification,
192+
vif!(E000 1100) => ValueType::ModelVersion,
193+
vif!(E000 1101) => ValueType::HardwareVersionNumber,
194+
vif!(E000 1110) => ValueType::MetrologyFirmwareVersionNumber,
195+
vif!(E000 1111) => ValueType::OtherSoftwareVersionNumber,
196+
vif!(E001 0000) => ValueType::CustomerLocation,
197+
vif!(E001 0001) => ValueType::Customer,
198+
vif!(E001 0010) => ValueType::AccessCodeUser,
199+
vif!(E001 0011) => ValueType::AccessCodeOperator,
200+
vif!(E001 0100) => ValueType::AccessCodeSystemOperator,
201+
vif!(E001 0101) => ValueType::AccessCodeDeveloper,
202+
vif!(E001 0110) => ValueType::Password,
203+
vif!(E001 0111) => ValueType::ErrorFlags,
204+
vif!(E001 1000) => ValueType::ErrorMask,
205+
vif!(E001 1001) => ValueType::SecurityKey,
206+
vif!(E001 1010) => ValueType::DigitalOutput,
207+
vif!(E001 1011) => ValueType::DigitalInput,
208+
vif!(E001 1100) => ValueType::BaudRate,
209+
vif!(E001 1101) => ValueType::ResponseDelayTime,
210+
vif!(E001 1110) => ValueType::Retry,
211+
vif!(E001 1111) => ValueType::RemoteControl,
212+
vif!(E010 0000) => ValueType::FirstStorageNumberForCyclicStorage,
213+
vif!(E010 0001) => ValueType::LastStorageNumberForCyclicStorage,
214+
vif!(E010 0010) => ValueType::SizeOfStorageBlock,
215+
vif!(E010 0011) => ValueType::DescriptorForTariffAndSubunit,
216+
vif!(E010 01nn) => ValueType::StorageInterval(DurationType::decode_nn(value)),
217+
vif!(E010 1000) => ValueType::StorageInterval(DurationType::Months),
218+
vif!(E010 1001) => ValueType::StorageInterval(DurationType::Years),
219+
vif!(E010 1010) => ValueType::OperatorSpecific,
220+
vif!(E010 1011) => ValueType::TimePointSecond,
221+
vif!(E010 11nn) => ValueType::DurationSinceLastReadout(DurationType::decode_nn(value)),
222+
vif!(E011 0000) => ValueType::StartDateTimeOfTariff,
223+
// Unfortunate overlap so we can't use the macro :(
224+
// vif!(E011 00nn) => ValueType::DurationOfTariff(DurationType::decode_nn(value)),
225+
0b0011_0001..=0b0011_0011 => ValueType::DurationOfTariff(DurationType::decode_nn(value)),
178226
vif!(E011 01nn) => ValueType::PeriodOfTarrif(DurationType::decode_nn(value)),
179227
vif!(E011 1000) => ValueType::PeriodOfTarrif(DurationType::Months),
180228
vif!(E011 1001) => ValueType::PeriodOfTarrif(DurationType::Years),
181-
_ => todo!("table 12 {value} {value:x} {value:b}"),
229+
vif!(E011 1010) => ValueType::Dimensionless,
230+
vif!(E011 1011) => ValueType::WirelessContainer,
231+
vif!(E011 11nn) => {
232+
ValueType::PeriodOfNominalDataTransmissions(DurationType::decode_nn(value))
233+
}
234+
vif!(E100 nnnn) => ValueType::Volts(exp(MASK_NNNN, value, -9)),
235+
vif!(E101 nnnn) => ValueType::Amperes(exp(MASK_NNNN, value, -12)),
236+
vif!(E110 0000) => ValueType::ResetCounter,
237+
vif!(E110 0001) => ValueType::CumulationCounter,
238+
vif!(E110 0010) => ValueType::ControlSignal,
239+
vif!(E110 0011) => ValueType::DayOfWeek,
240+
vif!(E110 0100) => ValueType::WeekNumber,
241+
vif!(E110 0101) => ValueType::TimePointOfDayChange,
242+
vif!(E110 0110) => ValueType::StateOfParameterActivation,
243+
vif!(E110 0111) => ValueType::SpecialSupplierInformation,
244+
vif!(E110 10pp) => ValueType::DurationSinceLastCumulation(DurationType::decode_pp(value)),
245+
vif!(E110 11pp) => ValueType::OperatingTimeBattery(DurationType::decode_pp(value)),
246+
vif!(E111 0000) => ValueType::DateAndTimeOfBatteryChange,
247+
vif!(E111 0001) => ValueType::RFLevel,
248+
vif!(E111 0010) => ValueType::DSTTypeK,
249+
vif!(E111 0011) => ValueType::ListeningWindowManagement,
250+
vif!(E111 0100) => ValueType::RemainingBatteryLife(DurationType::Days),
251+
vif!(E111 0101) => ValueType::NumberTimesMeterStopped,
252+
vif!(E111 0110) => ValueType::ManufacturerSpecificContainer,
253+
_ => return None,
182254
})
183255
}
184256

185257
fn parse_table_13(value: u8) -> Option<ValueType> {
186-
todo!("table 13 {value} {value:x} {value:b}")
258+
Some(match value {
259+
vif!(E000 0000) => ValueType::CurrentlySelectedApplication,
260+
vif!(E000 0010) => ValueType::RemainingBatteryLife(DurationType::Months),
261+
vif!(E000 0011) => ValueType::RemainingBatteryLife(DurationType::Years),
262+
_ => return None,
263+
})
187264
}
188265

189266
fn parse_table_14(value: u8) -> Option<ValueType> {
267+
// "These codes were used until 2004, now they are reserved for future use."
190268
Some(match value {
191-
0b0000_0000..=0b0000_0001 => ValueType::Energy(EnergyUnit::MWh, exp(MASK_N, value, -1)),
192-
_ => todo!("table 14 {value} {value:x} {value:b}"),
269+
vif!(E000 000n) => ValueType::Energy(EnergyUnit::MWh, exp(MASK_N, value, -1)),
270+
vif!(E000 001n) => ValueType::ReactiveEnergy(exp(MASK_N, value, 0)),
271+
vif!(E000 010n) => ValueType::ApparentEnergy(exp(MASK_N, value, 0)),
272+
vif!(E000 100n) => ValueType::Energy(EnergyUnit::GJ, exp(MASK_N, value, -1)),
273+
vif!(E000 11nn) => ValueType::Energy(EnergyUnit::MCal, exp(MASK_NN, value, -1)),
274+
vif!(E001 000n) => ValueType::Volume(VolumeUnit::M3, exp(MASK_N, value, 2)),
275+
vif!(E001 01nn) => ValueType::ReactivePower(exp(MASK_NN, value, -3)),
276+
vif!(E001 100n) => ValueType::Mass(MassUnit::T, exp(MASK_N, value, 2)),
277+
vif!(E001 101n) => ValueType::RelativeHumidity(exp(MASK_N, value, -1)),
278+
vif!(E010 0000) => ValueType::Volume(VolumeUnit::Feet3, 0),
279+
vif!(E010 0001) => ValueType::Volume(VolumeUnit::Feet3, -1), // The table says "0,1 feet³" and I don't know what that means
280+
0b0010_0010..=0b0010_0110 => ValueType::RetiredCode(value),
281+
vif!(E010 100n) => ValueType::Power(PowerUnit::MW, exp(MASK_N, value, -1)),
282+
vif!(E010 1010) => ValueType::PhaseUU,
283+
vif!(E010 1011) => ValueType::PhaseUI,
284+
vif!(E010 11nn) => ValueType::Frequency(exp(MASK_NN, value, -3)),
285+
vif!(E011 000n) => ValueType::Power(PowerUnit::GJph, exp(MASK_N, value, -1)),
286+
vif!(E011 01nn) => ValueType::ApparentPower(exp(MASK_NN, value, -1)),
287+
0b0101_1000..=0b0110_0111 => ValueType::RetiredCode(value),
288+
vif!(E110 1000) => ValueType::ResultingPowerFactorK,
289+
vif!(E110 1001) => ValueType::ThermalOutputRatingFactorKq,
290+
vif!(E110 1010) => ValueType::ThermalCouplingRatingFactorOverallKc,
291+
vif!(E110 1011) => ValueType::ThermalCouplingRatingFactorRoomSideKcr,
292+
vif!(E110 1100) => ValueType::ThermalCouplingRatingFactorHeaterSideKch,
293+
vif!(E110 1101) => ValueType::LowTemperatureRatingFactorKt,
294+
vif!(E110 1110) => ValueType::DisplayOutputScalingFactorKD,
295+
vif!(E111 00nn) => ValueType::RetiredCode(value),
296+
vif!(E111 01nn) => ValueType::ColdWarmTemperatureLimit(exp(MASK_NN, value, -3)),
297+
vif!(E111 1nnn) => ValueType::CumulativeMaxOfActivePower(exp(MASK_NNN, value, -3)),
298+
_ => return None,
193299
})
194300
}
195301

@@ -262,6 +368,7 @@ pub enum ValueType {
262368
Any,
263369
PlainText(String),
264370
ManufacturerSpecific,
371+
RetiredCode(u8), // "These codes were used until 2004, now they are reserved for future use."
265372
// Table 10 - Primary VIF-codes
266373
Energy(EnergyUnit, Exponent),
267374
Volume(VolumeUnit, Exponent),
@@ -302,6 +409,7 @@ pub enum ValueType {
302409
Customer,
303410
AccessCodeUser,
304411
AccessCodeOperator,
412+
AccessCodeSystemOperator,
305413
AccessCodeDeveloper,
306414
Password,
307415
ErrorFlags,

0 commit comments

Comments
 (0)
Please sign in to comment.