Browse Source

New quirks handler (closes #38 #533)

Useful functions when you are confronted with equipment which does
not respect the protocol, which behaves strangely or when you wish
to move away from the standard.

Thank you @mhei for the great initial version.
Stéphane Raimbault 2 years ago
parent
commit
f0db03dd48
9 changed files with 142 additions and 6 deletions
  1. 5 0
      docs/index.md
  2. 37 0
      docs/modbus_disable_quirks.md
  3. 35 0
      docs/modbus_enable_quirks.md
  4. 1 0
      src/modbus-private.h
  5. 3 1
      src/modbus-rtu.c
  6. 3 1
      src/modbus-tcp.c
  7. 30 3
      src/modbus.c
  8. 10 0
      src/modbus.h
  9. 18 1
      tests/unit-test-client.c

+ 5 - 0
docs/index.md

@@ -251,6 +251,11 @@ error codes into error message strings; for details refer to
 
 ## Miscellaneous
 
+To deviate from the Modbus standard, you can enable or disable quirks with:
+
+- [modbus_disable_quirks](modbus_disable_quirks.md)
+- [modbus_enable_quirks](modbus_enable_quirks.md)
+
 The `_LIBMODBUS_VERSION_STRING_` constant indicates the libmodbus version the
 program has been compiled against. The variables 'libmodbus_version_major',
 'libmodbus_version_minor', 'libmodbus_version_micro' give the version the

+ 37 - 0
docs/modbus_disable_quirks.md

@@ -0,0 +1,37 @@
+# modbus_disable_quirks
+
+## Name
+
+modbus_disable_quirks - disable a list of quirks according to a mask
+
+## Synopsis
+
+```c
+int modbus_disable_quirks(modbus_t *ctx, unsigned int quirks_mask);
+```
+
+## Description
+
+The function shall disable the quirks according to the provided mask. It's
+useful to revert changes applied by a previous call to
+[modbus_enable_quirks](modbus_enable_quirks.md)
+
+To reset all quirks, you can use the specific value `MODBUS_QUIRK_ALL`.
+
+```c
+modbus_enable_quirks(ctx, MODBUS_QUIRK_MAX_SLAVE | MODBUS_QUIRK_REPLY_TO_BROADCAST);
+
+...
+
+// Reset all quirks
+modbus_disable_quirks(ctx, MODBUS_QUIRK_ALL);
+```
+
+## Return value
+
+The function shall return 0 if successful. Otherwise it shall return -1 and set
+errno.
+
+## See also
+
+- [modbus_enable_quirks](modbus_enable_quirks.md)

+ 35 - 0
docs/modbus_enable_quirks.md

@@ -0,0 +1,35 @@
+# modbus_enable_quirks
+
+## Name
+
+modbus_enable_quirks - enable a list of quirks according to a mask
+
+## Synopsis
+
+```c
+int modbus_enable_quirks(modbus_t *ctx, unsigned int quirks_mask);
+```
+
+## Description
+
+The function is only useful when you are confronted with equipment which does
+not respect the protocol, which behaves strangely or when you wish to move away
+from the standard.
+
+In that case, you can enable a specific quirk to workaround the issue, libmodbus
+offers the following flags:
+
+- `MODBUS_QUIRK_MAX_SLAVE` allows slave adresses between 247 and 255.
+- `MODBUS_QUIRK_REPLY_TO_BROADCAST` force a reply to a broacast request when the
+  device is a slave in RTU mode (should be enabled on the slave device).
+
+You can combine the flags by using the OR logical operator.
+
+## Return value
+
+The function shall return 0 if successful. Otherwise it shall return -1 and set
+errno.
+
+## See also
+
+- [modbus_disable_quirks](modbus_disable_quirks.md)

+ 1 - 0
src/modbus-private.h

@@ -96,6 +96,7 @@ struct _modbus {
     int s;
     int debug;
     int error_recovery;
+    int quirks;
     struct timeval response_timeout;
     struct timeval byte_timeout;
     struct timeval indication_timeout;

+ 3 - 1
src/modbus-rtu.c

@@ -91,8 +91,10 @@ static const uint8_t table_crc_lo[] = {
  * internal slave ID in slave mode */
 static int _modbus_set_slave(modbus_t *ctx, int slave)
 {
+    int max_slave = (ctx->quirks & MODBUS_QUIRK_MAX_SLAVE) ? 255 : 247;
+
     /* Broadcast address is 0 (MODBUS_BROADCAST_ADDRESS) */
-    if (slave >= 0 && slave <= 247) {
+    if (slave >= 0 && slave <= max_slave) {
         ctx->slave = slave;
     } else {
         errno = EINVAL;

+ 3 - 1
src/modbus-tcp.c

@@ -76,8 +76,10 @@ static int _modbus_tcp_init_win32(void)
 
 static int _modbus_set_slave(modbus_t *ctx, int slave)
 {
+    int max_slave = (ctx->quirks & MODBUS_QUIRK_MAX_SLAVE) ? 255 : 247;
+
     /* Broadcast address is 0 (MODBUS_BROADCAST_ADDRESS) */
-    if (slave >= 0 && slave <= 247) {
+    if (slave >= 0 && slave <= max_slave) {
         ctx->slave = slave;
     } else if (slave == MODBUS_TCP_SLAVE) {
         /* The special value MODBUS_TCP_SLAVE (0xFF) can be used in TCP mode to

+ 30 - 3
src/modbus.c

@@ -1063,9 +1063,13 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req,
         break;
     }
 
-    /* Suppress any responses when the request was a broadcast */
-    return (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU &&
-            slave == MODBUS_BROADCAST_ADDRESS) ? 0 : send_msg(ctx, rsp, rsp_length);
+    /* Suppress any responses in RTU when the request was a broadcast, excepted when quirk is enabled. */
+    if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU &&
+        slave == MODBUS_BROADCAST_ADDRESS &&
+        !(ctx->quirks & MODBUS_QUIRK_REPLY_TO_BROADCAST)) {
+        return 0;
+    }
+    return send_msg(ctx, rsp, rsp_length);
 }
 
 int modbus_reply_exception(modbus_t *ctx, const uint8_t *req,
@@ -1635,6 +1639,7 @@ void _modbus_init_common(modbus_t *ctx)
 
     ctx->debug = FALSE;
     ctx->error_recovery = MODBUS_ERROR_RECOVERY_NONE;
+    ctx->quirks = MODBUS_QUIRK_NONE;
 
     ctx->response_timeout.tv_sec = 0;
     ctx->response_timeout.tv_usec = _RESPONSE_TIMEOUT;
@@ -1789,6 +1794,28 @@ int modbus_get_header_length(modbus_t *ctx)
     return ctx->backend->header_length;
 }
 
+int modbus_enable_quirks(modbus_t *ctx, uint32_t quirks_mask) {
+    if (ctx == NULL) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    /* Enable quirks that have a true value at their index in the mask */
+    ctx->quirks |= quirks_mask;
+    return 0;
+}
+
+int modbus_disable_quirks(modbus_t *ctx, uint32_t quirks_mask) {
+    if (ctx == NULL) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    /* Disable quirks that have a true value at ther index in the mask */
+    ctx->quirks &= ~quirks_mask;
+    return 0;
+}
+
 int modbus_connect(modbus_t *ctx)
 {
     if (ctx == NULL) {

+ 10 - 0
src/modbus.h

@@ -176,6 +176,14 @@ typedef enum
     MODBUS_ERROR_RECOVERY_PROTOCOL      = (1<<2)
 } modbus_error_recovery_mode;
 
+typedef enum
+{
+    MODBUS_QUIRK_NONE                   = 0,
+    MODBUS_QUIRK_MAX_SLAVE              = (1<<1),
+    MODBUS_QUIRK_REPLY_TO_BROADCAST     = (1<<2),
+    MODBUS_QUIRK_ALL                    = 0xFF
+} modbus_quirks;
+
 MODBUS_API int modbus_set_slave(modbus_t* ctx, int slave);
 MODBUS_API int modbus_get_slave(modbus_t* ctx);
 MODBUS_API int modbus_set_error_recovery(modbus_t *ctx, modbus_error_recovery_mode error_recovery);
@@ -237,6 +245,8 @@ MODBUS_API int modbus_reply(modbus_t *ctx, const uint8_t *req,
                             int req_length, modbus_mapping_t *mb_mapping);
 MODBUS_API int modbus_reply_exception(modbus_t *ctx, const uint8_t *req,
                                       unsigned int exception_code);
+MODBUS_API int modbus_enable_quirks(modbus_t *ctx, unsigned int quirks_mask);
+MODBUS_API int modbus_disable_quirks(modbus_t *ctx, unsigned int quirks_mask);
 
 /**
  * UTILS FUNCTIONS

+ 18 - 1
tests/unit-test-client.c

@@ -451,9 +451,26 @@ int main(int argc, char *argv[])
     printf("* modbus_write_registers: ");
     ASSERT_TRUE(rc == -1 && errno == EMBMDATA, "");
 
-    /** SLAVE REPLY **/
+    /** SLAVE ADDRESS **/
     old_slave = modbus_get_slave(ctx);
 
+    printf("\nTEST SLAVE ADDRESS:\n");
+
+    printf("1/2 Not compliant slave address is refused: ");
+    rc = modbus_set_slave(ctx, 248);
+    ASSERT_TRUE(rc == -1, "Slave address of 248 shouldn't be allowed");
+
+    printf("2/2 Not compliant slave address is allowed: ");
+    modbus_enable_quirks(ctx, MODBUS_QUIRK_MAX_SLAVE);
+    rc = modbus_set_slave(ctx, 248);
+    ASSERT_TRUE(rc == 0, "Not compliant slave address should have been accepted");
+
+    modbus_disable_quirks(ctx, MODBUS_QUIRK_MAX_SLAVE);
+    rc = modbus_set_slave(ctx, old_slave);
+    ASSERT_TRUE(rc == 0, "Uanble to restore slave value")
+
+    /** SLAVE REPLY **/
+
     printf("\nTEST SLAVE REPLY:\n");
     modbus_set_slave(ctx, INVALID_SERVER_ID);
     rc = modbus_read_registers(ctx, UT_REGISTERS_ADDRESS,