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 @@ -48,6 +48,7 @@ tests/random-test-server
tests/proxy-test-client
tests/proxy-test-server
tests/unit-test-client
tests/unit-test-rtu-slave-filter
tests/unit-test-server
tests/version
tests/stamp-h2
3 changes: 3 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ You can tweak the serial mode with the following functions:
- [modbus_rtu_get_rts_delay](modbus_rtu_get_rts_delay.md)
- [modbus_rtu_set_rts_delay](modbus_rtu_set_rts_delay.md)

To serve several slaves from a single RTU context, use
[modbus_rtu_set_slave_filter](modbus_rtu_set_slave_filter.md).

### TCP (IPv4) Context

The TCP backend implements a Modbus variant used for communications over
Expand Down
61 changes: 61 additions & 0 deletions docs/modbus_rtu_set_slave_filter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# modbus_rtu_set_slave_filter

## Name

modbus_rtu_set_slave_filter - set the unit identifiers accepted in RTU slave mode

## Synopsis

```c
int modbus_rtu_set_slave_filter(modbus_t *ctx, const uint8_t *slaves, unsigned int count);
```

## Description

The *modbus_rtu_set_slave_filter()* function shall configure the set of Modbus
unit identifiers accepted by an RTU context in slave mode. By default an RTU
context only handles indications addressed to the single slave set with
[modbus_set_slave](modbus_set_slave.md); a filter lets one context serve several
slaves on the same serial link, each typically backed by its own data mapping.

*slaves* points to an array of *count* unit identifiers to accept. The broadcast
address is always accepted regardless of the filter. Passing a NULL *slaves* or
a *count* of 0 clears the filter and restores the default single-slave
behaviour.

When a filter is set, an indication addressed to a unit identifier outside the
set is ignored (*modbus_receive()* returns 0) and the context keeps listening
for the next indication.

## Return value

The *modbus_rtu_set_slave_filter()* function shall return 0 if successful.
Otherwise it shall return -1 and set errno to EINVAL if *ctx* is not an RTU
context or if a unit identifier is out of range.

## Example

```c
ctx = modbus_new_rtu("/dev/ttyUSB0", 9600, 'N', 8, 1);

/* Serve unit identifiers 5, 6 and 7 from this context. */
uint8_t slaves[] = {5, 6, 7};
modbus_rtu_set_slave_filter(ctx, slaves, 3);

modbus_connect(ctx);

for (;;) {
uint8_t req[MODBUS_RTU_MAX_ADU_LENGTH];
int rc = modbus_receive(ctx, req);
if (rc > 0) {
modbus_reply(ctx, req, rc, mapping_for(req[0]));
}
}
```

## See also

- [modbus_set_slave](modbus_set_slave.md)
- [modbus_new_rtu](modbus_new_rtu.md)
- [modbus_receive](modbus_receive.md)
- [modbus_reply](modbus_reply.md)
6 changes: 6 additions & 0 deletions src/modbus-rtu-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ typedef struct _modbus_rtu {
#endif
/* To handle many slaves on the same link */
int confirmation_to_ignore;
/* Multi-slave mode: when slave_filter_active is set, an indication is
* accepted if its unit identifier has its bit set in slave_filter (a
* 256-bit bitmap). Otherwise only ctx->slave (and broadcast) is accepted,
* which preserves the historical single-slave behaviour. */
int slave_filter_active;
uint8_t slave_filter[32];
} modbus_rtu_t;

#endif /* MODBUS_RTU_PRIVATE_H */
73 changes: 69 additions & 4 deletions src/modbus-rtu.c
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,12 @@ static int _modbus_rtu_receive(modbus_t *ctx, uint8_t *req)
}
} else {
rc = _modbus_receive_msg(ctx, req, MSG_INDICATION);
if (rc == 0) {
/* The next expected message is a confirmation to ignore */
if (rc == 0 && !ctx_rtu->slave_filter_active) {
/* An indication for another slave was ignored: on a shared bus the
* addressed slave will answer, so the next message is a
* confirmation to ignore. A multi-slave server (slave filter set)
* is the authoritative responder for its unit ids and keeps
* listening for the next indication instead. */
ctx_rtu->confirmation_to_ignore = TRUE;
}
}
Expand Down Expand Up @@ -371,6 +375,20 @@ static int _modbus_rtu_pre_check_confirmation(modbus_t *ctx,
}
}

/* Returns TRUE if an indication addressed to unit identifier `slave` must be
handled by this context. With a slave filter set (multi-slave mode) the
decision is a bitmap lookup; otherwise only ctx->slave matches. Broadcast is
handled by the caller. */
static int _modbus_rtu_slave_accepted(modbus_t *ctx, int slave)
{
modbus_rtu_t *ctx_rtu = ctx->backend_data;

if (ctx_rtu->slave_filter_active) {
return (ctx_rtu->slave_filter[slave / 8] >> (slave % 8)) & 1;
}
return slave == ctx->slave;
}

/* The check_crc16 function shall return 0 if the message is ignored and the
message length if the CRC is valid. Otherwise it shall return -1 and set
errno to EMBBADCRC. */
Expand Down Expand Up @@ -400,9 +418,9 @@ static int _modbus_rtu_check_integrity(modbus_t *ctx, uint8_t *msg, const int ms
}

/* Filter on the Modbus unit identifier (slave) in RTU mode */
if (slave != ctx->slave && slave != MODBUS_BROADCAST_ADDRESS) {
if (slave != MODBUS_BROADCAST_ADDRESS && !_modbus_rtu_slave_accepted(ctx, slave)) {
if (ctx->debug) {
printf("Request for slave %d ignored (not %d)\n", slave, ctx->slave);
printf("Request for slave %d ignored\n", slave);
}
/* Following call to check_confirmation handles this error */
return 0;
Expand Down Expand Up @@ -1025,6 +1043,48 @@ int modbus_rtu_get_serial_mode(modbus_t *ctx)
}
}

int modbus_rtu_set_slave_filter(modbus_t *ctx,
const uint8_t *slaves,
unsigned int count)
{
modbus_rtu_t *ctx_rtu;
int max_slave;
unsigned int i;

if (ctx == NULL || ctx->backend->backend_type != _MODBUS_BACKEND_TYPE_RTU) {
errno = EINVAL;
return -1;
}

ctx_rtu = ctx->backend_data;

/* A NULL list or a count of 0 clears the filter and restores the default
behaviour (only ctx->slave and broadcast are accepted). */
if (slaves == NULL || count == 0) {
ctx_rtu->slave_filter_active = FALSE;
memset(ctx_rtu->slave_filter, 0, sizeof(ctx_rtu->slave_filter));
return 0;
}

/* Validate every id before mutating so a bad entry leaves the filter
unchanged. */
max_slave = (ctx->quirks & MODBUS_QUIRK_MAX_SLAVE) ? 255 : 247;
for (i = 0; i < count; i++) {
if (slaves[i] > max_slave) {
errno = EINVAL;
return -1;
}
}

memset(ctx_rtu->slave_filter, 0, sizeof(ctx_rtu->slave_filter));
for (i = 0; i < count; i++) {
ctx_rtu->slave_filter[slaves[i] / 8] |= (uint8_t) (1 << (slaves[i] % 8));
}
ctx_rtu->slave_filter_active = TRUE;

return 0;
}

int modbus_rtu_get_rts(modbus_t *ctx)
{
if (ctx == NULL) {
Expand Down Expand Up @@ -1395,5 +1455,10 @@ modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop

ctx_rtu->confirmation_to_ignore = FALSE;

/* No slave filter by default: only ctx->slave is accepted in slave mode
* (see modbus_rtu_set_slave_filter for multi-slave servers). */
ctx_rtu->slave_filter_active = FALSE;
memset(ctx_rtu->slave_filter, 0, sizeof(ctx_rtu->slave_filter));

return ctx;
}
3 changes: 3 additions & 0 deletions src/modbus-rtu.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop
MODBUS_API int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode);
MODBUS_API int modbus_rtu_get_serial_mode(modbus_t *ctx);

MODBUS_API int
modbus_rtu_set_slave_filter(modbus_t *ctx, const uint8_t *slaves, unsigned int count);

#define MODBUS_RTU_RTS_NONE 0
#define MODBUS_RTU_RTS_UP 1
#define MODBUS_RTU_RTS_DOWN 2
Expand Down
6 changes: 5 additions & 1 deletion tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ noinst_PROGRAMS = \
random-test-client \
unit-test-server \
unit-test-client \
unit-test-rtu-slave-filter \
proxy-test-server \
proxy-test-client \
version
Expand Down Expand Up @@ -36,6 +37,9 @@ unit_test_server_LDADD = $(common_ldflags)
unit_test_client_SOURCES = unit-test-client.c unit-test.h
unit_test_client_LDADD = $(common_ldflags)

unit_test_rtu_slave_filter_SOURCES = unit-test-rtu-slave-filter.c
unit_test_rtu_slave_filter_LDADD = $(common_ldflags)

proxy_test_server_SOURCES = proxy-test-server.c
proxy_test_server_LDADD = $(common_ldflags)

Expand All @@ -57,4 +61,4 @@ AM_CFLAGS = $(LIBMODBUSCFLAGS) $(WARNING_CFLAGS)
CLEANFILES = *~ *.log

noinst_SCRIPTS=unit-tests.sh
TESTS=./unit-tests.sh
TESTS=./unit-tests.sh unit-test-rtu-slave-filter
Loading