Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ tests/proxy-test-client
tests/proxy-test-server
tests/unit-test-client
tests/unit-test-server
tests/unit-test-transport
tests/version
tests/stamp-h2
5 changes: 5 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ Setter/getter of internal socket:
- [modbus_set_socket](modbus_set_socket.md)
- [modbus_get_socket](modbus_get_socket.md)

Pluggable I/O transport:

- [modbus_set_transport](modbus_set_transport.md)
- [modbus_get_transport](modbus_get_transport.md)

Information about header:

- [modbus_get_header_length](modbus_get_header_length.md)
Expand Down
25 changes: 25 additions & 0 deletions docs/modbus_get_transport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# modbus_get_transport

## Name

modbus_get_transport - get the registered I/O transport

## Synopsis

```c
modbus_transport_t *modbus_get_transport(modbus_t *ctx);
```

## Description

The *modbus_get_transport()* function shall return the pluggable I/O transport
registered on the libmodbus context *ctx* with *modbus_set_transport()*, or NULL
if no transport is set.

## Return value

The function shall return the registered transport, or NULL if none is set.

## See also

- [modbus_set_transport](modbus_set_transport.md)
125 changes: 125 additions & 0 deletions docs/modbus_set_transport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# modbus_set_transport

## Name

modbus_set_transport - register a pluggable I/O transport

## Synopsis

```c
int modbus_set_transport(modbus_t *ctx, modbus_transport_t *transport);
```

## Description

The *modbus_set_transport()* function shall register a custom I/O *transport* on
the libmodbus context *ctx*. The transport replaces the low-level send, recv,
select, connect, close and flush calls while leaving the Modbus framing, CRC and
protocol logic unchanged. It is useful to route Modbus over a custom or
userspace IP stack, to drive a loopback for simulation, or to instrument the I/O
path in tests.

This function must be called after *modbus_new_tcp()* or *modbus_new_rtu()* and
before *modbus_connect()*. The context takes ownership of the transport:
*modbus_free()* shall call its *free()* member if non-NULL. Passing NULL detaches
a previously registered transport without calling *free()*, so a transport may be
stack-allocated or shared.

The transport is described by the *modbus_transport_t* structure. Every function
pointer is optional; a NULL member falls back to the default backend behaviour
for that operation.

```c
typedef struct modbus_transport {
int (*connect)(struct modbus_transport *t);
ssize_t (*send)(struct modbus_transport *t, const uint8_t *buf, int len);
ssize_t (*recv)(struct modbus_transport *t, uint8_t *buf, int len);
int (*select)(struct modbus_transport *t, struct timeval *tv);
int (*flush)(struct modbus_transport *t);
void (*close)(struct modbus_transport *t);
void (*free)(struct modbus_transport *t);
void *priv; /* private transport state, not touched by libmodbus */
int connected; /* managed by modbus_connect()/modbus_close() */
} modbus_transport_t;
```

The *connect()* member is called by *modbus_connect()*, *send()* transmits a
fully framed ADU, *recv()* reads up to *len* bytes and returns the number read or
0 when the peer closes, *select()* waits until data can be read or the timeout
*tv* expires (NULL waits indefinitely) and returns a positive value when data is
available or 0 on timeout, *flush()* discards pending input, *close()* tears the
connection down without freeing the struct, and *free()* releases all resources.
On error, members return -1 and set errno. With *MODBUS_ERROR_RECOVERY_LINK* (see
[modbus_set_error_recovery](modbus_set_error_recovery.md)), that errno decides
the recovery on every platform: errors such as ECONNRESET or EBADF mean the link
is lost, so libmodbus calls *close()* and *connect()* before retrying.
Connection parameters such as the address and port must be stored in *priv*
before the transport is registered, since the members receive only the
transport pointer.

## Return value

The *modbus_set_transport()* function shall return 0 if successful. Otherwise it
shall return -1 and set errno to EINVAL if *ctx* is NULL.

## Example

```c
typedef struct {
mystack_conn_t *conn;
} my_priv_t;

static int my_connect(modbus_transport_t *t)
{
my_priv_t *p = t->priv;
p->conn = mystack_connect("192.168.1.10", 502);
return p->conn ? 0 : -1;
}

static ssize_t my_send(modbus_transport_t *t, const uint8_t *buf, int len)
{
return mystack_send(((my_priv_t *) t->priv)->conn, buf, len);
}

static ssize_t my_recv(modbus_transport_t *t, uint8_t *buf, int len)
{
return mystack_recv(((my_priv_t *) t->priv)->conn, buf, len);
}

static int my_select(modbus_transport_t *t, struct timeval *tv)
{
return mystack_wait_rx(((my_priv_t *) t->priv)->conn, tv);
}

static void my_close(modbus_transport_t *t)
{
mystack_close(((my_priv_t *) t->priv)->conn);
}

my_priv_t priv = {0};
modbus_transport_t tr = {
.connect = my_connect,
.send = my_send,
.recv = my_recv,
.select = my_select,
.close = my_close,
.priv = &priv,
};

ctx = modbus_new_tcp("192.168.1.10", 502);
modbus_set_transport(ctx, &tr);
modbus_connect(ctx);

modbus_read_registers(ctx, 0, 10, regs);

modbus_close(ctx);
modbus_free(ctx);
```

## See also

- [modbus_get_transport](modbus_get_transport.md)
- [modbus_new_tcp](modbus_new_tcp.md)
- [modbus_new_rtu](modbus_new_rtu.md)
- [modbus_connect](modbus_connect.md)
- [modbus_set_socket](modbus_set_socket.md)
3 changes: 2 additions & 1 deletion src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ libmodbus_la_SOURCES = \
modbus-tcp.c \
modbus-tcp.h \
modbus-tcp-private.h \
modbus-transport.h \
modbus-version.h

libmodbus_la_LDFLAGS = -no-undefined \
Expand All @@ -35,7 +36,7 @@ endif

# Header files to install
libmodbusincludedir = $(includedir)/modbus
libmodbusinclude_HEADERS = modbus.h modbus-version.h modbus-rtu.h modbus-tcp.h
libmodbusinclude_HEADERS = modbus.h modbus-version.h modbus-rtu.h modbus-tcp.h modbus-transport.h

DISTCLEANFILES = modbus-version.h
EXTRA_DIST += modbus-version.h.in
Expand Down
4 changes: 4 additions & 0 deletions src/modbus-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ typedef int ssize_t;
#include <sys/types.h>

#include "modbus.h"
#include "modbus-transport.h"

MODBUS_BEGIN_DECLS

Expand Down Expand Up @@ -106,6 +107,9 @@ struct _modbus {
struct timeval indication_timeout;
const modbus_backend_t *backend;
void *backend_data;
/* Optional pluggable I/O transport (see modbus-transport.h).
* NULL means use the default backend I/O path. */
modbus_transport_t *transport;
};

void _modbus_init_common(modbus_t *ctx);
Expand Down
48 changes: 48 additions & 0 deletions src/modbus-transport.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/

#ifndef MODBUS_TRANSPORT_H
#define MODBUS_TRANSPORT_H

/* clang-format off */
#ifndef _MSC_VER
# include <sys/time.h>
# include <sys/types.h>
# include <stdint.h>
#else
# include "stdint.h"
# include <time.h>
typedef int ssize_t;
#endif
/* clang-format on */

#include "modbus.h"

MODBUS_BEGIN_DECLS

/* Pluggable I/O transport: replaces the low-level send, recv, select, connect,
* close and flush calls while leaving the Modbus framing, CRC and protocol
* logic unchanged. Every function pointer is optional; a NULL pointer falls
* back to the default backend behaviour for that operation. See
* modbus_set_transport(3). */
typedef struct modbus_transport {
int (*connect)(struct modbus_transport *t);
ssize_t (*send)(struct modbus_transport *t, const uint8_t *buf, int len);
ssize_t (*recv)(struct modbus_transport *t, uint8_t *buf, int len);
int (*select)(struct modbus_transport *t, struct timeval *tv);
int (*flush)(struct modbus_transport *t);
void (*close)(struct modbus_transport *t);
void (*free)(struct modbus_transport *t);
void *priv; /* Private transport state, not touched by libmodbus */
int connected; /* Managed by modbus_connect()/modbus_close() */
} modbus_transport_t;

MODBUS_API int modbus_set_transport(modbus_t *ctx, modbus_transport_t *transport);
MODBUS_API modbus_transport_t *modbus_get_transport(modbus_t *ctx);

MODBUS_END_DECLS

#endif /* MODBUS_TRANSPORT_H */
Loading