Skip to content

Commit 55e264b

Browse files
authored
Merge pull request #75 from maebli/feature/wmbus-continued
Feature/wmbus continued
2 parents 6ea9c8f + bb1e1d6 commit 55e264b

File tree

11 files changed

+250
-12
lines changed

11 files changed

+250
-12
lines changed

crates/m-bus-application-layer/src/extended_link_layer.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::ApplicationLayerError;
2+
13
#[derive(Debug, PartialEq)]
24
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -23,3 +25,81 @@ pub struct EncryptionFields {
2325
pub session_number: [u8; 4],
2426
pub payload_crc: u16,
2527
}
28+
29+
#[derive(Debug, Clone, Copy, PartialEq)]
30+
pub enum EllFormat {
31+
/// Extended Link Layer I (2 bytes: CC, ACC)
32+
FormatI,
33+
/// Extended Link Layer II (8 bytes: CC, ACC, SN[4], CRC[2])
34+
FormatII,
35+
/// Extended Link Layer III (16 bytes: CC, ACC, MFR[2], ADDR[6], SN[4], CRC[2])
36+
FormatIII,
37+
}
38+
39+
impl ExtendedLinkLayer {
40+
/// Parse Extended Link Layer from data, returns (ELL, bytes_consumed)
41+
pub fn parse(data: &[u8], format: EllFormat) -> Result<(Self, usize), ApplicationLayerError> {
42+
match format {
43+
EllFormat::FormatI => Self::parse_format_i(data),
44+
EllFormat::FormatII => Self::parse_format_ii(data),
45+
EllFormat::FormatIII => Self::parse_format_iii(data),
46+
}
47+
}
48+
49+
fn parse_format_i(data: &[u8]) -> Result<(Self, usize), ApplicationLayerError> {
50+
if data.len() < 2 {
51+
return Err(ApplicationLayerError::InsufficientData);
52+
}
53+
54+
Ok((
55+
ExtendedLinkLayer {
56+
communication_control: data[0],
57+
access_number: data[1],
58+
receiver_address: None,
59+
encryption: None,
60+
},
61+
2,
62+
))
63+
}
64+
65+
fn parse_format_ii(data: &[u8]) -> Result<(Self, usize), ApplicationLayerError> {
66+
if data.len() < 8 {
67+
return Err(ApplicationLayerError::InsufficientData);
68+
}
69+
70+
Ok((
71+
ExtendedLinkLayer {
72+
communication_control: data[0],
73+
access_number: data[1],
74+
receiver_address: None,
75+
encryption: Some(EncryptionFields {
76+
session_number: [data[2], data[3], data[4], data[5]],
77+
payload_crc: u16::from_le_bytes([data[6], data[7]]),
78+
}),
79+
},
80+
8,
81+
))
82+
}
83+
84+
fn parse_format_iii(data: &[u8]) -> Result<(Self, usize), ApplicationLayerError> {
85+
if data.len() < 16 {
86+
return Err(ApplicationLayerError::InsufficientData);
87+
}
88+
89+
Ok((
90+
ExtendedLinkLayer {
91+
communication_control: data[0],
92+
access_number: data[1],
93+
receiver_address: Some(ReceiverAddress {
94+
manufacturer: u16::from_le_bytes([data[2], data[3]]),
95+
address: [data[4], data[5], data[6], data[7], data[8], data[9]],
96+
}),
97+
encryption: Some(EncryptionFields {
98+
session_number: [data[10], data[11], data[12], data[13]],
99+
payload_crc: u16::from_le_bytes([data[14], data[15]]),
100+
}),
101+
},
102+
16,
103+
))
104+
}
105+
}

crates/m-bus-application-layer/src/lib.rs

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,12 +1037,56 @@ impl<'a> TryFrom<&'a [u8]> for UserDataBlock<'a> {
10371037
Err(ApplicationLayerError::MissingControlInformation)
10381038
}
10391039
}
1040-
ControlInformation::ExtendedLinkLayerII => Err(ApplicationLayerError::Unimplemented {
1041-
feature: "ExtendedLinkLayerII control information",
1042-
}),
1043-
ControlInformation::ExtendedLinkLayerIII => Err(ApplicationLayerError::Unimplemented {
1044-
feature: "ExtendedLinkLayerIII control information",
1045-
}),
1040+
ControlInformation::ExtendedLinkLayerII => {
1041+
// CI byte + ELL II (8 bytes) = 9 bytes total before application data
1042+
let (ell, ell_size) = ExtendedLinkLayer::parse(
1043+
data.get(1..)
1044+
.ok_or(ApplicationLayerError::InsufficientData)?,
1045+
extended_link_layer::EllFormat::FormatII,
1046+
)?;
1047+
let app_data_offset = 1 + ell_size;
1048+
1049+
// Create a ShortTplHeader from ELL fields
1050+
// For encrypted ELL frames, the ELL fields become the "header"
1051+
let short_tpl_header = ShortTplHeader {
1052+
access_number: ell.access_number,
1053+
status: StatusField::from_bits_truncate(ell.communication_control),
1054+
configuration_field: ConfigurationField::from_bytes(0x00, 0x00),
1055+
};
1056+
1057+
Ok(UserDataBlock::VariableDataStructureWithShortTplHeader {
1058+
extended_link_layer: Some(ell),
1059+
short_tpl_header,
1060+
variable_data_block: data
1061+
.get(app_data_offset..)
1062+
.ok_or(ApplicationLayerError::InsufficientData)?,
1063+
})
1064+
}
1065+
ControlInformation::ExtendedLinkLayerIII => {
1066+
// CI byte + ELL III (16 bytes) = 17 bytes total before application data
1067+
let (ell, ell_size) = ExtendedLinkLayer::parse(
1068+
data.get(1..)
1069+
.ok_or(ApplicationLayerError::InsufficientData)?,
1070+
extended_link_layer::EllFormat::FormatIII,
1071+
)?;
1072+
let app_data_offset = 1 + ell_size;
1073+
1074+
// Create a ShortTplHeader from ELL fields
1075+
// For encrypted ELL frames, the ELL fields become the "header"
1076+
let short_tpl_header = ShortTplHeader {
1077+
access_number: ell.access_number,
1078+
status: StatusField::from_bits_truncate(ell.communication_control),
1079+
configuration_field: ConfigurationField::from_bytes(0x00, 0x00),
1080+
};
1081+
1082+
Ok(UserDataBlock::VariableDataStructureWithShortTplHeader {
1083+
extended_link_layer: Some(ell),
1084+
short_tpl_header,
1085+
variable_data_block: data
1086+
.get(app_data_offset..)
1087+
.ok_or(ApplicationLayerError::InsufficientData)?,
1088+
})
1089+
}
10461090
}
10471091
}
10481092
}

