m_bus_parser/user_data/
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    FixedDataHeader,
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}
28
29impl DataRecord<'_> {
30    #[must_use]
31    pub fn get_size(&self) -> usize {
32        self.data_record_header.get_size() + self.data.get_size()
33    }
34}
35
36impl<'a> DataRecord<'a> {
37    fn parse(
38        data: &'a [u8],
39        fixed_data_header: Option<&'a FixedDataHeader>,
40    ) -> Result<Self, DataRecordError> {
41        let data_record_header = DataRecordHeader::try_from(data)?;
42        let mut offset = data_record_header
43            .raw_data_record_header
44            .data_information_block
45            .get_size();
46        let mut data_out = Data {
47            value: Some(DataType::ManufacturerSpecific(data)),
48            size: data.len(),
49        };
50        if let Some(x) = &data_record_header
51            .raw_data_record_header
52            .value_information_block
53        {
54            offset += x.get_size();
55            if let Some(data_info) = &data_record_header
56                .processed_data_record_header
57                .data_information
58            {
59                data_out = data_info.data_field_coding.parse(
60                    data.get(offset..)
61                        .ok_or(DataRecordError::InsufficientData)?,
62                    fixed_data_header,
63                )?;
64            }
65        }
66
67        Ok(DataRecord {
68            data_record_header,
69            data: data_out,
70        })
71    }
72}
73
74#[cfg_attr(feature = "serde", derive(serde::Serialize))]
75#[derive(Debug, PartialEq, Clone)]
76#[cfg_attr(feature = "defmt", derive(defmt::Format))]
77pub struct DataRecordHeader<'a> {
78    pub raw_data_record_header: RawDataRecordHeader<'a>,
79    pub processed_data_record_header: ProcessedDataRecordHeader,
80}
81
82impl DataRecordHeader<'_> {
83    #[must_use]
84    pub fn get_size(&self) -> usize {
85        let s = self
86            .raw_data_record_header
87            .data_information_block
88            .get_size();
89        if let Some(x) = &self.raw_data_record_header.value_information_block {
90            s + x.get_size()
91        } else {
92            s
93        }
94    }
95}
96
97impl<'a> TryFrom<&'a [u8]> for RawDataRecordHeader<'a> {
98    type Error = DataRecordError;
99    fn try_from(data: &[u8]) -> Result<RawDataRecordHeader, DataRecordError> {
100        let difb = DataInformationBlock::try_from(data)?;
101        let offset = difb.get_size();
102
103        let mut vifb = None;
104
105        if difb.data_information_field.data != 0x0F {
106            vifb = Some(ValueInformationBlock::try_from(
107                data.get(offset..)
108                    .ok_or(DataRecordError::InsufficientData)?,
109            )?);
110        }
111
112        Ok(RawDataRecordHeader {
113            data_information_block: difb,
114            value_information_block: vifb,
115        })
116    }
117}
118
119impl TryFrom<&RawDataRecordHeader<'_>> for ProcessedDataRecordHeader {
120    type Error = DataRecordError;
121    fn try_from(raw_data_record_header: &RawDataRecordHeader) -> Result<Self, DataRecordError> {
122        let mut value_information = None;
123        let mut data_information = None;
124
125        if let Some(x) = &raw_data_record_header.value_information_block {
126            let v = ValueInformation::try_from(x)?;
127
128            let mut d = DataInformation::try_from(&raw_data_record_header.data_information_block)?;
129
130            // unfortunately, the data field coding is not always set in the data information block
131            // so we must do some additional checks to determine the correct data field coding
132
133            if v.labels.contains(&ValueLabel::Date) {
134                d.data_field_coding = DataFieldCoding::DateTypeG;
135            } else if v.labels.contains(&ValueLabel::DateTime) {
136                d.data_field_coding = DataFieldCoding::DateTimeTypeF;
137            } else if v.labels.contains(&ValueLabel::Time) {
138                d.data_field_coding = DataFieldCoding::DateTimeTypeJ;
139            } else if v.labels.contains(&ValueLabel::DateTimeWithSeconds) {
140                d.data_field_coding = DataFieldCoding::DateTimeTypeI;
141            }
142
143            value_information = Some(v);
144            data_information = Some(d);
145        }
146
147        Ok(Self {
148            data_information,
149            value_information,
150        })
151    }
152}
153
154impl<'a> TryFrom<&'a [u8]> for DataRecordHeader<'a> {
155    type Error = DataRecordError;
156    fn try_from(data: &'a [u8]) -> Result<Self, DataRecordError> {
157        let raw_data_record_header = RawDataRecordHeader::try_from(data)?;
158        let processed_data_record_header =
159            ProcessedDataRecordHeader::try_from(&raw_data_record_header)?;
160        Ok(Self {
161            raw_data_record_header,
162            processed_data_record_header,
163        })
164    }
165}
166
167impl<'a> TryFrom<(&'a [u8], &'a FixedDataHeader)> for DataRecord<'a> {
168    type Error = DataRecordError;
169    fn try_from(
170        (data, fixed_data_header): (&'a [u8], &'a FixedDataHeader),
171    ) -> Result<Self, Self::Error> {
172        Self::parse(data, Some(fixed_data_header))
173    }
174}
175
176impl<'a> TryFrom<&'a [u8]> for DataRecord<'a> {
177    type Error = DataRecordError;
178    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
179        Self::parse(data, None)
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn test_parse_raw_data_record() {
189        let data = &[0x03, 0x13, 0x15, 0x31, 0x00];
190        let _result = DataRecordHeader::try_from(data.as_slice());
191    }
192    #[test]
193    #[cfg(feature = "std")]
194    fn test_manufacturer_specific_block() {
195        let data = [0x0F, 0x01, 0x02, 0x03, 0x04];
196        let result = DataRecord::try_from(data.as_slice());
197        println!("{:?}", result);
198    }
199}