Problems Understanding Library Struct

Hello everyone,
hope you're all doing as good as possible during these times...

I'm using the library ArduinoModbus to communicate with an Accelerometer.

I've used it before with another sensor and it worked without any problem. However, the response is quite different now and given that I'm getting an error message I'm debugging it and reached a problem that I can't understand what is going on.

Problem

While debugging, I found that the problem is with an if condition that returns -1:
image

And when I went to check the pre_check_confirmation I found that it is a struct item and that is it... I couldn't find what it defines. Looks like a function, but I don't know what happens within the ctx->backend->pre_check_confirmation or ctx->backend->pre_check_confirmation(ctx, req, rsp, rsp_length).

typedef struct _modbus_backend {
    unsigned int backend_type;
    unsigned int header_length;
    unsigned int checksum_length;
    unsigned int max_adu_length;
    int (*set_slave) (modbus_t *ctx, int slave);
    int (*build_request_basis) (modbus_t *ctx, int function, int addr,
                                int nb, uint8_t *req);
    int (*build_response_basis) (sft_t *sft, uint8_t *rsp);
    int (*prepare_response_tid) (const uint8_t *req, int *req_length);
    int (*send_msg_pre) (uint8_t *req, int req_length);
    ssize_t (*send) (modbus_t *ctx, const uint8_t *req, int req_length);
    int (*receive) (modbus_t *ctx, uint8_t *req);
    ssize_t (*recv) (modbus_t *ctx, uint8_t *rsp, int rsp_length);
    int (*check_integrity) (modbus_t *ctx, uint8_t *msg,
                            const int msg_length);
    int (*pre_check_confirmation) (modbus_t *ctx, const uint8_t *req,
                                   const uint8_t *rsp, int rsp_length);
    int (*connect) (modbus_t *ctx);
    void (*close) (modbus_t *ctx);
    int (*flush) (modbus_t *ctx);
    int (*select) (modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length);
    void (*free) (modbus_t *ctx);
} modbus_backend_t;

struct _modbus {
    /* Slave address */
    int slave;
    /* Socket or file descriptor */
    int s;
    int debug;
    int error_recovery;
    struct timeval response_timeout;
    struct timeval byte_timeout;
    const modbus_backend_t *backend;
    void *backend_data;
};

In the struct above, we can see the int (*pre_check_confirmation) (modbus_t *ctx, const uint8_t *req, const uint8_t *rsp, int rsp_length); but as shown in the picture below, there is no definition of what happens here... I'm missing something.

image

Arduino Monitor

Reading Input Register values ... 010300020004E5C9 
Received: 010309905F01925F017963010EBA 
Restante Length: 0 
offset: 1 
Function: 3 
rc==-1

failed! Invalid data

Packages

Here, I show you the packages sent and received that I tested here and also the datasheet example. With this, at least in my opinion, it is pretty clear that the communication is working.

Real

Send: 01 03 00 02 00 04 E5 C9
Received: 01 03 09 91 5F 01 94 5F 01 78 63 01 52 8C

Datasheet

image

