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
6 changes: 4 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
set(srcs
"mb_controller/common/esp_modbus_common.c"
"mb_controller/common/esp_modbus_master.c"
"mb_controller/common/esp_modbus_slave.c"
"mb_controller/common/esp_modbus_master_serial.c"
Expand All @@ -13,6 +14,7 @@ set(srcs
"mb_controller/tcp/mbc_tcp_slave.c"
"mb_objects/mb_master.c"
"mb_objects/mb_slave.c"
"mb_objects/functions/mbfunc_handling.c"
"mb_objects/functions/mbfunccoils_master.c"
"mb_objects/functions/mbfunccoils.c"
"mb_objects/functions/mbfuncdiag.c"
Expand Down Expand Up @@ -43,9 +45,9 @@ set(srcs
"mb_transports/tcp/tcp_slave.c"
)

set(include_dirs mb_transports mb_controller/common/include mb_objects/include mb_ports/common mb_ports/serial mb_ports/tcp)
set(include_dirs mb_transports mb_controller/common/include mb_objects/common mb_ports/common mb_ports/serial mb_ports/tcp)

set(priv_include_dirs mb_controller/serial mb_controller/tcp mb_controller/common mb_transports/rtu mb_transports/ascii mb_transports/tcp)
set(priv_include_dirs mb_controller/serial mb_controller/tcp mb_controller/common mb_objects/include mb_transports/rtu mb_transports/ascii mb_transports/tcp)

if(CONFIG_FMB_EXT_TYPE_SUPPORT)
list(APPEND srcs "mb_controller/common/mb_endianness_utils.c")
Expand Down
8 changes: 8 additions & 0 deletions Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,12 @@ menu "Modbus configuration"
otherwise the only legacy types are supported. The extended types include
integer, float, double types with different endianness and size.

config FMB_FUNC_HANDLERS_MAX
int "Maximum number of Modbus function handlers"
range 16 255
default 16
help
This option defines the maximum number of Modbus command handlers for Modbus master and slave.
The option can be useful to register additional commands and its handlers.

endmenu
8 changes: 4 additions & 4 deletions docs/en/applications_and_references.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@ The examples below demonstrate the library port for serial, TCP slave and master

.. _example_mb_slave:

- `Modbus serial slave example <https://github.com/espressif/esp-modbus/tree/release/v2.0/examples/serial/mb_serial_slave>`__
- `Modbus serial slave example <https://github.com/espressif/esp-modbus/tree/main/examples/serial/mb_serial_slave>`__

.. _example_mb_master:

- `Modbus serial master example <https://github.com/espressif/esp-modbus/tree/release/v2.0/examples/serial/mb_serial_master>`__
- `Modbus serial master example <https://github.com/espressif/esp-modbus/tree/main/examples/serial/mb_serial_master>`__

.. _example_mb_tcp_master:

- `Modbus TCP master example <https://github.com/espressif/esp-modbus/tree/release/v2.0/examples/tcp/mb_tcp_slave>`__
- `Modbus TCP master example <https://github.com/espressif/esp-modbus/tree/main/examples/tcp/mb_tcp_master>`__

.. _example_mb_tcp_slave:

- `Modbus TCP slave example <https://github.com/espressif/esp-modbus/tree/release/v2.0/examples/tcp/mb_tcp_master>`__
- `Modbus TCP slave example <https://github.com/espressif/esp-modbus/tree/main/examples/tcp/mb_tcp_slave>`__

Please refer to the specific example README.md for details.

Expand Down
121 changes: 118 additions & 3 deletions docs/en/master_api_overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ The following overview describes how to setup Modbus master communication. The o

1. :ref:`modbus_api_port_initialization` - Initialization of Modbus controller interface for the selected port.
2. :ref:`modbus_api_master_configure_descriptor` - Configure data descriptors to access slave parameters.
3. :ref:`modbus_api_master_setup_communication_options` - Allows to setup communication options for selected port.
4. :ref:`modbus_api_master_start_communication` - Start stack and sending / receiving data.
5. :ref:`modbus_api_master_destroy` - Destroy Modbus controller and its resources.
3. :ref:`modbus_api_master_handler_customization` - Customization of Modbus function handling.
4. :ref:`modbus_api_master_setup_communication_options` - Allows to setup communication options for selected port.
5. :ref:`modbus_api_master_start_communication` - Start stack and sending / receiving data.
6. :ref:`modbus_api_master_destroy` - Destroy Modbus controller and its resources.

.. _modbus_api_master_configure_descriptor:

Expand Down Expand Up @@ -305,6 +306,120 @@ Initialization of master descriptor. The descriptor represents an array of type
The Data Dictionary can be initialized from SD card, MQTT or other source before start of stack. Once the initialization and setup is done, the Modbus controller allows the reading of complex parameters from any slave included in descriptor table using its CID.
Refer to :ref:`example TCP master <example_mb_tcp_master>`, :ref:`example Serial master <example_mb_master>` for more information.

.. _modbus_api_master_handler_customization:

Master Customize Function Handlers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The Master object contains the command handling tables to define the specific handling functionality for each supported Modbus command. The default handling functions in this table support most common Modbus commands. However, the list of commands can be extended by adding the new command into handling table with its custom handling behavior. It is also possible overriding the function handler for the specific command. The below described API functions allow using this behavior for master objects.

:cpp:func:`mbc_set_handler`

The function adds new handler for the function or overrides the existing handler for the function.

:cpp:func:`mbc_get_handler`

The function returns the handler for the specified function code from handling table. Allows to keep and use the predefined handlers for standard functions.

:cpp:func:`mbc_delete_handler`

The function allows to delete the handler for specified command and free the handler table entry for this.

:cpp:func:`mbc_get_handler_count`

The function returns the actual number of command handlers registered for the object reffered by parameter.

The example code to override the handler routine for the command `<0x04 - Read Input Registers>` is below. This example allows to perform a custom action and then calls the standard handler, which maps the device data to the command buffer from the actual parameter. This is just recommended behavior for handling functions, but users can change the order of the calls if absolutely required. Please refer to the existing handler :cpp:func:`mbm_fn_read_inp_reg` for more information.

.. code:: c

static void *master_handle = NULL; // Pointer to allocated interface structure
const uint8_t override_command = 0x04;
mb_fn_handler_fp pstandard_handler = NULL;
....
// This is the custom function handler for the command.
// The handler is executed from the context of modbus controller event task and should be as simple as possible.
// Parameters: frame_ptr - the pointer to the incoming ADU frame from slave starting from function code,
// plen - the pointer to length of the frame. After return from the handler the modbus object will
// handle the end of transaction according to the exception returned.
mb_exception_t my_custom_fc04_handler(void *pinst, uint8_t *frame_ptr, uint16_t *plen)
{
mb_exception_t exception = MB_EX_CRITICAL;
MB_RETURN_ON_FALSE(frame_ptr && plen, exception, TAG, "incorrect frame buffer length");
// It is the possible place for the custom behavior
if (pstandard_handler) {
exception = pstandard_handler(pinst, frame_ptr, plen); // invoke standard behavior with mapping
}
return exception;
}
....
// Get the standard handler for the command to use it in the handler.
err = mbc_get_handler(master_handle, custom_command, &pstandard_handler);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
"could not get handler for command %d, returned (0x%x).", (int)custom_command, (int)err);
// This call overrides the handler for the standard command.
err = mbc_set_handler(master_handle, override_command, my_custom_fc04_handler);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
"could not override handler, returned (0x%x).", (int)err);

