You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have notices that custom functions are supported and there is even an example for TCP.
But I was testing in RTU and I found that the custom command is sent, but the response is never processed.
This is in part functions are not supported in: fn get_response_pdu_len(adu_buf: &BytesMut) -> Result<Option<usize>>
Additionally in this funcion some quick fix can be done for supporting exception code responses for all functions, changing the match arm from 0x81..=0xAB to 0x81..=0xFF. As all the exceptions share the same format regardless of the function code.
I guess the initial problem of not supporting this is how to determine the start and end of a frame.
I have tested some changes in the decode functions and I kind of found a way to deal with it. It's not the best option, but it's something.
diff --git a/src/codec/rtu.rs b/src/codec/rtu.rs
index 60a4cf8..5e39da0 100644
--- a/src/codec/rtu.rs+++ b/src/codec/rtu.rs@@ -11,6 +11,7 @@ use log::{debug, error, warn};
use smallvec::SmallVec;
use std::io::{Cursor, Error, ErrorKind, Result};
use tokio_util::codec::{Decoder, Encoder};
+use std::time::{Instant, Duration};
// [MODBUS over Serial Line Specification and Implementation Guide V1.02](http://modbus.org/docs/Modbus_over_serial_l
ine_V1_02.pdf), page 13
// "The maximum size of a MODBUS RTU frame is 256 bytes."
@@ -105,6 +106,8 @@ pub(crate) struct RequestDecoder {
#[derive(Debug, Default, Eq, PartialEq)]
pub(crate) struct ResponseDecoder {
frame_decoder: FrameDecoder,
+ last_time: Option<Instant>,+ last_size: usize,
}
#[derive(Debug, Default, Eq, PartialEq)]
@@ -165,8 +168,15 @@ fn get_response_pdu_len(adu_buf: &BytesMut) -> Result<Option<usize>> {
// incomplete frame
return Ok(None);
}
- }- 0x81..=0xAB => 2,+ },+ // For custom functions, use all the buffer+ 65..=75 | 100..=110 => if adu_buf.len() > 3 {+ adu_buf.len() - 3+ } else {+ return Ok(None);+ },+ // Above this value it means an exception code as the first bit is 1+ 0x81..=0xFF => 2,
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
@@ -223,12 +233,37 @@ impl Decoder for ResponseDecoder {
type Error = Error;
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<(SlaveId, Bytes)>> {
- decode(+ if let Some(last_time) = self.last_time {+ // Check if 20 ms passed since the last burst of data+ if last_time.elapsed() > Duration::from_millis(20) {+ // If it was, clear the previous bytes received+ buf.advance(self.last_size);+ self.last_size = 0;+ }+ }++ let resp = decode(
"response",
&mut self.frame_decoder,
get_response_pdu_len,
buf,
- )+ );++ if let Ok(Some(_)) = resp {+ // Restart if the frame was parsed+ self.last_time = None;+ // Ensure last size is cleared+ self.last_size = 0;+ } else {+ if let Ok(_) = resp {+ // Only update the last_time if the frame was not parsed+ self.last_time = Some(Instant::now());+ }+ // In any case, update the last size+ self.last_size = buf.len();+ }++ resp
}
}
@@ -242,28 +277,20 @@ where
F: Fn(&BytesMut) -> Result<Option<usize>>,
{
// TODO: Transform this loop into idiomatic code
- loop {- let mut retry = false;- let res = get_pdu_len(buf)- .and_then(|pdu_len| {- debug_assert!(!retry);- if let Some(pdu_len) = pdu_len {- frame_decoder.decode(buf, pdu_len)- } else {- // Incomplete frame- Ok(None)- }- })- .or_else(|err| {- warn!("Failed to decode {} frame: {}", pdu_type, err);- frame_decoder.recover_on_error(buf);- retry = true;+ get_pdu_len(buf)+ .and_then(|pdu_len| {+ if let Some(pdu_len) = pdu_len {+ frame_decoder.decode(buf, pdu_len)+ } else {+ // Incomplete frame
Ok(None)
- });- if !retry {- return res;- }- }+ }+ })+ .or_else(|err| {+ warn!("Failed to decode {} frame: {}", pdu_type, err);+ //frame_decoder.recover_on_error(buf);+ Ok(None)+ })
}
impl Decoder for ClientCodec {
Do you think it can be a good solution?
The text was updated successfully, but these errors were encountered:
I would also appreciate this. I have some devices that don't provide register/coil addresses, they only specify a series of bytes to send (according to the Modbus RTU spec). Currently I'm using TTYPort::write() from the serialport crate, but it is no longer maintained and I'd like to move away from that if possible. Also, this crate is async while serialport is not.
Hey just leaving a note that support for custom messages would be awesome. I also need to communicate with a device that just implements custom functions 🙈
ju6ge
added a commit
to ju6ge/tokio-modbus
that referenced
this issue
Jan 9, 2023
Okay so I played around with this just to come to the conclusion that some vendors are just breaking the modbus spec shamelessly and are still calling it modbus. Anyway I do not see a way how this can be resolved nicely. So I will just have to stick with a vendor specific fork for my current project. Sucks but what ever 🤷♂️ …
I have notices that custom functions are supported and there is even an example for TCP.
But I was testing in RTU and I found that the custom command is sent, but the response is never processed.
This is in part functions are not supported in:
fn get_response_pdu_len(adu_buf: &BytesMut) -> Result<Option<usize>>
Additionally in this funcion some quick fix can be done for supporting exception code responses for all functions, changing the match arm from
0x81..=0xAB
to0x81..=0xFF
. As all the exceptions share the same format regardless of the function code.I guess the initial problem of not supporting this is how to determine the start and end of a frame.
I have tested some changes in the decode functions and I kind of found a way to deal with it. It's not the best option, but it's something.
Do you think it can be a good solution?
The text was updated successfully, but these errors were encountered: