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<'a>>,
13}
14#[cfg_attr(feature = "serde", derive(serde::Serialize))]
15#[derive(Debug, PartialEq, Clone)]
16#[cfg_attr(feature = "defmt", derive(defmt::Format))]
17pub struct ProcessedDataRecordHeader<'a> {
18 pub data_information: Option<DataInformation>,
19 pub value_information: Option<ValueInformation<'a>>,
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 #[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<'a> DataRecord<'a> {
36 #[must_use]
38 pub fn value(&self) -> Option<&DataType<'_>> {
39 self.data.value.as_ref()
40 }
41
42 #[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 #[must_use]
53 pub fn value_information(&self) -> Option<&ValueInformation<'a>> {
54 self.data_record_header
55 .processed_data_record_header
56 .value_information
57 .as_ref()
58 }
59
60 #[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<'a>,
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<'a> TryFrom<&RawDataRecordHeader<'a>> for ProcessedDataRecordHeader<'a> {
192 type Error = DataRecordError;
193 fn try_from(raw_data_record_header: &RawDataRecordHeader<'a>) -> 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 let date_time_labels = v.labels().fold(0u8, |flags, label| {
208 flags
209 | match label {
210 ValueLabel::Date => 1,
211 ValueLabel::DateTime => 2,
212 ValueLabel::Time => 4,
213 ValueLabel::DateTimeWithSeconds => 8,
214 _ => 0,
215 }
216 });
217 if date_time_labels & 1 != 0 {
218 d.data_field_coding = DataFieldCoding::DateTypeG;
219 } else if date_time_labels & 2 != 0 {
220 d.data_field_coding = if d.data_field_coding == DataFieldCoding::Integer48Bit {
223 DataFieldCoding::DateTimeTypeI
224 } else {
225 DataFieldCoding::DateTimeTypeF
226 };
227 } else if date_time_labels & 4 != 0 {
228 d.data_field_coding = DataFieldCoding::DateTimeTypeJ;
229 } else if date_time_labels & 8 != 0 {
230 d.data_field_coding = DataFieldCoding::DateTimeTypeI;
231 }
232
233 value_information = Some(v);
234 data_information = Some(d);
235 } else if raw_data_record_header
236 .data_information_block
237 .data_information_field
238 .is_special_function()
239 {
240 data_information = Some(DataInformation::try_from(
241 &raw_data_record_header.data_information_block,
242 )?);
243 }
244
245 Ok(Self {
246 data_information,
247 value_information,
248 })
249 }
250}
251
252impl<'a> TryFrom<&'a [u8]> for DataRecordHeader<'a> {
253 type Error = DataRecordError;
254 fn try_from(data: &'a [u8]) -> Result<Self, DataRecordError> {
255 let raw_data_record_header = RawDataRecordHeader::try_from(data)?;
256 let processed_data_record_header =
257 ProcessedDataRecordHeader::try_from(&raw_data_record_header)?;
258 Ok(Self {
259 raw_data_record_header,
260 processed_data_record_header,
261 })
262 }
263}
264
265impl<'a> TryFrom<(&'a [u8], &'a LongTplHeader)> for DataRecord<'a> {
266 type Error = DataRecordError;
267 fn try_from(
268 (data, fixed_data_header): (&'a [u8], &'a LongTplHeader),
269 ) -> Result<Self, Self::Error> {
270 Self::parse(data, Some(fixed_data_header))
271 }
272}
273
274impl<'a> TryFrom<&'a [u8]> for DataRecord<'a> {
275 type Error = DataRecordError;
276 fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
277 Self::parse(data, None)
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn date_time_overrides_match_all_single_extension_vifs() {
287 for dif in [0x02, 0x04, 0x06, 0x09] {
290 for vif in 0..=u8::MAX {
291 if vif == 0x7d {
294 continue;
295 }
296 for vife in 0..=0x7f {
297 let bytes = [dif, vif, vife];
298 let Ok(raw) = RawDataRecordHeader::try_from(bytes.as_slice()) else {
299 continue;
300 };
301 let Ok(actual) = ProcessedDataRecordHeader::try_from(&raw) else {
302 continue;
303 };
304 let Some(value) = actual.value_information.as_ref() else {
305 continue;
306 };
307 let original = DataInformation::try_from(&raw.data_information_block)
308 .unwrap()
309 .data_field_coding;
310 let expected = if value.has_label(ValueLabel::Date) {
311 DataFieldCoding::DateTypeG
312 } else if value.has_label(ValueLabel::DateTime) {
313 if original == DataFieldCoding::Integer48Bit {
314 DataFieldCoding::DateTimeTypeI
315 } else {
316 DataFieldCoding::DateTimeTypeF
317 }
318 } else if value.has_label(ValueLabel::Time) {
319 DataFieldCoding::DateTimeTypeJ
320 } else if value.has_label(ValueLabel::DateTimeWithSeconds) {
321 DataFieldCoding::DateTimeTypeI
322 } else {
323 original
324 };
325 assert_eq!(
326 actual.data_information.unwrap().data_field_coding,
327 expected,
328 "DIF={dif:02x} VIF={vif:02x} VIFE={vife:02x}",
329 );
330 }
331 }
332 }
333 }
334
335 #[test]
336 fn test_parse_raw_data_record() {
337 let data = &[0x03, 0x13, 0x15, 0x31, 0x00];
338 let _result = DataRecordHeader::try_from(data.as_slice());
339 }
340 #[test]
341 #[cfg(feature = "std")]
342 fn test_manufacturer_specific_block() {
343 let data = [0x0F, 0x01, 0x02, 0x03, 0x04];
344 let result = DataRecord::try_from(data.as_slice());
345 println!("{:?}", result);
346 }
347}