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 mut header_size = data_record_header.get_size();
73        if data_record_header
74            .raw_data_record_header
75            .data_information_block
76            .data_information_field
77            .data
78            == 0x0F
79        {
80            header_size = 0;
81        }
82        if data.len() < header_size {
83            return Err(DataRecordError::InsufficientData);
84        }
85        let offset = header_size;
86        let mut data_out = Data {
87            value: Some(DataType::ManufacturerSpecific(
88                data.get(offset..)
89                    .ok_or(DataRecordError::InsufficientData)?,
90            )),
91            size: data.len() - offset,
92        };
93        if data_record_header
94            .raw_data_record_header
95            .value_information_block
96            .is_some()
97        {
98            if let Some(data_info) = &data_record_header
99                .processed_data_record_header
100                .data_information
101            {
102                data_out = data_info.data_field_coding.parse(
103                    data.get(offset..)
104                        .ok_or(DataRecordError::InsufficientData)?,
105                    fixed_data_header,
106                )?;
107            }
108        }
109
110        let mut record_size = data_record_header.get_size() + data_out.get_size();
111        if record_size > data.len() {
112            record_size = data.len();
113        }
114        let raw_bytes = data
115            .get(..record_size)
116            .ok_or(DataRecordError::InsufficientData)?;
117
118        Ok(DataRecord {
119            data_record_header,
120            data: data_out,
121            raw_bytes,
122        })
123    }
124}
125
126#[cfg_attr(feature = "serde", derive(serde::Serialize))]
127#[derive(Debug, PartialEq, Clone)]
128#[cfg_attr(feature = "defmt", derive(defmt::Format))]
129pub struct DataRecordHeader<'a> {
130    pub raw_data_record_header: RawDataRecordHeader<'a>,
131    pub processed_data_record_header: ProcessedDataRecordHeader,
132}
133
134impl DataRecordHeader<'_> {
135    #[must_use]
136    pub fn get_size(&self) -> usize {
137        let s = self
138            .raw_data_record_header
139            .data_information_block
140            .get_size();
141        if let Some(x) = &self.raw_data_record_header.value_information_block {
142            s + x.get_size()
143        } else {
144            s
145        }
146    }
147}
148
149impl<'a> TryFrom<&'a [u8]> for RawDataRecordHeader<'a> {
150    type Error = DataRecordError;
151    fn try_from(data: &[u8]) -> Result<RawDataRecordHeader<'_>, DataRecordError> {
152        let difb = DataInformationBlock::try_from(data)?;
153        let offset = difb.get_size();
154
155        let mut vifb = None;
156
157        if difb.data_information_field.data != 0x0F {
158            vifb = Some(ValueInformationBlock::try_from(
159                data.get(offset..)
160                    .ok_or(DataRecordError::InsufficientData)?,
161            )?);
162        }
163
164        Ok(RawDataRecordHeader {
165            data_information_block: difb,
166            value_information_block: vifb,
167        })
168    }
169}
170
171impl TryFrom<&RawDataRecordHeader<'_>> for ProcessedDataRecordHeader {
172    type Error = DataRecordError;
173    fn try_from(raw_data_record_header: &RawDataRecordHeader) -> Result<Self, DataRecordError> {
174        let mut value_information = None;
175        let mut data_information = None;
176
177        if let Some(x) = &raw_data_record_header.value_information_block {
178            let v = ValueInformation::try_from(x)?;
179
180            let mut d = DataInformation::try_from(&raw_data_record_header.data_information_block)?;
181
182            // unfortunately, the data field coding is not always set in the data information block
183            // so we must do some additional checks to determine the correct data field coding
184
185            if v.labels.contains(&ValueLabel::Date) {
186                d.data_field_coding = DataFieldCoding::DateTypeG;
187            } else if v.labels.contains(&ValueLabel::DateTime) {
188                d.data_field_coding = DataFieldCoding::DateTimeTypeF;
189            } else if v.labels.contains(&ValueLabel::Time) {
190                d.data_field_coding = DataFieldCoding::DateTimeTypeJ;
191            } else if v.labels.contains(&ValueLabel::DateTimeWithSeconds) {
192                d.data_field_coding = DataFieldCoding::DateTimeTypeI;
193            }
194
195            value_information = Some(v);
196            data_information = Some(d);
197        }
198
199        Ok(Self {
200            data_information,
201            value_information,
202        })
203    }
204}
205
206impl<'a> TryFrom<&'a [u8]> for DataRecordHeader<'a> {
207    type Error = DataRecordError;
208    fn try_from(data: &'a [u8]) -> Result<Self, DataRecordError> {
209        let raw_data_record_header = RawDataRecordHeader::try_from(data)?;
210        let processed_data_record_header =
211            ProcessedDataRecordHeader::try_from(&raw_data_record_header)?;
212        Ok(Self {
213            raw_data_record_header,
214            processed_data_record_header,
215        })
216    }
217}
218
219impl<'a> TryFrom<(&'a [u8], &'a LongTplHeader)> for DataRecord<'a> {
220    type Error = DataRecordError;
221    fn try_from(
222        (data, fixed_data_header): (&'a [u8], &'a LongTplHeader),
223    ) -> Result<Self, Self::Error> {
224        Self::parse(data, Some(fixed_data_header))
225    }
226}
227
228impl<'a> TryFrom<&'a [u8]> for DataRecord<'a> {
229    type Error = DataRecordError;
230    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
231        Self::parse(data, None)
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn test_parse_raw_data_record() {
241        let data = &[0x03, 0x13, 0x15, 0x31, 0x00];
242        let _result = DataRecordHeader::try_from(data.as_slice());
243    }
244    #[test]
245    #[cfg(feature = "std")]
246    fn test_manufacturer_specific_block() {
247        let data = [0x0F, 0x01, 0x02, 0x03, 0x04];
248        let result = DataRecord::try_from(data.as_slice());
249        println!("{:?}", result);
250    }
251}