async_modbus/
util.rs

1/// CRC for Modbus RTU messages.
2pub fn crc(data: &[u8]) -> u16 {
3    let mut crc = 0xffff;
4
5    for &byte in data {
6        crc ^= u16::from(byte);
7        for _ in 0..8 {
8            if (crc & 0x0001) != 0 {
9                crc >>= 1;
10                crc ^= 0xa001;
11            } else {
12                crc >>= 1;
13            }
14        }
15    }
16
17    crc
18}
19
20/// Macro to generate common methods for Modbus message types
21macro_rules! modbus_message_impl {
22    ($function_code:expr, $($field_name:ident: $field_type:ty),*) => {
23        $crate::util::modbus_message_impl!($function_code, $($field_name: $field_type),*; );
24    };
25    ($function_code:expr, $($field_name:ident: $field_type:ty),*; $($prologue:stmt)*) => {
26        #[allow(dead_code)]
27        pub(crate) const FUNCTION: u8 = $function_code;
28
29        #[allow(dead_code)]
30        #[inline]
31        fn new_inner(addr: u8, $($field_name: $field_type),*) -> Self {
32            $($prologue)*
33
34            let mut message = Self {
35                addr,
36                function: $function_code,
37                $(
38                    $field_name,
39                )*
40                crc: Default::default(),
41            };
42
43            message.crc = message.calculate_crc().into();
44            message
45        }
46
47        #[allow(dead_code)]
48        fn new_with_inner(addr: u8, f: impl FnOnce(&mut Self)) -> Self {
49            $($prologue)*
50
51            let mut message = <Self as zerocopy::FromZeros>::new_zeroed();
52            message.addr = addr;
53            message.function = $function_code;
54            f(&mut message);
55            message.crc = message.calculate_crc().into();
56            message
57        }
58
59        pub(crate) fn calculate_crc(&self) -> u16 {
60            let bytes = self.as_bytes();
61            // The last two bytes are the CRC itself
62            crate::crc(&bytes[..bytes.len() - 2])
63        }
64
65        /// Check if the CRC is valid.
66        pub fn validate_crc(&self) -> Result<(), $crate::CrcError> {
67            if self.crc.get() == self.calculate_crc() {
68                Ok(())
69            } else {
70                Err($crate::CrcError)
71            }
72        }
73
74        /// Update the CRC from the current message.
75        pub fn update_crc(&mut self) {
76            self.crc = self.calculate_crc().into();
77        }
78
79        /// Get the device address.
80        pub fn address(&self) -> u8 {
81            self.addr
82        }
83
84        /// Get the function code.
85        pub(crate) fn function(&self) -> u8 {
86            self.function
87        }
88    };
89}
90
91/// Macro to generate Modbus message types
92macro_rules! modbus_message {
93    (
94        $(#[$outer:meta])*
95        $name:ident {
96            function_code: $function_code:expr,
97            $(
98                $field_name:ident: $field_type:ty
99            ),* $(,)?
100        }
101    ) => {
102        $(#[$outer])*
103        #[derive(IntoBytes, Immutable, FromBytes, KnownLayout)]
104        #[repr(C)]
105        pub struct $name {
106            addr: u8,
107            function: u8,
108            $(
109                pub(crate) $field_name: $field_type,
110            )*
111            crc: zerocopy::little_endian::U16,
112        }
113
114        impl $name {
115            $crate::util::modbus_message_impl!($function_code, $($field_name: $field_type),*);
116        }
117    };
118
119    // Variant for messages with const generics
120    (
121        $(#[$outer:meta])*
122        $name:ident<const N: usize> {
123            function_code: $function_code:expr,
124            $(
125                $field_name:ident: $field_type:ty
126            ),* $(,)?
127        }
128    ) => {
129        $(#[$outer])*
130        #[derive(IntoBytes, Immutable, FromBytes, KnownLayout)]
131        #[repr(C)]
132        pub struct $name<const N: usize> {
133            addr: u8,
134            function: u8,
135            $(
136                pub(crate) $field_name: $field_type,
137            )*
138            crc: zerocopy::little_endian::U16,
139        }
140
141        impl<const N: usize> $name<N> {
142            $crate::util::modbus_message_impl!(
143                $function_code,
144                $($field_name: $field_type),*;
145                const { assert!(N <= 127, "N must be less than or equal to 127") }
146            );
147        }
148    };
149}
150
151pub(crate) use {modbus_message, modbus_message_impl};
152
153#[cfg(test)]
154mod tests {
155    use hex_literal::hex;
156
157    #[test]
158    fn crc() {
159        assert_eq!(super::crc(&hex!("00 06 00 00 00 17")), 0x15c8);
160    }
161}