libmodbus icon indicating copy to clipboard operation
libmodbus copied to clipboard

modbus_receive_confirmation ignore length for user function codes

Open catompiler opened this issue 9 years ago • 5 comments

libmodbus version

3.1.4

Operating system

Windows 7

Description of the Modbus network (server, client, links, etc)

Modbus RTU client

Expected behavior

Read a full answer from user function on my device.

Actual behavior

Read only first bytes (minimal length).

Steps to reproduce the behavior (commands or source code)

int res;
uint8_t req[] = { 123 /* address */, 65 /* user function */, ... /* data */};
res = modbus_send_raw_request(modbus_ctx, req, req_size);
/* res check skipped */
uint8_t buf[MODBUS_RTU_MAX_ADU_LENGTH];
res = modbus_receive_raw_confirmation(modbus_ctx, buf);
/* res == -1 invalid checksum because the answer is not read to the end */

libmodbus output with debug mode enabled

None

Fix

I fixed it by adding this code in modbus.c (and prototype in modbus.h):

/* Waits a raw response from a modbus server or a request from a modbus client.
   This function blocks if there is no replies (3 timeouts).

   The function shall return the number of received characters and the received
   message in an array of uint8_t if successful. Otherwise it shall return -1
   and errno is set to one of the values defined below:
   - ECONNRESET
   - EMBBADDATA
   - EMBUNKEXC
   - ETIMEDOUT
   - read() or recv() error codes
*/

int _modbus_receive_raw_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type)
{
    int rc = 0;
    fd_set rset;
    struct timeval tv;
    struct timeval *p_tv;
    int length_to_read;
    int msg_length = 0;
    _step_t step = _STEP_FUNCTION;

    if (ctx->debug) {
        if (msg_type == MSG_INDICATION) {
            printf("Waiting for a raw indication...\n");
        } else {
            printf("Waiting for a raw confirmation...\n");
        }
    }

    /* Add a file descriptor to the set */
    FD_ZERO(&rset);
    FD_SET(ctx->s, &rset);

    length_to_read = ctx->backend->header_length + 1;

    if (msg_type == MSG_INDICATION) {
        /* Wait for a message, we don't know when the message will be
         * received */
        p_tv = NULL;
    } else {
        tv.tv_sec = ctx->response_timeout.tv_sec;
        tv.tv_usec = ctx->response_timeout.tv_usec;
        p_tv = &tv;
    }

    while (rc != -1) {
        rc = ctx->backend->select(ctx, &rset, p_tv, length_to_read);
        if (rc == -1) {
            /* Timeout at end of msg */
            if(step == _STEP_DATA && errno == ETIMEDOUT){
                break;
            }
            _error_print(ctx, "select");
            if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) {
                int saved_errno = errno;

                if (errno == ETIMEDOUT) {
                    _sleep_response_timeout(ctx);
                    modbus_flush(ctx);
                } else if (errno == EBADF) {
                    modbus_close(ctx);
                    modbus_connect(ctx);
                }
                errno = saved_errno;
            }
            return -1;
        }

        rc = ctx->backend->recv(ctx, msg + msg_length, length_to_read);
        if (rc == 0) {
            errno = ECONNRESET;
            rc = -1;
        }

        if (rc == -1) {
            _error_print(ctx, "read");
            if ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) &&
                (errno == ECONNRESET || errno == ECONNREFUSED ||
                 errno == EBADF)) {
                int saved_errno = errno;
                modbus_close(ctx);
                modbus_connect(ctx);
                /* Could be removed by previous calls */
                errno = saved_errno;
            }
            return -1;
        }

        /* Display the hex code of each character received */
        if (ctx->debug) {
            int i;
            for (i=0; i < rc; i++)
                printf("<%.2X>", msg[msg_length + i]);
        }

        /* Sums bytes received */
        msg_length += rc;

        if(step == _STEP_FUNCTION){
            /* Set byte timeout */
            if (ctx->byte_timeout.tv_sec > 0 || ctx->byte_timeout.tv_usec > 0) {
                tv.tv_sec = ctx->byte_timeout.tv_sec;
                tv.tv_usec = ctx->byte_timeout.tv_usec;
                p_tv = &tv;
            }
            /* Read one byte */
            length_to_read = 1;
            /* Data step */
            step = _STEP_DATA;
        }
    }

    if (ctx->debug)
        printf("\n");

    return ctx->backend->check_integrity(ctx, msg, msg_length);
}

/* Receives the raw confirmation.

   The function shall store the read response in rsp and return the number of
   values (bits or words). Otherwise, its shall return -1 and errno is set.

   The function doesn't check the confirmation is the expected response to the
   initial request.
*/
int modbus_receive_raw_confirmation(modbus_t *ctx, uint8_t *rsp)
{
    if (ctx == NULL) {
        errno = EINVAL;
        return -1;
    }

    return _modbus_receive_raw_msg(ctx, rsp, MSG_CONFIRMATION);
}

catompiler avatar Jul 06 '16 11:07 catompiler

Very related to https://github.com/stephane/libmodbus/issues/231

karlp avatar Apr 11 '17 16:04 karlp

Yes, but these changes are based on the Modbus frame format (time-out between bytes / messages), not on the data of the user function.

catompiler avatar Apr 12 '17 04:04 catompiler

Yes, but libmodbus doesn't use t3.5 and t1.5 timeouts anyway, for any messages, let alone user messages.

karlp avatar Apr 12 '17 08:04 karlp

Thank you for this, exactly what I was looking for. Added your functions to my library and they work well!

KsaweryRettinger avatar Jul 30 '19 11:07 KsaweryRettinger

Thanks for the code. Works well. Why it is not merged into master branch is a mystery for me. Although one must be aware that this solution works with a timeout and is as such very slow (read not usable in realtime eventloop).

cminnoy avatar Dec 06 '24 08:12 cminnoy