Skip to main content

m_bus_application_layer/
data_record.rs

1use super::{
2    data_information::{Data, DataFieldCoding, DataInformation, DataInformationBlock, DataType},
3    value_information::{ValueInformation, ValueInformationBlock, ValueLabel},
4    variable_user_data::DataRecordError,
5    LongTplHeader,
6};
7#[cfg_attr(feature = "serde", derive(serde::Serialize))]
8#[derive(Debug, PartialEq, Clone)]
9#[cfg_attr(feature = "defmt", derive(defmt::Format))]
10pub struct RawDataRecordHeader<'a> {
11    pub data_information_block: DataInformationBlock<'a>,
12    pub value_information_block: Option<ValueInformationBlock>,
13}
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[derive(Debug, PartialEq, Clone)]
16#[cfg_attr(feature = "defmt", derive(defmt::Format))]
17pub struct ProcessedDataRecordHeader {
18    pub data_information: Option<DataInformation>,
19    pub value_information: Option<ValueInformation>,
20}
21#[cfg_attr(feature = "serde", derive(serde::Serialize))]
22#[derive(Debug, PartialEq, Clone)]
23#[cfg_attr(feature = "defmt", derive(defmt::Format))]
24pub struct DataRecord<'a> {
25    pub data_record_header: DataRecordHeader<'a>,
26    pub data: Data<'a>,
27    /// Raw bytes encompassing this data record
28    pub raw_bytes: &'a [u8],
29}
30
31impl DataRecord<'_> {
32    #[must_use]
33    pub fn get_size(&self) -> usize {
34        self.raw_bytes.len()
35    }
36
37    #[cfg(feature = "std")]
38    #[must_use]
39    pub fn data_record_header_hex(&self) -> String {
40        let start = 0;
41        let end = self.data_record_header.get_size();
42        self.raw_bytes
43            .get(start..end)
44            .unwrap_or(&[])
45            .iter()
46            .map(|b| format!("{:02X}", b))
47            .collect::<Vec<_>>()
48            .join(" ")
49    }
50
51    #[cfg(feature = "std")]
52    #[must_use]
53    pub fn data_hex(&self) -> String {
54        let start = self.data_record_header.get_size();
55        let end = self.get_size();
56        self.raw_bytes
57            .get(start..end)
58            .unwrap_or(&[])
59            .iter()
60            .map(|b| format!("{:02X}", b))
61            .collect::<Vec<_>>()
62            .join(" ")
63    }
64}
65
66impl<'a> DataRecord<'a> {
67    fn parse(
68        data: &'a [u8],
69        fixed_data_header: Option<&'a LongTplHeader>,
70    ) -> Result<Self, DataRecordError> {
71        let data_record_header = DataRecordHeader::try_from(data)?;
72        let header_size = data_record_header.get_size();
73        if data.len() < header_size {
74            return Err(DataRecordError::InsufficientData);
75        }
76        let offset = header_size;
77        let data_out = if let Some(data_info) = &data_record_header
78            .processed_data_record_header
79            .data_information
80        {
81            data_info.data_field_coding.parse(
82                data.get(offset..)
83                    .ok_or(DataRecordError::InsufficientData)?,
84                fixed_data_header,
85            )?
86        } else {
87            Data {
88                value: Some(DataType::ManufacturerSpecific(
89                    data.get(offset..)
90                        .ok_or(DataRecordError::InsufficientData)?,
91                )),
92                size: data.len() - offset,
93            }
94        };
95
96        let mut record_size = data_record_header.get_size() + data_out.get_size();
97        if record_size > data.len() {
98            record_size = data.len();
99        }
100        let raw_bytes = data
101            .get(..record_size)
102            .ok_or(DataRecordError::InsufficientData)?;
103
104        Ok(DataRecord {
105            data_record_header,
106            data: data_out,
107            raw_bytes,
108        })
109    }
110}
111
112#[cfg_attr(feature = "serde", derive(serde::Serialize))]
113#[derive(Debug, PartialEq, Clone)]
114#[cfg_attr(feature = "defmt", derive(defmt::Format))]
115pub struct DataRecordHeader<'a> {
116    pub raw_data_record_header: RawDataRecordHeader<'a>,
117    pub processed_data_record_header: ProcessedDataRecordHeader,
118}
119
120impl DataRecordHeader<'_> {
121    #[must_use]
122    pub fn get_size(&self) -> usize {
123        let s = self
124            .raw_data_record_header
125            .data_information_block
126            .get_size();
127        if let Some(x) = &self.raw_data_record_header.value_information_block {
128            s + x.get_size()
129        } else {
130            s
131        }
132    }
133}
134
135impl<'a> TryFrom<&'a [u8]> for RawDataRecordHeader<'a> {
136    type Error = DataRecordError;
137    fn try_from(data: &[u8]) -> Result<RawDataRecordHeader<'_>, DataRecordError> {
138        let difb = DataInformationBlock::try_from(data)?;
139        let offset = difb.get_size();
140
141        let mut vifb = None;
142
143        if !difb.data_information_field.is_special_function() {
144            vifb = Some(ValueInformationBlock::try_from(
145                data.get(offset..)
146                    .ok_or(DataRecordError::InsufficientData)?,
147            )?);
148        }
149
150        Ok(RawDataRecordHeader {
151            data_information_block: difb,
152            value_information_block: vifb,
153        })
154    }
155}
156
157impl TryFrom<&RawDataRecordHeader<'_>> for ProcessedDataRecordHeader {
158    type Error = DataRecordError;
159    fn try_from(raw_data_record_header: &RawDataRecordHeader) -> Result<Self, DataRecordError> {
160        let mut value_information = None;
161        let mut data_information = None;
162
163        if let Some(x) = &raw_data_record_header.value_information_block {
164            let v = ValueInformation::try_from(x)?;
165
166            let mut d = DataInformation::try_from(&raw_data_record_header.data_information_block)?;
167
168            // unfortunately, the data field coding is not always set in the data information block
169            // so we must do some additional checks to determine the correct data field coding
170
171            if v.labels.contains(&ValueLabel::Date) {
172                d.data_field_coding = DataFieldCoding::DateTypeG;
173            } else if v.labels.contains(&ValueLabel::DateTime) {
174                d.data_field_coding = DataFieldCoding::DateTimeTypeF;
175            } else if v.labels.contains(&ValueLabel::Time) {
176                d.data_field_coding = DataFieldCoding::DateTimeTypeJ;
177            } else if v.labels.contains(&ValueLabel::DateTimeWithSeconds) {
178                d.data_field_coding = DataFieldCoding::DateTimeTypeI;
179            }
180
181            value_information = Some(v);
182            data_information = Some(d);
183        } else if raw_data_record_header
184            .data_information_block
185            .data_information_field
186            .is_special_function()
187        {
188            data_information = Some(DataInformation::try_from(
189                &raw_data_record_header.data_information_block,
190            )?);
191        }
192
193        Ok(Self {
194            data_information,
195            value_information,
196        })
197    }
198}
199
200impl<'a> TryFrom<&'a [u8]> for DataRecordHeader<'a> {
201    type Error = DataRecordError;
202    fn try_from(data: &'a [u8]) -> Result<Self, DataRecordError> {
203        let raw_data_record_header = RawDataRecordHeader::try_from(data)?;
204        let processed_data_record_header =
205            ProcessedDataRecordHeader::try_from(&raw_data_record_header)?;
206        Ok(Self {
207            raw_data_record_header,
208            processed_data_record_header,
209        })
210    }
211}
212
213impl<'a> TryFrom<(&'a [u8], &'a LongTplHeader)> for DataRecord<'a> {
214    type Error = DataRecordError;
215    fn try_from(
216        (data, fixed_data_header): (&'a [u8], &'a LongTplHeader),
217    ) -> Result<Self, Self::Error> {
218        Self::parse(data, Some(fixed_data_header))
219    }
220}
221
222impl<'a> TryFrom<&'a [u8]> for DataRecord<'a> {
223    type Error = DataRecordError;
224    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
225        Self::parse(data, None)
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn test_parse_raw_data_record() {
235        let data = &[0x03, 0x13, 0x15, 0x31, 0x00];
236        let _result = DataRecordHeader::try_from(data.as_slice());
237    }
238    #[test]
239    #[cfg(feature = "std")]
240    fn test_manufacturer_specific_block() {
241        let data = [0x0F, 0x01, 0x02, 0x03, 0x04];
242        let result = DataRecord::try_from(data.as_slice());
243        println!("{:?}", result);
244    }
245}