stephane / libmodbus

A Modbus library for Linux, Mac OS, FreeBSD and Windows
http://libmodbus.org
GNU Lesser General Public License v2.1
3.42k stars 1.74k forks source link

Patch for hardware that echoes transmitted bytes #34

Closed jaimealsilva closed 12 years ago

jaimealsilva commented 12 years ago

Hello there.

First I want to thank Stephane for this work. Kudos man! This is a great library that makes working with Modbus so easy ! and I can get it on my Debian machines by only using apt-get! Thanks a lot! I know is not easy to find the time and energy to organize and share your work.

Now to the subject: I'm using libmodbus on a Technologic Systems TS-7800 SBC with the RS485 optional bus. It turns out that this board's MAX485 is wired in such a way that the receiver is always enabled (check out page 6 of it's schematic bottom-right corner) so everything you transmit gets back on the receive buffer, libmodbus thinks it is the slave answer and fails with a checksum error.

I tried to fix it by disabling the receiver before transmitting with tcsetatrr and turning it back on after transmission but it didn't worked, the bytes were still echoed to the receive buffer. So finally I fixed it by reading from the port the same amount of bytes transmitted so they get out of the buffer.

I think this can be useful to someone else who has hardware that echoes the transmitted bytes. So I add a new field to the modbus_rtu structure and, added a couple functions and modified the rtu's transmit function. The new functions are:

int modbus_rtu_set_echohw_mode(modbus_t *ctx, uint8_t mode);
int modbus_rtu_get_echohw_mode(modbus_t *ctx);

echohw mode can be one of MODBUS_RTU_NO_ECHOHW and MODBUS_RTU_HAS_ECHOHW. By default the RTU is in MODBUS_RTU_NO_ECHOHW which means it works as usual, but if you change it to MODBUS_RTU_HAS_ECHOHW then all the transmitted bytes will be taken out of the receive buffer.

Please excuse me for sending the patch this way but I'm not familiar with how patches should be send on Github. Maybe I should fork or something but I'm on a hurry and I want to share this before I forget. So after this paragraph comes the patch against the src directory of the current stable version (3..0.1). And once again, thanks for the great work.

diff -rupN src-orig/modbus-rtu.c src/modbus-rtu.c
--- src-orig/modbus-rtu.c   2011-10-23 12:29:38.000000000 -0500
+++ src/modbus-rtu.c    2011-10-23 13:56:33.000000000 -0500
@@ -264,7 +264,28 @@ ssize_t _modbus_rtu_send(modbus_t *ctx,
DWORD n_bytes = 0;
return (WriteFile(ctx_rtu->w_ser.fd, req, req_length, &n_bytes, NULL)) ? n_bytes : -1;
#else
-    return write(ctx->s, req, req_length);
+    int w, r, i;
+    uint8_t *rb;
+    modbus_rtu_t *ctx_rtu = ctx->backend_data;
+    
+    // Transmit
+    w= write(ctx->s, req, req_length);
+    
+    // Read back written bytes if hw has echo
+    if (ctx_rtu->echohw) {
+      rb = malloc(w);
+      r= 0;
+      while (r < w)
+        r += read(ctx->s, rb + r, w - r);
+      if (ctx->debug) {
+        for (i = 0; i < r; ++i)
+          fprintf(stderr, "|%02X|", rb[i]);
+        fprintf(stderr, "\n");
+      }
+      free(rb);
+    }
+    
+    return w;
#endif
}

@@ -764,6 +785,29 @@ int modbus_rtu_get_serial_mode(modbus_t
}
}

+int modbus_rtu_set_echohw_mode(modbus_t* ctx, uint8_t mode) {
+  if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
+    modbus_rtu_t* rtu = (modbus_rtu_t*) ctx->backend_data;
+    rtu->echohw= mode;
+    return 0;
+  }
+  /* Wrong backend and invalid mode specified */
+  errno = EINVAL;
+  return -1;
+
+}
+
+int modbus_rtu_get_echohw_mode(modbus_t* ctx) {
+  if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
+    modbus_rtu_t* rtu = (modbus_rtu_t*) ctx->backend_data;
+    return rtu->echohw;
+  }
+  /* Wrong backend and invalid mode specified */
+  errno = EINVAL;
+  return -1;
+
+}
+
void _modbus_rtu_close(modbus_t *ctx)
{
/* Closes the file descriptor in RTU mode */
@@ -912,6 +956,11 @@ modbus_t* modbus_new_rtu(const char *dev
}
ctx_rtu->data_bit = data_bit;
ctx_rtu->stop_bit = stop_bit;
+    
+#if HAVE_DECL_TIOCSRS485    
+    ctx_rtu->serial_mode = MODBUS_RTU_RS232;
+#endif    
+    ctx_rtu->echohw= MODBUS_RTU_NO_ECHOHW;

return ctx;
}
diff -rupN src-orig/modbus-rtu.h src/modbus-rtu.h
--- src-orig/modbus-rtu.h   2011-10-23 12:29:38.000000000 -0500
+++ src/modbus-rtu.h    2011-10-22 17:47:14.000000000 -0500
@@ -35,4 +35,10 @@ modbus_t* modbus_new_rtu(const char *dev
int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode);
int modbus_rtu_get_serial_mode(modbus_t *ctx);

+#define MODBUS_RTU_HAS_ECHOHW 1
+#define MODBUS_RTU_NO_ECHOHW 0
+
+int modbus_rtu_set_echohw_mode(modbus_t *ctx, uint8_t mode);
+int modbus_rtu_get_echohw_mode(modbus_t *ctx);
+
#endif /* _MODBUS_RTU_H_ */
diff -rupN src-orig/modbus-rtu-private.h src/modbus-rtu-private.h
--- src-orig/modbus-rtu-private.h   2011-10-23 12:29:38.000000000 -0500
+++ src/modbus-rtu-private.h    2011-10-22 17:47:14.000000000 -0500
@@ -81,6 +81,7 @@ typedef struct _modbus_rtu {
#if HAVE_DECL_TIOCSRS485
int serial_mode;
#endif
+    uint8_t echohw;
} modbus_rtu_t;

#endif /* _MODBUS_RTU_PRIVATE_H_ */
jaimealsilva commented 12 years ago

OK, now I've done it the right way: sent a pull request

x-o1d commented 7 years ago

Has this been merged? having a similar issue with local echo..

joshkel commented 7 years ago

In case it helps anyone else: Unless/until libmodbus deals with local echo directly, it's relatively easy to handle it yourself. For example, replace

  modbus_reply(context, query, rc, &mapping);

with

  int reply_length= modbus_reply(ctx, query, rc, &mapping);
  int serial_fd = modbus_get_socket(ctx);

  char buffer[reply_length];
  int remaining = reply_length;
  while (remaining) {
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(serial_fd, &fds);
    int result = select(serial_fd + 1, &fds, NULL, NULL, NULL);

    if (result < 0) {
      fprintf(stderr, "Read and discard select failed with error %i\n", errno);
      return;
    }

    result = read(serial_fd, buffer, remaining);
    if (result < 0) {
      fprintf(stderr, "Read and discard failed with error %i\n", errno);
      return;
    } else if (result == 0) {
      fprintf(stderr, "Read and discard failed: 0 bytes read\n");
      return;
    }
    remaining -= result;
  }

(This uses select - which is probably overkill, but it allows for plenty of flexibility if you want to change it - and is based on code I'm using with libmodbus 3.0.5, as shipped with Ubuntu 14.04.)