Skip to main content

wired_mbus_link_layer/
lib.rs

1#![cfg_attr(not(any(feature = "std", test)), no_std)]
2
3//! is part of the MBUS data link layer
4//! It is used to encapsulate the application layer data
5use m_bus_core::{FrameError, Function};
6
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[derive(Debug, PartialEq)]
9#[non_exhaustive]
10pub enum WiredFrame<'a> {
11    SingleCharacter {
12        character: u8,
13    },
14    ShortFrame {
15        function: Function,
16        address: Address,
17    },
18    LongFrame {
19        function: Function,
20        address: Address,
21        #[cfg_attr(feature = "serde", serde(skip_serializing))]
22        data: &'a [u8],
23    },
24    ControlFrame {
25        function: Function,
26        address: Address,
27        #[cfg_attr(feature = "serde", serde(skip_serializing))]
28        data: &'a [u8],
29    },
30}
31
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33#[derive(Debug, Clone, PartialEq)]
34#[cfg_attr(feature = "defmt", derive(defmt::Format))]
35#[non_exhaustive]
36pub enum Address {
37    Uninitalized,
38    Primary(u8),
39    Secondary,
40    Broadcast { reply_required: bool },
41}
42
43#[cfg(feature = "std")]
44impl std::fmt::Display for Address {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        match self {
47            Address::Uninitalized => write!(f, "Uninitalized"),
48            Address::Primary(byte) => write!(f, "Primary ({byte})"),
49            Address::Secondary => write!(f, "Secondary"),
50            Address::Broadcast { reply_required } => {
51                write!(f, "Broadcast (Reply Required: {})", reply_required)
52            }
53        }
54    }
55}
56
57impl Address {
58    const fn from(byte: u8) -> Self {
59        match byte {
60            0 => Self::Uninitalized,
61            253 => Self::Secondary,
62            254 => Self::Broadcast {
63                reply_required: true,
64            },
65            255 => Self::Broadcast {
66                reply_required: false,
67            },
68            _ => Self::Primary(byte),
69        }
70    }
71}
72
73impl<'a> TryFrom<&'a [u8]> for WiredFrame<'a> {
74    type Error = FrameError;
75
76    fn try_from(data: &'a [u8]) -> Result<Self, FrameError> {
77        let first_byte = *data.first().ok_or(FrameError::EmptyData)?;
78
79        if first_byte == 0xE5 {
80            return Ok(WiredFrame::SingleCharacter { character: 0xE5 });
81        }
82
83        let second_byte = *data.get(1).ok_or(FrameError::LengthShort)?;
84        let third_byte = *data.get(2).ok_or(FrameError::LengthShort)?;
85
86        match first_byte {
87            0x68 => {
88                validate_checksum(data.get(4..).ok_or(FrameError::LengthShort)?)?;
89
90                let length = *data.get(1).ok_or(FrameError::LengthShort)? as usize;
91
92                if second_byte != third_byte || data.len() != length + 6 {
93                    return Err(FrameError::WrongLengthIndication);
94                }
95
96                if *data.last().ok_or(FrameError::LengthShort)? != 0x16 {
97                    return Err(FrameError::InvalidStopByte);
98                }
99                let control_field = *data.get(4).ok_or(FrameError::LengthShort)?;
100                let address_field = *data.get(5).ok_or(FrameError::LengthShort)?;
101                match control_field {
102                    0x53 => Ok(WiredFrame::ControlFrame {
103                        function: Function::try_from(control_field)?,
104                        address: Address::from(address_field),
105                        data: data.get(6..data.len() - 2).ok_or(FrameError::LengthShort)?,
106                    }),
107                    _ => Ok(WiredFrame::LongFrame {
108                        function: Function::try_from(control_field)?,
109                        address: Address::from(address_field),
110                        data: data.get(6..data.len() - 2).ok_or(FrameError::LengthShort)?,
111                    }),
112                }
113            }
114            0x10 => {
115                validate_checksum(data.get(1..).ok_or(FrameError::LengthShort)?)?;
116                if data.len() == 5 && *data.last().ok_or(FrameError::InvalidStopByte)? == 0x16 {
117                    Ok(WiredFrame::ShortFrame {
118                        function: Function::try_from(second_byte)?,
119                        address: Address::from(third_byte),
120                    })
121                } else {
122                    Err(FrameError::LengthShort)
123                }
124            }
125            _ => Err(FrameError::InvalidStartByte),
126        }
127    }
128}
129
130fn validate_checksum(data: &[u8]) -> Result<(), FrameError> {
131    // Assuming the checksum is the second to last byte in the data array.
132    let checksum_byte_index = data.len() - 2;
133    let checksum_byte = *data
134        .get(checksum_byte_index)
135        .ok_or(FrameError::LengthShort)?;
136
137    let calculated_checksum = data
138        .get(..checksum_byte_index)
139        .ok_or(FrameError::LengthShort)?
140        .iter()
141        .fold(0, |acc: u8, &x| acc.wrapping_add(x));
142
143    if checksum_byte == calculated_checksum {
144        Ok(())
145    } else {
146        Err(FrameError::WrongChecksum {
147            expected: checksum_byte,
148            actual: calculated_checksum,
149        })
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_detect_frame_type() {
159        let single_character_frame: &[u8] = &[0xE5];
160        let short_frame: &[u8] = &[0x10, 0x7B, 0x8b, 0x06, 0x16];
161        let control_frame: &[u8] = &[0x68, 0x03, 0x03, 0x68, 0x53, 0x01, 0x51, 0xA5, 0x16];
162
163        let example: &[u8] = &[
164            0x68, 0x4D, 0x4D, 0x68, 0x08, 0x01, 0x72, 0x01, 0x00, 0x00, 0x00, 0x96, 0x15, 0x01,
165            0x00, 0x18, 0x00, 0x00, 0x00, 0x0C, 0x78, 0x56, 0x00, 0x00, 0x00, 0x01, 0xFD, 0x1B,
166            0x00, 0x02, 0xFC, 0x03, 0x48, 0x52, 0x25, 0x74, 0x44, 0x0D, 0x22, 0xFC, 0x03, 0x48,
167            0x52, 0x25, 0x74, 0xF1, 0x0C, 0x12, 0xFC, 0x03, 0x48, 0x52, 0x25, 0x74, 0x63, 0x11,
168            0x02, 0x65, 0xB4, 0x09, 0x22, 0x65, 0x86, 0x09, 0x12, 0x65, 0xB7, 0x09, 0x01, 0x72,
169            0x00, 0x72, 0x65, 0x00, 0x00, 0xB2, 0x01, 0x65, 0x00, 0x00, 0x1F, 0xB3, 0x16,
170        ];
171
172        assert_eq!(
173            WiredFrame::try_from(single_character_frame),
174            Ok(WiredFrame::SingleCharacter { character: 0xE5 })
175        );
176        assert_eq!(
177            WiredFrame::try_from(short_frame),
178            Ok(WiredFrame::ShortFrame {
179                function: Function::try_from(0x7B).unwrap(),
180                address: Address::from(0x8B)
181            })
182        );
183        assert_eq!(
184            WiredFrame::try_from(control_frame),
185            Ok(WiredFrame::ControlFrame {
186                function: Function::try_from(0x53).unwrap(),
187                address: Address::from(0x01),
188                data: &[0x51]
189            })
190        );
191
192        assert_eq!(
193            WiredFrame::try_from(example),
194            Ok(WiredFrame::LongFrame {
195                function: Function::try_from(8).unwrap(),
196                address: Address::from(1),
197                data: &[
198                    114, 1, 0, 0, 0, 150, 21, 1, 0, 24, 0, 0, 0, 12, 120, 86, 0, 0, 0, 1, 253, 27,
199                    0, 2, 252, 3, 72, 82, 37, 116, 68, 13, 34, 252, 3, 72, 82, 37, 116, 241, 12,
200                    18, 252, 3, 72, 82, 37, 116, 99, 17, 2, 101, 180, 9, 34, 101, 134, 9, 18, 101,
201                    183, 9, 1, 114, 0, 114, 101, 0, 0, 178, 1, 101, 0, 0, 31
202                ]
203            })
204        );
205    }
206}