modbius_core/requests/write/
single.rs

1use core::convert::TryFrom;
2
3use crate::{BitState, ModbusSerializationError, PublicModbusFunction, util};
4
5//TODO it should be possible to write these requests as well the read requests using the same macros
6//      this copy paste of the code is quite bad.
7
8/// Request structure to write single coil
9#[derive(Debug, Clone, Copy, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
10pub struct WriteSingleCoil {
11    pub addr: u16,
12    pub state: BitState,
13}
14
15impl WriteSingleCoil {
16    pub const MODBUS_FUNCTION_CODE: PublicModbusFunction = PublicModbusFunction::WriteSingleCoil;
17
18    /// Create a new request to write a single coil at addr and set it to state
19    pub fn new(addr: u16, state: BitState) -> Self {
20        Self { addr, state }
21    }
22
23    /// Parse this request from the given modbus data
24    ///
25    /// The data should only consist out of the address and quantity as the slave id function
26    /// will be already read through other means.
27    pub fn from_data(data: &[u8]) -> Result<(Self, &[u8]), ModbusSerializationError> {
28        if data.len() < 4 {
29            Err(ModbusSerializationError::UnexpectedEOF {
30                expected: 4,
31                got: data.len(),
32            })
33        } else {
34            unsafe { Self::from_data_unchecked(data) }
35        }
36    }
37
38    /// Parse this request from the given modbus data without bounds checks.
39    ///
40    /// The data should only consist out of the address and quantity as the slave id function
41    /// will be already read through other means.
42    ///
43    /// # Safety
44    /// This function causes undefined behavior if the len of data is smaller than 4
45    pub unsafe fn from_data_unchecked(data: &[u8]) -> Result<(Self, &[u8]), ModbusSerializationError> {
46        let (addr, data) = util::read_u16_unchecked(data);
47        let (state, data) = util::read_u16_unchecked(data);
48        let state = BitState::try_from(state)?;
49
50        Ok((Self::new(addr, state), data))
51    }
52
53    pub fn into_data(self) -> [u8;5] {
54        let addr_bytes = self.addr.to_be_bytes();
55        let state_bytes = u16::to_be_bytes(self.state.into());
56
57        [Self::MODBUS_FUNCTION_CODE as u8, addr_bytes[0], addr_bytes[1], state_bytes[0], state_bytes[1]]
58    }
59
60    /// Write this request to the slice as modbus data
61    pub fn write_to_slice(
62        self,
63        out: &mut [u8],
64    ) -> Result<(), ModbusSerializationError> {
65        if out.len() < 5 {
66            return Err(ModbusSerializationError::InsufficientBuffer {
67                expected: 5,
68                got: out.len(),
69            });
70        }
71
72        unsafe { self.write_to_slice_unchecked(out) };
73        Ok(())
74    }
75
76    /// Write this request to the slice as modbus data without bounds checking.
77    ///
78    /// # Safety
79    /// This function invokes undefined behavior if the len of data is less than 5
80    pub unsafe fn write_to_slice_unchecked(self, out: &mut [u8]) {
81        out.get_unchecked_mut(0..5).copy_from_slice(&self.into_data());
82    }
83}
84
85/// Request structure to write single register
86#[derive(Debug, Clone, Copy, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
87pub struct WriteSingleRegister {
88    pub addr: u16,
89    pub value: u16,
90}
91
92impl WriteSingleRegister {
93    pub const MODBUS_FUNCTION_CODE: PublicModbusFunction = PublicModbusFunction::WriteSingleRegister;
94
95    /// Create a new request to write a single coil at addr and set it to state
96    pub fn new(addr: u16, value: u16) -> Self {
97        Self { addr, value }
98    }
99
100    /// Parse this request from the given modbus data
101    ///
102    /// The data should only consist out of the address and quantity as the slave id function
103    /// will be already read through other means.
104    pub fn from_data(data: &[u8]) -> Result<(Self, &[u8]), ModbusSerializationError> {
105        if data.len() < 4 {
106            Err(ModbusSerializationError::UnexpectedEOF {
107                expected: 4,
108                got: data.len(),
109            })
110        } else {
111            unsafe { Self::from_data_unchecked(data) }
112        }
113    }
114
115    /// Parse this request from the given modbus data without bounds checks.
116    ///
117    /// The data should only consist out of the address and quantity as the slave id function
118    /// will be already read through other means.
119    ///
120    /// # Safety
121    /// This function causes undefined behavior if the len of data is smaller than 4
122    pub unsafe fn from_data_unchecked(data: &[u8]) -> Result<(Self, &[u8]), ModbusSerializationError> {
123        let (addr, data) = util::read_u16_unchecked(data);
124        let (value, data) = util::read_u16_unchecked(data);
125
126        Ok((Self::new(addr, value), data))
127    }
128
129    pub fn into_data(self) -> [u8;5] {
130        let addr_bytes = self.addr.to_be_bytes();
131        let value_bytes = self.value.to_be_bytes();
132
133        [Self::MODBUS_FUNCTION_CODE as u8, addr_bytes[0], addr_bytes[1], value_bytes[0], value_bytes[1]]
134    }
135
136    /// Write this request to the slice as modbus data
137    pub fn write_to_slice(
138        self,
139        out: &mut [u8],
140    ) -> Result<(), ModbusSerializationError> {
141        if out.len() < 5 {
142            return Err(ModbusSerializationError::InsufficientBuffer {
143                expected: 5,
144                got: out.len(),
145            });
146        }
147
148        unsafe { self.write_to_slice_unchecked(out) };
149        Ok(())
150    }
151
152    /// Write this request to the slice as modbus data without bounds checking.
153    ///
154    /// # Safety
155    /// This function invokes undefined behavior if the len of data is less than 5
156    pub unsafe fn write_to_slice_unchecked(self, out: &mut [u8]) {
157        out.get_unchecked_mut(0..5).copy_from_slice(&self.into_data());
158    }
159}