.. note:: The custom handler set by the function :cpp:func:`mbc_set_handler` should be as short as possible and should contain simple and safe logic to not break the normal functionality of the stack. This is user application responsibility to handle the command appropriately.

The example code to handle custom vendor specific command is below. This example sends the 'Master' string to slave and gets the response from slave with the string being appended from slave. It is just a simple echo example to demonstrate the approach.

.. code:: c

#define MB_CUST_DATA_LEN 100
static char my_custom_data[MB_CUST_DATA_LEN] = {0}; // custom data buffer for the request
static void *master_handle = NULL; // Pointer to allocated interface structure

// This is the custom function handler to process incoming slave response.
// Parameters: frame_ptr: is a pointer to incoming frame buffer, plen: is pointer to length including the function code
// In spite of logging showed here, try to use just simple functionality in the handler.
mb_exception_t my_custom_fc_handler(void *pinst, uint8_t *frame_ptr, uint16_t *plen)
{
MB_RETURN_ON_FALSE((frame_ptr && plen && *plen && *plen < (MB_CUST_DATA_LEN - 1)), MB_EX_ILLEGAL_DATA_VALUE, TAG,
"incorrect custom frame buffer");
ESP_LOGI(TAG, "Custom handler, Frame ptr: %p, len: %u", frame_ptr, *plen);
strncpy((char *)&my_custom_data[0], (char *)&frame_ptr[1], MB_CUST_DATA_LEN);
ESP_LOG_BUFFER_HEXDUMP("CUSTOM_DATA", &my_custom_data[0], (*plen - 1), ESP_LOG_INFO);
return MB_EX_NONE;
}
....
// The setup of the master object is completed and the master_handle is already actual