crates/m-bus-application-layer/src/variable_user_data.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ mod tests {
8181

8282
assert_eq!(records.len(), 5);
8383
{
84-
let record = records.get(0).unwrap();
84+
let record = records.first().unwrap();
8585
let code = get_data_field_coding(record);
8686
assert_eq!(code, DataFieldCoding::VariableLength);
8787
let value = record.data.value.clone().unwrap();
@@ -146,7 +146,7 @@ mod tests {
146146

147147
assert_eq!(records.len(), 10);
148148
{
149-
let record = records.get(0).unwrap();
149+
let record = records.first().unwrap();
150150
let code = get_data_field_coding(record);
151151
assert_eq!(code, DataFieldCoding::VariableLength);
152152
let value = record.data.value.clone().unwrap();
@@ -225,7 +225,7 @@ mod tests {
225225
/* DIF = 0x03, VIF = 0x13, Value = 0x153100 */
226226
let data = &[0x03, 0x13, 0x15, 0x31, 0x00];
227227

228-
let _result = DataRecords::try_from(data.as_slice());
228+
let _result = DataRecords::from(data.as_slice());
229229
}
230230

231231
#[test]
@@ -253,7 +253,7 @@ mod tests {
253253
use crate::DataRecords;
254254
/* Data block 3: unit 1, storage No 0, tariff 2, instantaneous energy, 218,37 kWh (6 digit BCD) */
255255
let data = &[0x02, 0xFC, 0x74, 0x03, 0x48, 0x52, 0x25, 0x44, 0x0D];
256-
let _data = DataRecords::try_from(data.as_slice());
256+
let _data = DataRecords::from(data.as_slice());
257257
}
258258

259259
const fn _test_parse_variable_data2() {

crates/m-bus-core/src/decryption.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,8 @@ mod tests {
319319
assert_eq!(iv[7], 0x07);
320320

321321
// Check access number repeated (bytes 8-15)
322-
for i in 8..16 {
323-
assert_eq!(iv[i], 0x50);
322+
for &byte in iv.iter().skip(8) {
323+
assert_eq!(byte, 0x50);
324324
}
325325
}
326326

707 Bytes
Binary file not shown.

examples/example_parsing_long_frame.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
#![allow(
2+
clippy::unwrap_used,
3+
clippy::expect_used,
4+
clippy::unnecessary_fallible_conversions
5+
)]
6+
17
use m_bus_parser::{Address, Function, WiredFrame};
28
/// 68 4d 4d 68 08 01 72 01 00 00 00 96 15 01 00 18 00 00 00 0c 78 56 00 00 00 01
39
/// fd 1b 00 02 fc 03 48 52 25 74 44 0d 22 fc 03 48 52 25 74 f1 0c 12 fc 03 48 52

examples/example_parsing_long_frame2.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
#![allow(
2+
clippy::unwrap_used,
3+
clippy::expect_used,
4+
clippy::unnecessary_fallible_conversions
5+
)]
6+
17
use m_bus_parser::{Address, Function, WiredFrame};
28

39
fn main() {

examples/example_parsing_user_data.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(clippy::unwrap_used, clippy::unnecessary_fallible_conversions)]
2+
13
use m_bus_parser::user_data::DataRecords;
24

35
fn main() {

tests/test.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
#![allow(
2+
clippy::unwrap_used,
3+
clippy::expect_used,
4+
clippy::panic,
5+
clippy::indexing_slicing
6+
)]
7+
18
use serde::Deserialize;
29
use serde_xml_rs::from_str;
310
use std::fs;

tests/test_wireless.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
2+
13
use m_bus_core::DeviceType;
24
use m_bus_parser::mbus_data::MbusData;
35
use m_bus_parser::user_data::UserDataBlock;
@@ -39,12 +41,14 @@ fn device_type_from_str(device_type: &str) -> DeviceType {
3941
match device_type {
4042
"Other" => DeviceType::Other,
4143
"Water" => DeviceType::WaterMeter,
44+
"ColdWater" => DeviceType::ColdWaterMeter,
4245
"WarmWater" => DeviceType::WarmWaterMeter,
4346
"Heat" => DeviceType::HeatMeterReturn,
4447
"HeatMeterFlow" => DeviceType::HeatMeterFlow,
4548
"HeatCostAllocator" => DeviceType::HeatCostAllocator,
4649
"HeatCoolingLoad" => DeviceType::CombinedHeatCoolingMeter,
4750
"Electricity" => DeviceType::ElectricityMeter,
51+
"Gas" => DeviceType::GasMeter,
4852
"RoomSensor" => DeviceType::RoomSensor,
4953
s if s.starts_with("Reserved(") => {
5054
// Parse "Reserved(128)" format

0 commit comments

Comments
 (0)