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    #[cfg_attr(
29        feature = "serde",
30        serde(serialize_with = "m_bus_core::serde_hex::serialize")
31    )]
32    pub raw_bytes: &'a [u8],
33}
34
35impl DataRecord<'_> {
36    /// Returns the parsed value carried by this record.
37    #[must_use]
38    pub fn value(&self) -> Option<&DataType<'_>> {
39        self.data.value.as_ref()
40    }
41
42    /// Returns the processed data information (DIF and DIFE fields).
43    #[must_use]
44    pub fn data_information(&self) -> Option<&DataInformation> {
45        self.data_record_header
46            .processed_data_record_header
47            .data_information
48            .as_ref()
49    }
50
51    /// Returns the processed value information (VIF and VIFE fields).
52    #[must_use]
53    pub fn value_information(&self) -> Option<&ValueInformation> {
54        self.data_record_header
55            .processed_data_record_header
56            .value_information
57            .as_ref()
58    }
59
60    /// Returns all raw bytes consumed by this record.
61    #[must_use]
62    pub fn raw_bytes(&self) -> &[u8] {
63        self.raw_bytes
64    }
65
66    #[must_use]
67    pub fn get_size(&self) -> usize {
68        self.raw_bytes.len()
69    }
70
71    #[cfg(feature = "std")]
72    #[must_use]
73    pub fn data_record_header_hex(&self) -> String {
74        let start = 0;
75        let end = self.data_record_header.get_size();
76        self.raw_bytes
77            .get(start..end)
78            .unwrap_or(&[])
79            .iter()
80            .map(|b| format!("{:02X}", b))
81            .collect::<Vec<_>>()
82            .join(" ")
83    }
84
85    #[cfg(feature = "std")]
86    #[must_use]
87    pub fn data_hex(&self) -> String {
88        let start = self.data_record_header.get_size();
89        let end = self.get_size();
90        self.raw_bytes
91            .get(start..end)
92            .unwrap_or(&[])
93            .iter()
94            .map(|b| format!("{:02X}", b))
95            .collect::<Vec<_>>()
96            .join(" ")
97    }
98}
99
100impl<'a> DataRecord<'a> {
101    fn parse(
102        data: &'a [u8],
103        fixed_data_header: Option<&'a LongTplHeader>,
104    ) -> Result<Self, DataRecordError> {
105        let data_record_header = DataRecordHeader::try_from(data)?;
106        let header_size = data_record_header.get_size();
107        if data.len() < header_size {
108            return Err(DataRecordError::InsufficientData);
109        }
110        let offset = header_size;
111        let data_out = if let Some(data_info) = &data_record_header
112            .processed_data_record_header
113            .data_information
114        {
115            data_info.data_field_coding.parse(
116                data.get(offset..)
117                    .ok_or(DataRecordError::InsufficientData)?,
118                fixed_data_header,
119            )?
120        } else {
121            Data {
122                value: Some(DataType::ManufacturerSpecific(
123                    data.get(offset..)
124                        .ok_or(DataRecordError::InsufficientData)?,
125                )),
126                size: data.len() - offset,
127            }
128        };
129
130        let mut record_size = data_record_header.get_size() + data_out.get_size();
131        if record_size > data.len() {
132            record_size = data.len();
133        }
134        let raw_bytes = data
135            .get(..record_size)
136            .ok_or(DataRecordError::InsufficientData)?;
137
138        Ok(DataRecord {
139            data_record_header,
140            data: data_out,
141            raw_bytes,
142        })
143    }
144}
145
146#[cfg_attr(feature = "serde", derive(serde::Serialize))]
147#[derive(Debug, PartialEq, Clone)]
148#[cfg_attr(feature = "defmt", derive(defmt::Format))]
149pub struct DataRecordHeader<'a> {
150    pub raw_data_record_header: RawDataRecordHeader<'a>,
151    pub processed_data_record_header: ProcessedDataRecordHeader,
152}
153
154impl DataRecordHeader<'_> {
155    #[must_use]
156    pub fn get_size(&self) -> usize {
157        let s = self
158            .raw_data_record_header
159            .data_information_block
160            .get_size();
161        if let Some(x) = &self.raw_data_record_header.value_information_block {
162            s + x.get_size()
163        } else {
164            s
165        }
166    }
167}
168
169impl<'a> TryFrom<&'a [u8]> for RawDataRecordHeader<'a> {
170    type Error = DataRecordError;
171    fn try_from(data: &[u8]) -> Result<RawDataRecordHeader<'_>, DataRecordError> {
172        let difb = DataInformationBlock::try_from(data)?;
173        let offset = difb.get_size();
174
175        let mut vifb = None;
176
177        if !difb.data_information_field.is_special_function() {
178            vifb = Some(ValueInformationBlock::try_from(
179                data.get(offset..)
180                    .ok_or(DataRecordError::InsufficientData)?,
181            )?);
182        }
183
184        Ok(RawDataRecordHeader {
185            data_information_block: difb,
186            value_information_block: vifb,
187        })
188    }
189}
190
191impl TryFrom<&RawDataRecordHeader<'_>> for ProcessedDataRecordHeader {
192    type Error = DataRecordError;
193    fn try_from(raw_data_record_header: &RawDataRecordHeader) -> Result<Self, DataRecordError> {
194        let mut value_information = None;
195        let mut data_information = None;
196
197        if let Some(x) = &raw_data_record_header.value_information_block {
198            let v = ValueInformation::try_from(x)?;
199
200            let mut d = DataInformation::try_from(&raw_data_record_header.data_information_block)?;
201
202            // unfortunately, the data field coding is not always set in the data information block
203            // so we must do some additional checks to determine the correct data field coding
204
205            if v.labels.contains(&ValueLabel::Date) {
206                d.data_field_coding = DataFieldCoding::DateTypeG;
207            } else if v.labels.contains(&ValueLabel::DateTime) {
208                // VIF 0x6D with a 6-byte data field is a type I date and time
209                // (EN 13757-3), only the 4-byte variant is type F.
210                d.data_field_coding = if d.data_field_coding == DataFieldCoding::Integer48Bit {
211                    DataFieldCoding::DateTimeTypeI
212                } else {
213                    DataFieldCoding::DateTimeTypeF
214                };
215            } else if v.labels.contains(&ValueLabel::Time) {
216                d.data_field_coding = DataFieldCoding::DateTimeTypeJ;
217            } else if v.labels.contains(&ValueLabel::DateTimeWithSeconds) {
218                d.data_field_coding = DataFieldCoding::DateTimeTypeI;
219            }
220
221            value_information = Some(v);
222            data_information = Some(d);
223        } else if raw_data_record_header
224            .data_information_block
225            .data_information_field
226            .is_special_function()
227        {
228            data_information = Some(DataInformation::try_from(
229                &raw_data_record_header.data_information_block,
230            )?);
231        }
232
233        Ok(Self {
234            data_information,
235            value_information,
236        })
237    }
238}
239
240impl<'a> TryFrom<&'a [u8]> for DataRecordHeader<'a> {
241    type Error = DataRecordError;
242    fn try_from(data: &'a [u8]) -> Result<Self, DataRecordError> {
243        let raw_data_record_header = RawDataRecordHeader::try_from(data)?;
244        let processed_data_record_header =
245            ProcessedDataRecordHeader::try_from(&raw_data_record_header)?;
246        Ok(Self {
247            raw_data_record_header,
248            processed_data_record_header,
249        })
250    }
251}
252
253impl<'a> TryFrom<(&'a [u8], &'a LongTplHeader)> for DataRecord<'a> {
254    type Error = DataRecordError;
255    fn try_from(
256        (data, fixed_data_header): (&'a [u8], &'a LongTplHeader),
257    ) -> Result<Self, Self::Error> {
258        Self::parse(data, Some(fixed_data_header))
259    }
260}
261
262impl<'a> TryFrom<&'a [u8]> for DataRecord<'a> {
263    type Error = DataRecordError;
264    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
265        Self::parse(data, None)
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn test_parse_raw_data_record() {
275        let data = &[0x03, 0x13, 0x15, 0x31, 0x00];
276        let _result = DataRecordHeader::try_from(data.as_slice());
277    }
278    #[test]
279    #[cfg(feature = "std")]
280    fn test_manufacturer_specific_block() {
281        let data = [0x0F, 0x01, 0x02, 0x03, 0x04];
282        let result = DataRecord::try_from(data.as_slice());
283        println!("{:?}", result);
284    }
285}