Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for custom functions in RTU #123

Open
vdominguez1993 opened this issue Sep 1, 2022 · 3 comments
Open

Support for custom functions in RTU #123

vdominguez1993 opened this issue Sep 1, 2022 · 3 comments

Comments

@vdominguez1993
Copy link

vdominguez1993 commented Sep 1, 2022

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?

@flosse flosse added the rtu label Oct 5, 2022
@llamicron
Copy link

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.

Here's the spec for a device that doesn't provide registers/coils, as an example.

Maybe I'm misunderstanding the issue and this is actually possible with tokio-modbus...

@ju6ge
Copy link

ju6ge commented Jan 9, 2023

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
@ju6ge
Copy link

ju6ge commented Jan 10, 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 🤷‍♂️ …

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants