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
3 changes: 2 additions & 1 deletion doc/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ TXT3 = \
modbus_get_float_dcba.txt \
modbus_get_header_length.txt \
modbus_get_response_timeout.txt \
modbus_get_slave.txt \
modbus_get_slave.txt \
modbus_get_socket.txt \
modbus_mapping_free.txt \
modbus_mapping_new.txt \
Expand All @@ -29,6 +29,7 @@ TXT3 = \
modbus_receive.txt \
modbus_reply_exception.txt \
modbus_reply.txt \
modbus_reply_callback.txt \
modbus_report_slave_id.txt \
modbus_rtu_get_serial_mode.txt \
modbus_rtu_set_serial_mode.txt \
Expand Down
1 change: 1 addition & 0 deletions doc/modbus_reply.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ or write).

SEE ALSO
--------
linkmb:modbus_reply_callback[3]
linkmb:modbus_reply_exception[3]
linkmb:libmodbus[7]

Expand Down
177 changes: 177 additions & 0 deletions doc/modbus_reply_callback.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
modbus_reply_callback(3)
========================

NAME
----
modbus_reply_callback - reply to a modbus-request by invoking a user-callback.

SYNOPSIS
--------
*int modbus_reply_callback(modbus_t *'ctx', const uint8_t *'request', int 'req_length');*

*int modbus_set_reply_callbacks(modbus_t *'ctx', modbus_reply_callbacks_t *'cb', void *'user_ctx');*

[source,c]
----------
typedef struct {
int (*accept_rtu_slave)(void *user_ctx, int slave);
int (*verify)(void *user_ctx, int slave, int function, uint16_t address, int nb);
int (*read)(void *user_ctx, int slave, int function, uint16_t address, int nb, uint8_t bytes[], int len);
int (*write)(void *user_ctx, int slave, int function, uint16_t address, int nb, const uint8_t bytes[]);
} modbus_reply_callbacks_t;
----------

DESCRIPTION
-----------
*modbus_set_reply_callbacks()* has to be used to set the callback-structure into the
libmodbus-context. An optional parameter _user_ctx_ can be suppied which will be passed to the
callback function, typically for context purpose.

The *modbus_reply_callback()* function receives the request-byte-buffer and its byte-length as
filled in and returned by *modbus_receive()*. It decodes the modbus-request and calls
function-callbacks set by using *modbus_set_reply_callbacks()*.

Compared to *modbus_reply()* mapping-based function it is designed for more complex situations where
actions are needed to be performed instead of reading or writing to a Modbus mapping.

Based on the request *modbus_reply_callback()* shall invoke one or more callbacks and send a
response according the received request.

The user has to provide three callback functions: *verify*, *read* and *write*. When implementing
a RTU-slave, the additional *accept_rtu_slave*-callback has to be provided.

At different stages *modbus_reply_callback()* calls these callbacks by passing different arguments and
the user-context-pointer _user_ctx_ as the first one.

The first callback invoked by modbus_reply_callback() is *accept_rtu_slave* if the user implements a
RTU-slave. In its implementation the user has to check whether the slave-id, _slave_, which was
decoded from the request, should be answered to or not and returning TRUE if so, otherwise FALSE.
Returning FALSE will make *modbus_reply_callback()* exit immedialty and return 0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is accept_rtu_slave only required for RTU? The address field is also available in TCP requests. I don't see why this should be handled differently?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But is it used in TCP? To address different devices via one connection? I don't see it being explained in the spec (while flying over it).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remember correctly, it is used e.g. in SMA inverters to have different register mappings available. And think about a TCP to RTU modbus proxy where you want to have one address for the gateway itself and the others mapped to the UART...

Copy link
Contributor Author

@pboettch pboettch Nov 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, the slave-field in TCP is useful.

The reason for not needing an accept_rtu_slave in a TCP-connection is, that you probably don't want to ignore a request based on the slave-ID when implementing a TCP-server. A TCP-connection is point-to-point whereas a RTU-bus is broadcast, a slave on a RS482-bus sees all the requests coming by, even the ones it doesn't want to handle, so you need to filter - not answering those you don't care for - someone else will do. In TCP no one else will handle the request.

Is that logic correct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely correct. 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(but you can still handle it optionally if you want yes? For people making rtu drop in replacements, and that sort of thing...)

All callbacks hereafter receive the following arguments:

* _slave_ still indicating the RTU-slave-id if the modbus-instance is in RTU-mode otherwise it has
no meaning.
* _function_ containting the value of the modbus-function decoded from the request.
* _address_, the first register or coil-address.
* _nb_, the number of values to be handled.
* _bytes[]_, the byte-buffer containing the raw-data (write-request) or where the raw-data has to
be filled in (read-request).
* The *read*-callback additionally receives the _len_ argument indicating how many bytes can be
filled in.

*modbus_reply_callback()* is then doing some basic sanitizing on standard-specific parameters before
calling the second callback *verify*.

This is done to verify whether the access is valid for this instance in regards to the address-range
or the modbus-function of the request. The user has to return 0 if the range defined by _address_
and _nb_ is inside the device's range and the modbus-function can be handled.

Otherwise the user shall return EMBXILADD or EMBXILFUN respectively, *modbus_reply_callback()' then sends
an exception as a response.

The *read* and *write* callbacks are called if *verify* returned 0. The role of these callbacks is
to extract from the buffer (write-requests) or fill into the buffer (read-requests) the requested
data.

The buffer-pointer given to these two callbacks is pointing to the beginning of the payload-section of
the received or to-be-sent buffer. The data-format used is the one defined by the
modbus-specification and needs to be encoded, or decoded, properly inside the callback-function.
Coils are encoded as up to 8 coils per byte, registers are 16-bit wide and thus consume 2 bytes of the
buffer per value. See below.

The return value of the read-callback. Negative numbers or zero are interpreted as no response
should be send. To, for example, trigger a timeout on the other side.

The write-callbacks has to return 0 to signal success or a negative number to make the library
not send any response.

The library does not do any differentiation regarding the actual modbus-function and the appropriate
invoked callback. All MODBUS_READ-functions are handled by the read-callback, all
MODBUS_WRITE-function-requests are passed to the write-callback. When encountering the
MODBUS_FC_WRITE_AND_READ_REGISTERS-function modbus_reply_callback is calling the write and the
read-callback.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess a dedicated write_read callback is required here... reason below.

These functions are designed for implementing a Modbus TCP server and RTU slaves.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wording nitpick: better write "...implementing Modbus TCP and RTU slaves." ?


BUFFER DATA FORMAT
------------------

This table shows the relation between the byte-count and the value-count.

+------------------------------------+----------+-------------+-----------------+
| Name | Callback | Bytes-size | Value-size (sz) |
+------------------------------------+----------+-------------+-----------------+
| MODBUS_FC_READ_COILS | read | (sz + 8 )/8 | as decoded |
| MODBUS_FC_READ_DISCRETE_INPUTS | read | (sz + 8) /8 | as decoded |
| MODBUS_FC_READ_HOLDING_REGISTERS | read | * 2 | as decoded |
| MODBUS_FC_READ_INPUT_REGISTERS | read | * 2 | as decoded |
| MODBUS_FC_WRITE_SINGLE_COIL | write | 1 | 1 |
| MODBUS_FC_WRITE_SINGLE_REGISTER | write | 2 | 1 |
| MODBUS_FC_WRITE_MULTIPLE_COILS | write | (sz + 8) /8 | as decoded |
| MODBUS_FC_WRITE_MULTIPLE_REGISTERS | write | * 2 | as decoded |
+------------------------------------+----------+-------------+-----------------+

At transport-level modbus knows two different data formats: Registers and bits. The transport-buffer
is byte-oriented, hence the 'uint8_t *'-type.

*read* is used for any read access. Based on the function-argument the user has to do the
de-serialization from the payload format to useful data. Coils/bits are stuffed into bytes,
registers are combined over 2 bytes in big-endian byte-order (MSB first).

*write* is used for any write access. Based on the function-argument the user has to do the
serialization into the payload buffer. The write-callback has to return the number of bytes added to
the buffer. For example having added 9 bits two bytes have been filled and thus the return-value is
two.

Registers are of 16 bit size and are encored with their most-significant-byte first (big-endian).
For example to put two registers into a byte buffer:

uint16_t value1 = 0x1234, value2 = 0x5678;

buf[0] = (value1 >> 8) & 0xff;
buf[1] = value1 & 0xff;
buf[2] = (value2 >> 8) & 0xff;
buf[3] = value2 & 0xff;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just use the helper macros here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they are part of the API, yes. I personally prefer to tell the dirty truth and leave it to the user.

Results in 0x12, 0x34, 0x56, 0x78 on the transport-layer.

Bits are packed inside the byte-buffer. One byte can thus contain up to 8 bits Here is an example
which shows howto insert 4 bits

uint8_t buf[1];
buf[0] = (b3 << 3) | (b2 << 2) | (b1 << 1) | (b0);


RETURN VALUE
------------
The function shall return the length of the response sent if successful. Otherwise it shall return
-1 and set errno.

ERRORS
------
*EMBMDATA*::
Sending has failed

See also the errors returned by the syscall used to send the response (eg. send
or write).

EXAMPLE
-------

The 'modbus_reply' function (based on modbus_mapping_t) is using the 'modbus_reply_callback'
implementation for all read and write ModBus-function and is there a good example. It can be found
in modbus.c .

SEE ALSO
--------
linkmb:modbus_reply_exception[3]
linkmb:modbus_reply[3]
linkmb:modbus_set_bits_from_bytes[3]
linkmb:libmodbus[7]
linkmb:modbus_set_slave[3]

AUTHORS
-------
This libmodbus documentation was written by Patrick Boettcher, based on a first
version by Frode Tennebø <frode@tennebo.com>
4 changes: 4 additions & 0 deletions doc/modbus_set_slave.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ remote device or software drops the requests! The special value
The broadcast address is `MODBUS_BROADCAST_ADDRESS`. This special value must be
use when you want all Modbus devices of the network receive the request.

When implementing a modbus-slave (a server) setting the *MODBUS_SLAVE_ACCEPT_ALL* address to the
context will make that modbus_receive() will accept all requests instead of filtering out those not
matching the set slave-id. This is useful when implementing multi-slave-implementations.


RETURN VALUE
------------
Expand Down
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ AM_CFLAGS = ${my_CFLAGS}

libmodbus_la_SOURCES = \
modbus.c \
modbus-reply.c \
modbus-reply-mapping.c \
modbus.h \
modbus-data.c \
modbus-private.h \
Expand Down
8 changes: 8 additions & 0 deletions src/modbus-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ MODBUS_BEGIN_DECLS
#define _RESPONSE_TIMEOUT 500000
#define _BYTE_TIMEOUT 500000

/* Max between RTU and TCP max adu length (so TCP) */
#define MAX_MESSAGE_LENGTH 260

typedef enum {
_MODBUS_BACKEND_TYPE_RTU=0,
_MODBUS_BACKEND_TYPE_TCP
Expand Down Expand Up @@ -101,11 +104,16 @@ struct _modbus {
struct timeval indication_timeout;
const modbus_backend_t *backend;
void *backend_data;

const modbus_reply_callbacks_t *reply_cb;
void *reply_user_ctx;
};

void _modbus_init_common(modbus_t *ctx);
void _error_print(modbus_t *ctx, const char *context);
int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type);
void _sleep_response_timeout(modbus_t *ctx);
int _modbus_send_msg(modbus_t *ctx, uint8_t *msg, int msg_length);

#ifndef HAVE_STRLCPY
size_t strlcpy(char *dest, const char *src, size_t dest_size);
Expand Down
Loading