// Add custom command handler
const uint8_t custom_command = 0x41; // the function code for the request
// Override or add new handler entry.
err = mbc_set_handler(master_handle, custom_command, my_custom_fc_handler);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
"could not override handler, returned (0x%x).", (int)err);
mb_fn_handler_fp phandler = NULL;
// Make sure the handler is updated correctly
err = mbc_get_handler(master_handle, custom_command, &phandler);
MB_RETURN_ON_FALSE((err == ESP_OK && phandler == my_custom_fc_handler), ESP_ERR_INVALID_STATE, TAG,
"could not get handler for command %d, returned (0x%x).", (int)custom_command, (int)err);

char *pcustom_string = "Master"; // The custom request string that will be sent to the slave
mb_param_request_t req = {
.slave_addr = MB_DEVICE_ADDR1, // the slave UID to send the request
.command = 0x41, // the custom function code,
.reg_start = 0, // unused,
.reg_size = (strlen(pcustom_string) >> 1) // length of the data to send (registers)
};

// Send the request with custom command (vendor speciic)
// This function supports sending of even number of bytes
// as instructed by req.reg_size (Modbus register = 2 bytes)
err = mbc_master_send_request(master_handle, &req, pcustom_string);
if (err != ESP_OK) {
ESP_LOGE("CUSTOM_DATA", "Send custom request fail.");
} else {
// The request is processed correctly and the `my_custom_data[]` contains the sent string with appended slave string
...
}

Refer to :ref:`example Serial master <example_mb_master>` for more information.

.. _modbus_api_master_start_communication:

Master Communication
Expand Down
2 changes: 1 addition & 1 deletion docs/en/overview_messaging_and_mapping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ The below diagrams show how the extended data types appear on network layer.

The approach showed above can be used to pack the data into MBAP frames used by Modbus TCP as well as for other types with similar size.

The following sections give an overview of how to use the ESP_Modbus component found under `components/freemodbus`. The sections cover initialization of a Modbus port, and the setup a master or slave device accordingly:
The following sections give an overview of how to use the ESP_Modbus component found under `components/esp-modbus`. The sections cover initialization of a Modbus port, and the setup a master or slave device accordingly:

- :ref:`modbus_api_port_initialization`
- :ref:`modbus_api_slave_overview`
Expand Down
73 changes: 70 additions & 3 deletions docs/en/slave_api_overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ The sections below represent typical programming workflow for the slave API whic

1. :ref:`modbus_api_port_initialization` - Initialization of Modbus controller interface using communication options.
2. :ref:`modbus_api_slave_configure_descriptor` - Configure data descriptors to access slave parameters.
3. :ref:`modbus_api_slave_setup_communication_options` - Allows to setup communication options for selected port.
4. :ref:`modbus_api_slave_communication` - Start stack and sending / receiving data. Filter events when master accesses the register areas.
5. :ref:`modbus_api_slave_destroy` - Destroy Modbus controller and its resources.
3. :ref:`modbus_api_slave_handler_customization` - Customization of Modbus function handling in slave object.
4. :ref:`modbus_api_slave_setup_communication_options` - Allows to setup communication options for selected port.
5. :ref:`modbus_api_slave_communication` - Start stack and sending / receiving data. Filter events when master accesses the register areas.
6. :ref:`modbus_api_slave_destroy` - Destroy Modbus controller and its resources.

