liblightmodbus 3.0
A lightweight, header-only, hardware-agnostic Modbus RTU/TCP library
|
In order to use slave side functions, make sure to define LIGHTMODBUS_SLAVE
macro and enable necessary functions (e.g. LIGHTMODBUS_F03S
) before including the library. Please also see Building and integrating liblightmodbus for more information.
Slave device state is represented by ModbusSlave
structure. It must be initialized with modbusSlaveInit()
before it is used:
ModbusSlave contains an internal ModbusBuffer for the response data. This buffer relies on the allocator passed to modbusSlaveInit() as an argument. modbusDefaultAllocator is a default allocator that uses malloc()
and free()
functions. You may write your own more sophisticated allocators if you wish - please refer to Custom allocators for more details.
In this code myRegisterCallback
is a name of a callback function that will be called for every register operation. In previous this versions of the library this functionality was optionally available as LIGHTMODBUS_REGISTER_CALLBACK
. Now, all register operations are performed using callbacks. If you're sure that none of the used parsing functions use the callback, you can pass NULL
as the argument. Importantly, all default parsing functions in the library require the callback to be set.
myExceptionCallback
is a name of a callback function that will be called every time slave reports an exception to the master. To disable this functionality, pass NULL
as the callback.
The set of functions supported by the slave is determined by the 5th function argument - in this case modbusSlaveDefaultFunctions. This argument is a pointer to an array of ModbusSlaveFunctionHandler structures which associate function codes with callbacks. Lifetime of this array must not be shorter than the lifetime of the slave. The length of this array should be provided as the last argument.
The register callback is a function matching ModbusRegisterCallback used by the library to operate on register values. The register callback must respond to four types of register queries (ModbusRegisterQuery) described further in this section.
The library guarantees that before each register is accessed a suitable access query will be made for it beforehand. This mechanism can guarantee that registers outside of valid range will never be accessed and allows the user to implement read/write protection for registers. However, all access queries are performed before the first R/W query, so creating cross-dependencies between registers (i.e. access rights to one register depend on the value of other register) will cause problems. It's best to disable "write multiple X" functions on such registers.
modbusSlaveGetUserPointer()
and modbusSlaveSetUserPointer()
to pass a pointer to your data to the callback.ModbusRegisterCallbackArgs::query
value of either MODBUS_REGQ_R_CHECK or MODBUS_REGQ_W_CHECK indicates that an access query is being made to the callback function. When ModbusRegisterCallbackArgs::query
is MODBUS_REGQ_R_CHECK, read access is requested. MODBUS_REGQ_W_CHECK means that write access is requested.
The type of the accessed register is provided to the function in ModbusRegisterCallbackArgs::type
. The index of the register is stored in ModbusRegisterCallbackArgs::index
and the ID of the function requesting access is available in ModbusRegisterCallbackArgs::function
.
The value stored in ModbusRegisterCallbackArgs::value
during a write access check contains the value of the register that will be written in a subsequent write request. In case of read requests the value stored in this variable may not be used.
When responding to an access query, the function must return a value via ModbusRegisterCallbackResult::exceptionCode
in the result
output argument. This value is treated as the exception code that the slave will report to the master. In order to grant access to a certain register, MODBUS_EXCEP_NONE must be returned.
The return value of the register callback is checked by the library during the access check phase. Returning a value other than MODBUS_OK results in the slave reporting a MODBUS_EXCEP_SLAVE_FAILURE exception to the master.
MODBUS_OK
from the callback and use the result->exceptionCode
to report exceptions.Read query is a request to read contents of some register. During such query ModbusRegisterCallbackArgs::query
is set to MODBUS_REGQ_R. Read query is always preceded with a MODBUS_REGQ_R_CHECK query.
The type of the accessed register is provided to the function in ModbusRegisterCallbackArgs::type
. The index of the register is stored in ModbusRegisterCallbackArgs::index
and the ID of the function reading the register is available in ModbusRegisterCallbackArgs::function
. ModbusRegisterCallbackArgs::value
is unused.
The callback function must return the contents of the requested register via ModbusRegisterCallbackResult::value
in the result
output argument.
The return value from the callback is ignored.
MODBUS_OK
from the callback.Write query is a request to change contents of some register to given value. During such query ModbusRegisterCallbackArgs::query
is set to MODBUS_REGQ_W. Write query is always preceded with a MODBUS_REGQ_W_CHECK query.
The type of the accessed register is provided to the function in ModbusRegisterCallbackArgs::type
. The index of the register is stored in ModbusRegisterCallbackArgs::index
and the ID of the function writing the register is available in ModbusRegisterCallbackArgs::function
. The new value for the register is stored in ModbusRegisterCallbackArgs::value
.
Both value returned through the result
argument and the return value from the function are ignored.
MODBUS_OK
from the callback and do not modify the result
output argument during write queries.The table below represents a brief summary of different queries should be handled.
Query type | args->value | result->exceptionCode | result->value | Return value |
---|---|---|---|---|
MODBUS_REGQ_R_CHECK | Undefined | Must be set to exception code to be reported. MODBUS_EXCEP_NONE otherwise. | Ignored | Returning value different than MODBUS_OK results in MODBUS_EXCEP_SLAVE_FAILURE being reported by the slave. Do not rely on this. |
MODBUS_REGQ_W_CHECK | Value that will be written in the subsequent write request | Must be set to exception code to be reported. MODBUS_EXCEP_NONE otherwise. | Ignored | Returning value different than MODBUS_OK results in MODBUS_EXCEP_SLAVE_FAILURE being reported by the slave. Do not rely on this. |
MODBUS_REGQ_R | Undefined | Ignored | Must be set to the value of the register | Ignored |
MODBUS_REGQ_W | New value for the register | Ignored | Ignored | Ignored |
An example implementation of a register callback operating on registers stored in an array:
Exception callback is a function matching ModbusSlaveExceptionCallback called when the slave reports an exception. This callback is called even if the response frame is not sent to the master (e.g. when the exception is caused by a broadcasted request in Modbus RTU). This is an internal way of signalizing slave failure.
When called, the callback is provided with pointer to ModbusSlave, function that reported the excepion and a ModbusExceptionCode.
The exception callback must return ModbusError, but the return value is ignored. Preferably, it should return MODBUS_OK.
MODBUS_OK
from this callback.An example of a simple callback, printing out exception information:
After successful initialization of the slave device, it's ready to accept requests from the master. The requests can be processed using one of the three functions: modbusParseRequestPDU()
, modbusParseRequestRTU()
and modbusParseRequestTCP()
.
Calling each one of these results in an attempt to parse the request frame, a series of calls to the Register callback, an optional call to slave-exception callback and a response frame being generated for the master device.
The response frame can be accessed using modbusSlaveGetResponse()
and its length can be acquired using modbusSlaveGetResponseLength()
. These functions may not be called if modbusIsOk()
for the returned ModbusErrorInfo
is false
.
modbusSlaveGetResponseLength()
return value of 0. While it may seem sub-optimal, it significantly simplifies the structure of parsing functions. Moreover, in case of read requests, it ensures that appropriate register read queries will always be made to the register callback function allowing the user to reliably act upon these events. Similarly, if the request triggers an exception, the Slave exception callback is called, regardless of whether a response frame was produced. This is an internal way of signalizing a failure to comply.In order to destroy the ModbusSlave structure, simply call modbusSlaveDestroy()
: