Skip to main content

wired_mbus_link_layer/
lib.rs

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