diff --git a/src/frames/mod.rs b/src/frames/mod.rs index dbee406..66c732f 100644 --- a/src/frames/mod.rs +++ b/src/frames/mod.rs @@ -158,9 +158,7 @@ impl<'a> TryFrom<&'a [u8]> for Frame<'a> { if *data.last().ok_or(FrameError::LengthShort)? != 0x16 { return Err(FrameError::InvalidStopByte); } - let control_field = *data.get(4).ok_or(FrameError::LengthShort)?; - let address_field = *data.get(5).ok_or(FrameError::LengthShort)?; match control_field { 0x53 => Ok(Frame::ControlFrame { diff --git a/src/user_data/data_information.rs b/src/user_data/data_information.rs index e0137d8..c4305b5 100644 --- a/src/user_data/data_information.rs +++ b/src/user_data/data_information.rs @@ -1,5 +1,6 @@ use super::data_information::{self}; use super::variable_user_data::DataRecordError; +use super::FixedDataHeader; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[derive(Debug, PartialEq)] @@ -457,6 +458,7 @@ fn bcd_to_value_internal( data: &[u8], num_digits: usize, sign: i32, + lsb_order: bool, ) -> Result { if data.len() < (num_digits + 1) / 2 { return Err(DataRecordError::InsufficientData); @@ -466,7 +468,12 @@ fn bcd_to_value_internal( let mut current_weight = 1.0; for i in 0..num_digits { - let byte = data.get(i / 2).ok_or(DataRecordError::InsufficientData)?; + let index = if lsb_order { + (num_digits - i - 1) / 2 + } else { + i / 2 + }; + let byte = data.get(index).ok_or(DataRecordError::InsufficientData)?; let digit = if i % 2 == 0 { byte & 0x0F @@ -487,14 +494,20 @@ fn bcd_to_value_internal( } impl DataFieldCoding { - pub fn parse<'a>(&self, input: &'a [u8]) -> Result, DataRecordError> { + pub fn parse<'a>( + &self, + input: &'a [u8], + fixed_data_header: Option<&'a FixedDataHeader>, + ) -> Result, DataRecordError> { + let lsb_order = fixed_data_header.map(|x| x.lsb_order).unwrap_or(false); + macro_rules! bcd_to_value { ($data:expr, $num_digits:expr) => {{ - bcd_to_value_internal($data, $num_digits, 1) + bcd_to_value_internal($data, $num_digits, 1, lsb_order) }}; ($data:expr, $num_digits:expr, $sign:expr) => {{ - bcd_to_value_internal($data, $num_digits, $sign) + bcd_to_value_internal($data, $num_digits, $sign, lsb_order) }}; } @@ -875,7 +888,7 @@ mod tests { #[test] fn test_bcd_to_value_unsigned() { let data = [0x54, 0x76, 0x98]; - let result = bcd_to_value_internal(&data, 6, 1); + let result = bcd_to_value_internal(&data, 6, 1, false); assert_eq!( result.unwrap(), Data { diff --git a/src/user_data/data_record.rs b/src/user_data/data_record.rs index bbc4875..a5329f1 100644 --- a/src/user_data/data_record.rs +++ b/src/user_data/data_record.rs @@ -2,6 +2,7 @@ use super::{ data_information::{Data, DataFieldCoding, DataInformation, DataInformationBlock, DataType}, value_information::{ValueInformation, ValueInformationBlock, ValueLabel}, variable_user_data::DataRecordError, + FixedDataHeader, }; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[derive(Debug, PartialEq)] @@ -29,6 +30,44 @@ impl DataRecord<'_> { } } +impl<'a> DataRecord<'a> { + fn parse( + data: &'a [u8], + fixed_data_header: Option<&'a FixedDataHeader>, + ) -> 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(), + }; + if let Some(x) = &data_record_header + .raw_data_record_header + .value_information_block + { + offset += x.get_size(); + if let Some(data_info) = &data_record_header + .processed_data_record_header + .data_information + { + data_out = data_info.data_field_coding.parse( + data.get(offset..) + .ok_or(DataRecordError::InsufficientData)?, + fixed_data_header, + )?; + } + } + + Ok(DataRecord { + data_record_header, + data: data_out, + }) + } +} + #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[derive(Debug, PartialEq)] pub struct DataRecordHeader<'a> { @@ -121,37 +160,19 @@ impl<'a> TryFrom<&'a [u8]> for DataRecordHeader<'a> { } } +impl<'a> TryFrom<(&'a [u8], &'a FixedDataHeader)> for DataRecord<'a> { + type Error = DataRecordError; + fn try_from( + (data, fixed_data_header): (&'a [u8], &'a FixedDataHeader), + ) -> Result { + Self::parse(data, Some(fixed_data_header)) + } +} + impl<'a> TryFrom<&'a [u8]> for DataRecord<'a> { type Error = DataRecordError; 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(), - }; - if let Some(x) = &data_record_header - .raw_data_record_header - .value_information_block - { - offset += x.get_size(); - if let Some(data_info) = &data_record_header - .processed_data_record_header - .data_information - { - data_out = data_info.data_field_coding.parse( - data.get(offset..) - .ok_or(DataRecordError::InsufficientData)?, - )?; - } - } - Ok(DataRecord { - data_record_header, - data: data_out, - }) + Self::parse(data, None) } } diff --git a/src/user_data/mod.rs b/src/user_data/mod.rs index 92a0f67..cd038bc 100644 --- a/src/user_data/mod.rs +++ b/src/user_data/mod.rs @@ -17,6 +17,7 @@ pub mod variable_user_data; pub struct DataRecords<'a> { offset: usize, data: &'a [u8], + fixed_data_header: Option<&'a FixedDataHeader>, } #[cfg(feature = "serde")] @@ -44,7 +45,11 @@ impl<'a> Iterator for DataRecords<'a> { self.offset += 1; } _ => { - let record = DataRecord::try_from(self.data.get(self.offset..)?); + let record = if let Some(fixed_data_header) = self.fixed_data_header { + DataRecord::try_from((self.data.get(self.offset..)?, fixed_data_header)) + } else { + DataRecord::try_from(self.data.get(self.offset..)?) + }; if let Ok(record) = record { self.offset += record.get_size(); return Some(Ok(record)); @@ -60,8 +65,12 @@ impl<'a> Iterator for DataRecords<'a> { impl<'a> DataRecords<'a> { #[must_use] - pub const fn new(data: &'a [u8]) -> Self { - DataRecords { offset: 0, data } + pub const fn new(data: &'a [u8], fixed_data_header: Option<&'a FixedDataHeader>) -> Self { + DataRecords { + offset: 0, + data, + fixed_data_header, + } } } @@ -146,7 +155,9 @@ impl From for Direction { ControlInformation::HashProcedure(_) => Self::MasterToSlave, ControlInformation::SendErrorStatus => Self::SlaveToMaster, ControlInformation::SendAlarmStatus => Self::SlaveToMaster, - ControlInformation::ResponseWithVariableDataStructure => Self::SlaveToMaster, + ControlInformation::ResponseWithVariableDataStructure { lsb_order: _ } => { + Self::SlaveToMaster + } ControlInformation::ResponseWithFixedDataStructure => Self::SlaveToMaster, } } @@ -174,7 +185,7 @@ pub enum ControlInformation { HashProcedure(u8), SendErrorStatus, SendAlarmStatus, - ResponseWithVariableDataStructure, + ResponseWithVariableDataStructure { lsb_order: bool }, ResponseWithFixedDataStructure, } @@ -201,7 +212,9 @@ impl ControlInformation { 0x90..=0x97 => Ok(Self::HashProcedure(byte - 0x90)), 0x70 => Ok(Self::SendErrorStatus), 0x71 => Ok(Self::SendAlarmStatus), - 0x72 | 0x76 => Ok(Self::ResponseWithVariableDataStructure), + 0x72 | 0x76 => Ok(Self::ResponseWithVariableDataStructure { + lsb_order: byte & 0x04 != 0, + }), 0x73 | 0x77 => Ok(Self::ResponseWithFixedDataStructure), _ => Err(ApplicationLayerError::InvalidControlInformation { byte }), } @@ -513,6 +526,7 @@ pub struct FixedDataHeader { pub access_number: u8, pub status: StatusField, pub signature: u16, + pub lsb_order: bool, } #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, PartialEq)] @@ -600,16 +614,23 @@ impl<'a> TryFrom<&'a [u8]> for UserDataBlock<'a> { ControlInformation::HashProcedure(_) => todo!(), ControlInformation::SendErrorStatus => todo!(), ControlInformation::SendAlarmStatus => todo!(), - ControlInformation::ResponseWithVariableDataStructure => { + ControlInformation::ResponseWithVariableDataStructure { lsb_order } => { let mut iter = data.iter().skip(1); + let mut identification_number_bytes = [ + *iter.next().ok_or(ApplicationLayerError::InsufficientData)?, + *iter.next().ok_or(ApplicationLayerError::InsufficientData)?, + *iter.next().ok_or(ApplicationLayerError::InsufficientData)?, + *iter.next().ok_or(ApplicationLayerError::InsufficientData)?, + ]; + if lsb_order { + identification_number_bytes.reverse(); + } + Ok(UserDataBlock::VariableDataStructure { fixed_data_header: FixedDataHeader { - identification_number: IdentificationNumber::from_bcd_hex_digits([ - *iter.next().ok_or(ApplicationLayerError::InsufficientData)?, - *iter.next().ok_or(ApplicationLayerError::InsufficientData)?, - *iter.next().ok_or(ApplicationLayerError::InsufficientData)?, - *iter.next().ok_or(ApplicationLayerError::InsufficientData)?, - ])?, + identification_number: IdentificationNumber::from_bcd_hex_digits( + identification_number_bytes, + )?, manufacturer: ManufacturerCode::from_id(u16::from_le_bytes([ *iter.next().ok_or(ApplicationLayerError::InsufficientData)?, *iter.next().ok_or(ApplicationLayerError::InsufficientData)?, @@ -629,6 +650,7 @@ impl<'a> TryFrom<&'a [u8]> for UserDataBlock<'a> { *iter.next().ok_or(ApplicationLayerError::InsufficientData)?, *iter.next().ok_or(ApplicationLayerError::InsufficientData)?, ]), + lsb_order, }, variable_data_block: data .get(13..data.len()) @@ -817,4 +839,75 @@ mod tests { ); Ok(()) } + + #[test] + fn test_lsb_frame() { + use crate::frames::Frame; + use crate::user_data::data_information::DataType; + + let lsb_frame: &[u8] = &[ + 0x68, 0x64, 0x64, 0x68, 0x8, 0x7f, 0x76, 0x9, 0x67, 0x1, 0x6, 0x0, 0x0, 0x51, 0x4, + 0x50, 0x0, 0x0, 0x0, 0x2, 0x6c, 0x38, 0x1c, 0xc, 0xf, 0x0, 0x80, 0x87, 0x32, 0x8c, + 0x20, 0xf, 0x0, 0x0, 0x0, 0x0, 0xc, 0x14, 0x13, 0x32, 0x82, 0x58, 0xbc, 0x10, 0x15, + 0x0, 0x25, 0x81, 0x25, 0x8c, 0x20, 0x13, 0x0, 0x0, 0x0, 0x0, 0x8c, 0x30, 0x13, 0x0, + 0x0, 0x1, 0x61, 0x8c, 0x40, 0x13, 0x0, 0x0, 0x16, 0x88, 0xa, 0x3c, 0x1, 0x10, 0xa, + 0x2d, 0x0, 0x80, 0xa, 0x5a, 0x7, 0x18, 0xa, 0x5e, 0x6, 0x53, 0xc, 0x22, 0x0, 0x16, 0x7, + 0x26, 0x3c, 0x22, 0x0, 0x0, 0x33, 0x81, 0x4, 0x7e, 0x0, 0x0, 0x67, 0xc, 0xc, 0x16, + ]; + let non_lsb_frame: &[u8] = &[ + 0x68, 0xc7, 0xc7, 0x68, 0x8, 0x38, 0x72, 0x56, 0x73, 0x23, 0x72, 0x2d, 0x2c, 0x34, 0x4, + 0x87, 0x0, 0x0, 0x0, 0x4, 0xf, 0x7f, 0x1c, 0x1, 0x0, 0x4, 0xff, 0x7, 0x8a, 0xad, 0x8, + 0x0, 0x4, 0xff, 0x8, 0x6, 0xfe, 0x5, 0x0, 0x4, 0x14, 0x4e, 0x55, 0xb, 0x0, 0x84, 0x40, + 0x14, 0x0, 0x0, 0x0, 0x0, 0x84, 0x80, 0x40, 0x14, 0x0, 0x0, 0x0, 0x0, 0x4, 0x22, 0x76, + 0x7f, 0x0, 0x0, 0x34, 0x22, 0x8b, 0x2c, 0x0, 0x0, 0x2, 0x59, 0x61, 0x1b, 0x2, 0x5d, + 0x5f, 0x10, 0x2, 0x61, 0x2, 0xb, 0x4, 0x2d, 0x55, 0x0, 0x0, 0x0, 0x14, 0x2d, 0x83, 0x0, + 0x0, 0x0, 0x4, 0x3b, 0x6, 0x1, 0x0, 0x0, 0x14, 0x3b, 0xaa, 0x1, 0x0, 0x0, 0x4, 0xff, + 0x22, 0x0, 0x0, 0x0, 0x0, 0x4, 0x6d, 0x6, 0x2c, 0x1f, 0x3a, 0x44, 0xf, 0xcf, 0x11, 0x1, + 0x0, 0x44, 0xff, 0x7, 0xb, 0x69, 0x8, 0x0, 0x44, 0xff, 0x8, 0x54, 0xd3, 0x5, 0x0, 0x44, + 0x14, 0x11, 0xf3, 0xa, 0x0, 0xc4, 0x40, 0x14, 0x0, 0x0, 0x0, 0x0, 0xc4, 0x80, 0x40, + 0x14, 0x0, 0x0, 0x0, 0x0, 0x54, 0x2d, 0x3a, 0x0, 0x0, 0x0, 0x54, 0x3b, 0x28, 0x1, 0x0, + 0x0, 0x42, 0x6c, 0x1, 0x3a, 0x2, 0xff, 0x1a, 0x1, 0x1a, 0xc, 0x78, 0x56, 0x73, 0x23, + 0x72, 0x4, 0xff, 0x16, 0xe6, 0x84, 0x1e, 0x0, 0x4, 0xff, 0x17, 0xc1, 0xd5, 0xb4, 0x0, + 0x12, 0x16, + ]; + + let frames = [ + (lsb_frame, 9670106, Some(DataType::Number(808732.0))), + (non_lsb_frame, 72237356, Some(DataType::Number(568714.0))), + ]; + + for (frame, expected_iden_nr, data_record_value) in frames { + let frame = Frame::try_from(frame).unwrap(); + + if let Frame::LongFrame { + function: _, + address: _, + data, + } = frame + { + let user_data_block = UserDataBlock::try_from(data).unwrap(); + if let UserDataBlock::VariableDataStructure { + fixed_data_header, + variable_data_block, + } = user_data_block + { + assert_eq!( + fixed_data_header.identification_number.number, + expected_iden_nr + ); + + let mut data_records = + DataRecords::try_from((variable_data_block, &fixed_data_header)) + .unwrap() + .flatten(); + data_records.next().unwrap(); + assert_eq!(data_records.next().unwrap().data.value, data_record_value); + } else { + panic!("UserDataBlock is not a variable data structure"); + } + } else { + panic!("Frame is not a long frame"); + } + } + } } diff --git a/src/user_data/variable_user_data.rs b/src/user_data/variable_user_data.rs index 6e86fe4..57c20cf 100644 --- a/src/user_data/variable_user_data.rs +++ b/src/user_data/variable_user_data.rs @@ -1,5 +1,5 @@ use super::data_information::{self}; -use super::DataRecords; +use super::{DataRecords, FixedDataHeader}; #[derive(Debug, PartialEq)] pub enum DataRecordError { @@ -20,7 +20,13 @@ impl From for VariableUserDataError { impl<'a> From<&'a [u8]> for DataRecords<'a> { fn from(data: &'a [u8]) -> Self { - DataRecords::new(data) + DataRecords::new(data, None) + } +} + +impl<'a> From<(&'a [u8], &'a FixedDataHeader)> for DataRecords<'a> { + fn from((data, fixed_data_header): (&'a [u8], &'a FixedDataHeader)) -> Self { + DataRecords::new(data, Some(fixed_data_header)) } }