From f0db03dd4816b26cd55432aac820c61eeeccd96b Mon Sep 17 00:00:00 2001 From: Stéphane Raimbault Date: Tue, 4 Oct 2022 01:20:54 +0200 Subject: [PATCH] New quirks handler (closes #38 #533) --- docs/index.md | 5 +++++ docs/modbus_disable_quirks.md | 37 +++++++++++++++++++++++++++++++++++++ docs/modbus_enable_quirks.md | 35 +++++++++++++++++++++++++++++++++++ src/modbus-private.h | 1 + src/modbus-rtu.c | 4 +++- src/modbus-tcp.c | 4 +++- src/modbus.c | 33 ++++++++++++++++++++++++++++++--- src/modbus.h | 10 ++++++++++ tests/unit-test-client.c | 19 ++++++++++++++++++- 9 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 docs/modbus_disable_quirks.md create mode 100644 docs/modbus_enable_quirks.md diff --git a/docs/index.md b/docs/index.md index 230e0ff..cefa9ce 100644 --- a/docs/index.md +++ b/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 diff --git a/docs/modbus_disable_quirks.md b/docs/modbus_disable_quirks.md new file mode 100644 index 0000000..8942e66 --- /dev/null +++ b/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) diff --git a/docs/modbus_enable_quirks.md b/docs/modbus_enable_quirks.md new file mode 100644 index 0000000..c5b2619 --- /dev/null +++ b/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) diff --git a/src/modbus-private.h b/src/modbus-private.h index df2ca6d..b79658e 100644 --- a/src/modbus-private.h +++ b/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; diff --git a/src/modbus-rtu.c b/src/modbus-rtu.c index 38f6deb..6c37b48 100644 --- a/src/modbus-rtu.c +++ b/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; diff --git a/src/modbus-tcp.c b/src/modbus-tcp.c index 7210fb7..171cea6 100644 --- a/src/modbus-tcp.c +++ b/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 diff --git a/src/modbus.c b/src/modbus.c index 0dbf82b..e82411c 100644 --- a/src/modbus.c +++ b/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) { diff --git a/src/modbus.h b/src/modbus.h index 70a6c22..ee4ad76 100644 --- a/src/modbus.h +++ b/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 diff --git a/tests/unit-test-client.c b/tests/unit-test-client.c index fdc42ae..f8c4ab8 100644 --- a/tests/unit-test-client.c +++ b/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, -- libgit2 0.21.4