-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add modbus/rtu client implementation similar to existing server (#14)
* feat: add modbus/rtu client implementation * fix: publish pub items from `codec` module There are pub items, but crate didn't publish them and warning `unreachable_pub` got generated.
- Loading branch information
Showing
3 changed files
with
129 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
//! Modbus RTU client (master) specific functions. | ||
use super::*; | ||
|
||
/// Encode and RTU request. | ||
pub fn encode_request(adu: RequestAdu, buf: &mut [u8]) -> Result<usize> { | ||
let RequestAdu { hdr, pdu } = adu; | ||
if buf.len() < 2 { | ||
return Err(Error::BufferSize); | ||
} | ||
let len = pdu.encode(&mut buf[1..])?; | ||
if buf.len() < len + 3 { | ||
return Err(Error::BufferSize); | ||
} | ||
buf[0] = hdr.slave; | ||
let crc = crc16(&buf[0..=len]); | ||
BigEndian::write_u16(&mut buf[len + 1..], crc); | ||
Ok(len + 3) | ||
} | ||
|
||
/// Decode an RTU response. | ||
pub fn decode_response(buf: &[u8]) -> Result<Option<ResponseAdu>> { | ||
if buf.is_empty() { | ||
return Ok(None); | ||
} | ||
decode(DecoderType::Response, buf) | ||
.and_then(|frame| { | ||
let Some((DecodedFrame { slave, pdu }, _frame_pos)) = frame else { | ||
return Ok(None); | ||
}; | ||
let hdr = Header { slave }; | ||
// Decoding of the PDU should are unlikely to fail due | ||
// to transmission errors, because the frame's bytes | ||
// have already been verified with the CRC. | ||
|
||
ExceptionResponse::try_from(pdu) | ||
.map(|er| ResponsePdu(Err(er))) | ||
.or_else(|_| Response::try_from(pdu).map(|r| ResponsePdu(Ok(r)))) | ||
.map(|pdu| Some(ResponseAdu { hdr, pdu })) | ||
.map_err(|err| { | ||
// Unrecoverable error | ||
log::error!("Failed to decode Response PDU: {err}"); | ||
err | ||
}) | ||
}) | ||
.map_err(|_| { | ||
// Decoding the transport frame is non-destructive and must | ||
// never fail! | ||
unreachable!(); | ||
}) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn decode_empty_response() { | ||
let req = decode_response(&[]).unwrap(); | ||
assert!(req.is_none()); | ||
} | ||
|
||
#[test] | ||
fn decode_partly_received_response() { | ||
let buf = &[ | ||
0x12, // slave address | ||
0x16, // function code | ||
]; | ||
let req = decode_response(buf).unwrap(); | ||
assert!(req.is_none()); | ||
} | ||
|
||
#[test] | ||
fn encode_write_single_register_request() { | ||
let mut buf = [0u8; 255]; | ||
let sz = encode_request( | ||
RequestAdu { | ||
hdr: Header { slave: 0x12 }, | ||
pdu: RequestPdu(Request::WriteSingleRegister(0x2222, 0xABCD)), | ||
}, | ||
&mut buf, | ||
) | ||
.expect("Error encoding request"); | ||
|
||
let req = &buf[..sz]; | ||
assert_eq!( | ||
req, | ||
&[ | ||
0x12, // slave address | ||
0x06, // function code | ||
0x22, // addr | ||
0x22, // addr | ||
0xAB, // value | ||
0xCD, // value | ||
0x9F, // crc | ||
0xBE, // crc | ||
] | ||
); | ||
} | ||
|
||
#[test] | ||
fn decode_write_single_register_response() { | ||
use crate::frame::Response; | ||
let rsp = &[0x12, 0x06, 0x22, 0x22, 0xAB, 0xCD, 0x9F, 0xBE]; | ||
|
||
assert!(matches!( | ||
decode_response(rsp), | ||
Ok(Some(ResponseAdu { | ||
hdr: Header { slave: 0x12 }, | ||
pdu: ResponsePdu(Ok(Response::WriteSingleRegister(0x2222, 0xABCD))) | ||
})) | ||
)); | ||
} | ||
|
||
#[test] | ||
fn decode_malformed_write_single_register_response() { | ||
let rsp = &[0x12, 0x06, 0x22, 0x22, 0xAB, 0x65, 0x9E]; | ||
|
||
assert!(matches!(decode_response(rsp), Ok(None))); | ||
} | ||
|
||
#[test] | ||
fn decode_bad_crc_write_single_register_response() { | ||
let rsp = &[0x12, 0x06, 0x22, 0x22, 0xAB, 0xCD, 0x5F, 0xBE]; | ||
|
||
assert!(matches!(decode_response(rsp), Ok(None))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,6 @@ mod frame; | |
|
||
pub use codec::rtu; | ||
pub use codec::tcp; | ||
pub use codec::*; | ||
pub use error::*; | ||
pub use frame::*; | ||
|