I think someone got their lines out of order. It should be checking the condition before reporting the condition.

    println("rc==-1");
    if (rc == -1) {

should probably have been:

    if (rc == -1) {
      println("rc==-1");

In other words, the sketch is printing "rc==-1" regardless of whether 'rc' was -1 or not. That could lead you to believe that 'rc' was -1 when it wasn't.

@johnwasser, you're totally right. The actual problem is in the length.

Reading Input Register values ... 01 03 00 02 00 04 E5 C9 
Received: 01 03 09 90 5F 01 92 5F 01 79 63 01 0E BA 
calculated length based on request: 13
real length: 14

In the request, it asks for 4 registers: 01030002 >0004< E5C9 and the calculation is made like this: 2+2*requested_registers+offset+ crc_size = 2+2*4+1+2 = 13. and the sensor replied 14 bytes.

I check with the other Modbus device that I have and the equation works like a charm.
image

But this is getting out of topic, given that this is a programming question. Going back to it, even though the rc==-1 isn't the problem anymore, I still don't understand how ithis struct works... Could you help me?

Yes. After the first four bytes there are a bunch of function pointers. The code that says this:

    if (ctx->backend->pre_check_confirmation) {
      rc = ctx->backend->pre_check_confirmation(ctx, req, rsp, rsp_length);

Means: If the function pointer in the struct is not null (zero), call the function to do stuff. Look for the code that sets the function pointers to see what function each points to. The pointer are either set in the declaration of the 'backend' struct as an initilizer list (one initializer for each entry in the struct) or are set explicitly later, like:
ctx->backend->pre_check_confirmation = myPreCheck;

While digging more after what you said I followed this path:

As mentioned in post zero, the struct _modbus has modbus_backend_t in it. So I went to look for _modbus usage and found typedef struct _modbus modbus_t; that lead me to:

int ModbusRTUClientClass::begin(unsigned long baudrate, uint16_t config)
{
  modbus_t* mb = modbus_new_rtu(_rs485, baudrate, config);

  if (!ModbusClient::begin(mb, 0x00)) {
    return 0;
  }

  return 1;
}

that goes to:

#ifdef ARDUINO
modbus_t* modbus_new_rtu(RS485Class *rs485, unsigned long baud, uint16_t config)
#else
modbus_t* modbus_new_rtu(const char *device,
                         int baud, char parity, int data_bit,
                         int stop_bit)
#endif
{
    modbus_t *ctx;
    modbus_rtu_t *ctx_rtu;

#ifndef ARDUINO
    /* Check device argument */
    if (device == NULL || *device == 0) {
        fprintf(stderr, "The device string is empty\n");
        errno = EINVAL;
        return NULL;
    }

    /* Check baud argument */
    if (baud == 0) {
        fprintf(stderr, "The baud rate value must not be zero\n");
        errno = EINVAL;
        return NULL;
    }
#endif

    ctx = (modbus_t *)malloc(sizeof(modbus_t));
    _modbus_init_common(ctx);
    ctx->backend = &_modbus_rtu_backend;
    ctx->backend_data = (modbus_rtu_t *)malloc(sizeof(modbus_rtu_t));
    ctx_rtu = (modbus_rtu_t *)ctx->backend_data;
#ifdef ARDUINO
    ctx_rtu->rs485 = rs485;
    ctx_rtu->baud = baud;
    ctx_rtu->config = config;
#else
    ctx_rtu->device = NULL;

    /* Device name and \0 */
    ctx_rtu->device = (char *)malloc((strlen(device) + 1) * sizeof(char));
    strcpy(ctx_rtu->device, device);

    ctx_rtu->baud = baud;
    if (parity == 'N' || parity == 'E' || parity == 'O') {
        ctx_rtu->parity = parity;
    } else {
        modbus_free(ctx);
        errno = EINVAL;
        return NULL;
    }
    ctx_rtu->data_bit = data_bit;
    ctx_rtu->stop_bit = stop_bit;

#if HAVE_DECL_TIOCSRS485
    /* The RS232 mode has been set by default */
    ctx_rtu->serial_mode = MODBUS_RTU_RS232;
#endif

#if HAVE_DECL_TIOCM_RTS
    /* The RTS use has been set by default */
    ctx_rtu->rts = MODBUS_RTU_RTS_NONE;

    /* Calculate estimated time in micro second to send one byte */
    ctx_rtu->onebyte_time = 1000000 * (1 + data_bit + (parity == 'N' ? 0 : 1) + stop_bit) / baud;

    /* The internal function is used by default to set RTS */
    ctx_rtu->set_rts = _modbus_rtu_ioctl_rts;

    /* The delay before and after transmission when toggling the RTS pin */
    ctx_rtu->rts_delay = ctx_rtu->onebyte_time;
#endif
#endif

    ctx_rtu->confirmation_to_ignore = FALSE;

    return ctx;
}

anddddd finally: ctx->backend = &_modbus_rtu_backend;

const modbus_backend_t _modbus_rtu_backend = {
    _MODBUS_BACKEND_TYPE_RTU,
    _MODBUS_RTU_HEADER_LENGTH,
    _MODBUS_RTU_CHECKSUM_LENGTH,
    MODBUS_RTU_MAX_ADU_LENGTH,
    _modbus_set_slave,
    _modbus_rtu_build_request_basis,
    _modbus_rtu_build_response_basis,
    _modbus_rtu_prepare_response_tid,
    _modbus_rtu_send_msg_pre,
    _modbus_rtu_send,
    _modbus_rtu_receive,
    _modbus_rtu_recv,
    _modbus_rtu_check_integrity,
    _modbus_rtu_pre_check_confirmation,
    _modbus_rtu_connect,
    _modbus_rtu_close,
    _modbus_rtu_flush,
    _modbus_rtu_select,
    _modbus_rtu_free
};

(Actually, I should have said finally now...)

static int _modbus_rtu_pre_check_confirmation(modbus_t *ctx, const uint8_t *req,
                                              const uint8_t *rsp, int rsp_length)
{
#ifdef ARDUINO
    (void)rsp_length;
#endif
    /* Check responding slave is the slave we requested (except for broacast
     * request) */
    if (req[0] != rsp[0] && req[0] != MODBUS_BROADCAST_ADDRESS) {
        if (ctx->debug) {
            fprintf(stderr,
                    "The responding slave %d isn't the requested slave %d\n",
                    rsp[0], req[0]);
        }
        errno = EMBBADSLAVE;
        return -1;
    } else {
        return 0;
    }
}

Thanks @johnwasser!

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.