.. _modbus_api_slave_configure_descriptor:

Expand Down Expand Up @@ -161,6 +162,72 @@ Example to get the actual slave identificator:
}
...

.. _modbus_api_slave_handler_customization:

Slave Customize Function Handlers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The Slave object contains the command handling tables to define the specific handling functionality for each supported Modbus command. The default handling functions in this table support most useful Modbus commands. However, the list of commands can be extended by adding the new command into handling table with its custom handling behavior. It is also possible overriding the function handler for the specific command. The below described API functions allow using this behavior for slave objects.

:cpp:func:`mbc_set_handler`

The function adds new handler for the function or overrides the existing handler for the function.

:cpp:func:`mbc_get_handler`

The function returns the handler for the specified function code from handling table. Allows to keep and use the predefined handlers for standard functions.

:cpp:func:`mbc_delete_handler`

The function allows to delete the handler for specified command and free the handler table entry for this.

:cpp:func:`mbc_get_handler_count`

The function returns the actual number of command handlers registered for the object reffered by parameter.

The following example allows to override the standard command to read input registers. Refer to standard handler function :cpp:func:`mbs_fn_read_input_reg` for more information on how to handle custom commands.

.. code:: c

static void *slave_handle = NULL; // Pointer to allocated interface structure (must be actual)
mb_fn_handler_fp pstandard_handler = NULL;
....
// This is the custom function handler for the command.
// The handler is executed from the context of modbus controller event task and should be as simple as possible.
// Parameters: frame_ptr - the pointer to the incoming ADU request frame from master starting from function code,
// plen - the pointer to length of the frame. The handler body can override the buffer and return the length of data.
// After return from the handler the modbus object will handle the end of transaction according to the exception returned,
// then builds the response frame and send it back to the master. If the whole transaction time including the response
// latency exceeds the configured slave response time set in the master configuration the master will ignore the transaction.
mb_exception_t my_custom_fc04_handler(void *pinst, uint8_t *frame_ptr, uint16_t *plen)
{
MB_RETURN_ON_FALSE(frame_ptr && plen, MB_EX_CRITICAL, TAG, "incorrect frame buffer length");
// Place the custom behavior to process the buffer here
if (pstandard_handler) {
exception = pstandard_handler(pinst, frame_ptr, plen); // invoke standard behavior with mapping
}
return exception;
}
...
const uint8_t override_command = 0x04;
// Get the standard handler for the command to use it in the handler.
err = mbc_get_handler(master_handle, override_command, &pstandard_handler);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
"could not get handler for command %d, returned (0x%x).", (int)override_command, (int)err);
// Set the custom handler function for the command
err = mbc_set_handler(slave_handle, override_command, my_custom_fc04_handler);
MB_RETURN_ON_FALSE((err == ESP_OK), ;, TAG,
"could not override handler, returned (0x%x).", (int)err);
mb_fn_handler_fp phandler = NULL;
// Check the actual handler for the command
err = mbc_get_handler(slave_handle, override_command, &phandler);
MB_RETURN_ON_FALSE((err == ESP_OK && phandler == my_custom_fc04_handler), ;, TAG,
"could not get handler, returned (0x%x).", (int)err);

Refer to :ref:`example Serial slave <example_mb_slave>` for more information.

.. note:: The custom handlers set by the function :cpp:func:`mbc_set_handler` should be as short as possible, contain simple and safe logic and avoid blocking calls to not break the normal functionality of the stack. The possible latency in this handler may prevent to respond properly to the master request which waits for response during the slave response time configured in the configuration structure. If the slave does not respond to the master during the slave response time the master will report timeout failure and ignores the late response. This is user application responsibility to handle the command appropriately.

.. _modbus_api_slave_communication:

Slave Communication
Expand Down
Loading