From 69126c32f79fb2aa434f1d0a22b79fb6e38ef51b Mon Sep 17 00:00:00 2001 From: Michael Aebli Date: Wed, 18 Sep 2024 17:39:27 +0200 Subject: [PATCH 1/5] First attempt for support for manufacturer specific part parsing --- src/lib.rs | 13 +-- src/user_data/data_information.rs | 9 ++- src/user_data/data_record.rs | 130 +++++++++++++++++++----------- src/user_data/mod.rs | 6 +- 4 files changed, 97 insertions(+), 61 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5f3ad80..f8a55d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -257,17 +257,20 @@ fn parse_to_table(input: &str) -> std::string::String { for record in data_records.flatten() { table.add_row(row![ format!( - "({}{}", + "({:?}{:?}", record.data, record .data_record_header .processed_data_record_header .value_information ), - record - .data_record_header - .processed_data_record_header - .data_information + format!( + "{:?}", + record + .data_record_header + .processed_data_record_header + .data_information + ) ]); } } diff --git a/src/user_data/data_information.rs b/src/user_data/data_information.rs index 191a6fb..a8a3878 100644 --- a/src/user_data/data_information.rs +++ b/src/user_data/data_information.rs @@ -341,12 +341,13 @@ pub enum DataType<'a> { SingleEveryOrInvalid, SingleEveryOrInvalid, ), + ManufacturerSpecific(&'a [u8]), } #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[derive(PartialEq, Debug)] pub struct Data<'a> { - value: Option>, - size: usize, + pub value: Option>, + pub size: usize, } #[cfg(feature = "std")] @@ -384,6 +385,10 @@ impl std::fmt::Display for Data<'_> { DataType::Time(seconds, minutes, hours) => { write!(f, "{}:{}:{}", hours, minutes, seconds) } + DataType::Text(text_unit) => todo!(), + DataType::ManufacturerSpecific(data) => { + write!(f, "Manufacturer Specific: {:?}", data) + } }, None => write!(f, "No Data"), } diff --git a/src/user_data/data_record.rs b/src/user_data/data_record.rs index 255dca1..7615c3a 100644 --- a/src/user_data/data_record.rs +++ b/src/user_data/data_record.rs @@ -1,5 +1,5 @@ use super::{ - data_information::{Data, DataFieldCoding, DataInformation, DataInformationBlock}, + data_information::{Data, DataFieldCoding, DataInformation, DataInformationBlock, DataType}, value_information::{ValueInformation, ValueInformationBlock, ValueLabel}, variable_user_data::DataRecordError, }; @@ -7,13 +7,13 @@ use super::{ #[derive(Debug, PartialEq)] pub struct RawDataRecordHeader<'a> { pub data_information_block: DataInformationBlock<'a>, - pub value_information_block: ValueInformationBlock, + pub value_information_block: Option, } #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, PartialEq)] pub struct ProcessedDataRecordHeader { - pub data_information: DataInformation, - pub value_information: ValueInformation, + pub data_information: Option, + pub value_information: Option, } #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[derive(Debug, PartialEq)] @@ -28,6 +28,7 @@ impl DataRecord<'_> { self.data_record_header.get_size() + self.data.get_size() } } + #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[derive(Debug, PartialEq)] pub struct DataRecordHeader<'a> { @@ -38,13 +39,15 @@ pub struct DataRecordHeader<'a> { impl DataRecordHeader<'_> { #[must_use] pub fn get_size(&self) -> usize { - self.raw_data_record_header + let s = self + .raw_data_record_header .data_information_block - .get_size() - + self - .raw_data_record_header - .value_information_block - .get_size() + .get_size(); + if let Some(x) = &self.raw_data_record_header.value_information_block { + s + x.get_size() + } else { + s + } } } @@ -53,10 +56,16 @@ impl<'a> TryFrom<&'a [u8]> for RawDataRecordHeader<'a> { fn try_from(data: &[u8]) -> Result { let difb = DataInformationBlock::try_from(data)?; let offset = difb.get_size(); - let vifb = ValueInformationBlock::try_from( - data.get(offset..) - .ok_or(DataRecordError::InsufficientData)?, - )?; + + let mut vifb = None; + + if difb.data_information_field.data != 0x0F { + vifb = Some(ValueInformationBlock::try_from( + data.get(offset..) + .ok_or(DataRecordError::InsufficientData)?, + )?); + } + Ok(RawDataRecordHeader { data_information_block: difb, value_information_block: vifb, @@ -67,26 +76,31 @@ impl<'a> TryFrom<&'a [u8]> for RawDataRecordHeader<'a> { impl<'a> TryFrom<&RawDataRecordHeader<'a>> for ProcessedDataRecordHeader { type Error = DataRecordError; fn try_from(raw_data_record_header: &RawDataRecordHeader) -> Result { - let value_information = - ValueInformation::try_from(&raw_data_record_header.value_information_block)?; - let mut data_information = - DataInformation::try_from(&raw_data_record_header.data_information_block)?; - - // unfortunately, the data field coding is not always set in the data information block - // so we must do some additional checks to determine the correct data field coding - - if value_information.labels.contains(&ValueLabel::Date) { - data_information.data_field_coding = DataFieldCoding::DateTypeG; - } else if value_information.labels.contains(&ValueLabel::DateTime) { - data_information.data_field_coding = DataFieldCoding::DateTimeTypeF; - } else if value_information.labels.contains(&ValueLabel::Time) { - data_information.data_field_coding = DataFieldCoding::DateTimeTypeJ; - } else if value_information - .labels - .contains(&ValueLabel::DateTimeWithSeconds) - { - data_information.data_field_coding = DataFieldCoding::DateTimeTypeI; + let mut value_information = None; + let mut data_information = None; + + if let Some(x) = &raw_data_record_header.value_information_block { + let v = ValueInformation::try_from(x)?; + + let mut d = DataInformation::try_from(&raw_data_record_header.data_information_block)?; + + // unfortunately, the data field coding is not always set in the data information block + // so we must do some additional checks to determine the correct data field coding + + if v.labels.contains(&ValueLabel::Date) { + d.data_field_coding = DataFieldCoding::DateTypeG; + } else if v.labels.contains(&ValueLabel::DateTime) { + d.data_field_coding = DataFieldCoding::DateTimeTypeF; + } else if v.labels.contains(&ValueLabel::Time) { + d.data_field_coding = DataFieldCoding::DateTimeTypeJ; + } else if v.labels.contains(&ValueLabel::DateTimeWithSeconds) { + d.data_field_coding = DataFieldCoding::DateTimeTypeI; + } + + value_information = Some(v); + data_information = Some(d); } + Ok(Self { data_information, value_information, @@ -111,25 +125,36 @@ impl<'a> TryFrom<&'a [u8]> for DataRecord<'a> { type Error = DataRecordError; fn try_from(data: &[u8]) -> Result { let data_record_header = DataRecordHeader::try_from(data)?; - let offset = data_record_header + let mut offset = data_record_header .raw_data_record_header .data_information_block - .get_size() - + data_record_header - .raw_data_record_header - .value_information_block - .get_size(); - let data = data_record_header - .processed_data_record_header - .data_information - .data_field_coding - .parse( - data.get(offset..) - .ok_or(DataRecordError::InsufficientData)?, - )?; + .get_size(); + + let mut data_out = Data { + value: Some(DataType::ManufacturerSpecific(data)), + size: data.len(), + }; + if let Some(x) = &data_record_header + .raw_data_record_header + .value_information_block + { + offset += x.get_size(); + + data_out = data_record_header + .processed_data_record_header + .data_information + .clone() + .unwrap() + .data_field_coding + .parse( + data.get(offset..) + .ok_or(DataRecordError::InsufficientData)?, + )?; + } + Ok(DataRecord { - data_record_header, - data, + data_record_header: data_record_header, + data: data_out, }) } } @@ -143,4 +168,11 @@ mod tests { let data = &[0x03, 0x13, 0x15, 0x31, 0x00]; let _result = DataRecordHeader::try_from(data.as_slice()); } + #[test] + #[cfg(feature = "std")] + fn test_manufacturer_specific_block() { + let data = [0x0F, 0x01, 0x02, 0x03, 0x04]; + let result = DataRecord::try_from(data.as_slice()); + println!("{:?}", result); + } } diff --git a/src/user_data/mod.rs b/src/user_data/mod.rs index ad13e20..120dc63 100644 --- a/src/user_data/mod.rs +++ b/src/user_data/mod.rs @@ -35,10 +35,6 @@ impl<'a> Iterator for DataRecords<'a> { while self.offset < self.data.len() { match self.data.get(self.offset)? { - 0x0F => { - /* TODO: parse manufacturer specific */ - self.offset = self.data.len(); - } 0x1F => { /* TODO: parse manufacturer specific */ _more_records_follow = true; @@ -126,7 +122,7 @@ pub enum Direction { MasterToSlave, } -// implement from trait for diirection +// implement from trait for direction impl From for Direction { fn from(single_byte: ControlInformation) -> Self { match single_byte { From 42303494d577d6e626382c2f22abb3d88da6491e Mon Sep 17 00:00:00 2001 From: Michael Aebli Date: Thu, 19 Sep 2024 15:51:58 +0200 Subject: [PATCH 2/5] fixing formatting for table output --- src/lib.rs | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f8a55d7..cadc958 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -255,22 +255,27 @@ fn parse_to_table(input: &str) -> std::string::String { if let Some(data_records) = parsed_data.data_records { for record in data_records.flatten() { + let value_information = match record + .data_record_header + .processed_data_record_header + .value_information + { + Some(x) => format!("{}", x), + None => "None".to_string(), + }; + + let data_information = match record + .data_record_header + .processed_data_record_header + .data_information + { + Some(x) => format!("{}", x), + None => "None".to_string(), + }; + table.add_row(row![ - format!( - "({:?}{:?}", - record.data, - record - .data_record_header - .processed_data_record_header - .value_information - ), - format!( - "{:?}", - record - .data_record_header - .processed_data_record_header - .data_information - ) + format!("({}{:}", record.data, value_information), + format!("{}", data_information) ]); } } From bc6d447b99113b16d8605526b495e8c1f3c39322 Mon Sep 17 00:00:00 2001 From: Michael Aebli Date: Thu, 19 Sep 2024 16:28:04 +0200 Subject: [PATCH 3/5] adding a test getting rid of a clippy issue --- cli/src/main.rs | 5 +++++ src/user_data/data_record.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index ae6b78a..4a83e54 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -56,4 +56,9 @@ mod tests { let data_string = "0x68, 0x3C, 0x3C, 0x68, 0x08, 0x08, 0x72, 0x78, 0x03, 0x49, 0x11, 0x77, 0x04, 0x0E, 0x16, 0x0A, 0x00, 0x00, 0x00, 0x0C, 0x78, 0x78, 0x03, 0x49, 0x11, 0x04, 0x13, 0x31, 0xD4, 0x00, 0x00, 0x42, 0x6C, 0x00, 0x00, 0x44, 0x13, 0x00, 0x00, 0x00, 0x00, 0x04, 0x6D, 0x0B, 0x0B, 0xCD, 0x13, 0x02, 0x27, 0x00, 0x00, 0x09, 0xFD, 0x0E, 0x02, 0x09, 0xFD, 0x0F, 0x06, 0x0F, 0x00, 0x01, 0x75, 0x13, 0xD3, 0x16"; println!("{}", serialize_mbus_data(data_string, "table")); } + + fn test_parse_manufacturer_string(){ + let data_string = "0x0F 0x00 0x01 0x75 0x13 0xD3 0x16"; + println!("{}", + } } diff --git a/src/user_data/data_record.rs b/src/user_data/data_record.rs index 7615c3a..7710da8 100644 --- a/src/user_data/data_record.rs +++ b/src/user_data/data_record.rs @@ -153,7 +153,7 @@ impl<'a> TryFrom<&'a [u8]> for DataRecord<'a> { } Ok(DataRecord { - data_record_header: data_record_header, + data_record_header, data: data_out, }) } From 1630f669599047ac5ba71e8bfd4bbfd1da8d8697 Mon Sep 17 00:00:00 2001 From: Michael Aebli Date: Fri, 20 Sep 2024 16:00:14 +0200 Subject: [PATCH 4/5] getting rid of broken function --- cli/src/main.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 4a83e54..ae6b78a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -56,9 +56,4 @@ mod tests { let data_string = "0x68, 0x3C, 0x3C, 0x68, 0x08, 0x08, 0x72, 0x78, 0x03, 0x49, 0x11, 0x77, 0x04, 0x0E, 0x16, 0x0A, 0x00, 0x00, 0x00, 0x0C, 0x78, 0x78, 0x03, 0x49, 0x11, 0x04, 0x13, 0x31, 0xD4, 0x00, 0x00, 0x42, 0x6C, 0x00, 0x00, 0x44, 0x13, 0x00, 0x00, 0x00, 0x00, 0x04, 0x6D, 0x0B, 0x0B, 0xCD, 0x13, 0x02, 0x27, 0x00, 0x00, 0x09, 0xFD, 0x0E, 0x02, 0x09, 0xFD, 0x0F, 0x06, 0x0F, 0x00, 0x01, 0x75, 0x13, 0xD3, 0x16"; println!("{}", serialize_mbus_data(data_string, "table")); } - - fn test_parse_manufacturer_string(){ - let data_string = "0x0F 0x00 0x01 0x75 0x13 0xD3 0x16"; - println!("{}", - } } From 33494c7ad751b690b953fd494489bb3e994247df Mon Sep 17 00:00:00 2001 From: Michael Aebli Date: Fri, 20 Sep 2024 16:20:26 +0200 Subject: [PATCH 5/5] WIP getting rid of unwrap --- src/user_data/data_record.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/user_data/data_record.rs b/src/user_data/data_record.rs index 7710da8..379990d 100644 --- a/src/user_data/data_record.rs +++ b/src/user_data/data_record.rs @@ -123,13 +123,12 @@ impl<'a> TryFrom<&'a [u8]> for DataRecordHeader<'a> { impl<'a> TryFrom<&'a [u8]> for DataRecord<'a> { type Error = DataRecordError; - fn try_from(data: &[u8]) -> Result { + fn try_from(data: &'a [u8]) -> Result { let data_record_header = DataRecordHeader::try_from(data)?; let mut offset = data_record_header .raw_data_record_header .data_information_block .get_size(); - let mut data_out = Data { value: Some(DataType::ManufacturerSpecific(data)), size: data.len(), @@ -139,19 +138,16 @@ impl<'a> TryFrom<&'a [u8]> for DataRecord<'a> { .value_information_block { offset += x.get_size(); - - data_out = data_record_header + if let Some(data_info) = &data_record_header .processed_data_record_header .data_information - .clone() - .unwrap() - .data_field_coding - .parse( + { + data_out = data_info.data_field_coding.parse( data.get(offset..) .ok_or(DataRecordError::InsufficientData)?, )?; + } } - Ok(DataRecord { data_record_header, data: data_out,