Merge branch 'feature/master_slave_add_custom_fc_handlers' into 'main'

feature add custom fc handlers for master and slave v2

See merge request idf/esp-modbus!93
This commit is contained in:
Alex Lisitsyn
2025-04-03 18:52:24 +08:00
51 changed files with 1345 additions and 300 deletions

View File

@ -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"
@ -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"
@ -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")

View File

@ -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

View File

@ -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.

View File

@ -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:
@ -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 table to define specific handling functionality for each supported Modbus command. The default handling functions in this table support the most common Modbus commands. However, the list of commands can be extended by adding a new command into the handling table with its custom handling behavior. It is also possible to override the function handler for a 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

View File

@ -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`

View File

@ -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:
@ -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 table to define specific handling functionality for each supported Modbus command. The default handling functions in this table support the most common Modbus commands. However, the list of commands can be extended by adding a new command into the handling table with its custom handling behavior. It is also possible to override the function handler for a 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

View File

@ -20,7 +20,7 @@
#define MASTER_MAX_CIDS num_device_parameters
// Number of reading of parameters from slave
#define MASTER_MAX_RETRY 30
#define MASTER_MAX_RETRY (10)
// Timeout to update cid over Modbus
#define UPDATE_CIDS_TIMEOUT_MS (500)
@ -56,7 +56,9 @@
#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val }
#define EACH_ITEM(array, length) \
(typeof(*(array)) *pitem = (array); (pitem < &((array)[length])); pitem++)
(typeof(*(array)) *pitem = (array); (pitem < &((array)[length])); pitem++)
#define MB_CUST_DATA_LEN 100 // The length of custom command buffer
static const char *TAG = "MASTER_TEST";
@ -217,6 +219,8 @@ const mb_parameter_descriptor_t device_parameters[] = {
// Calculate number of parameters in the table
const uint16_t num_device_parameters = (sizeof(device_parameters)/sizeof(device_parameters[0]));
static char my_custom_data[MB_CUST_DATA_LEN] = {0}; // the custom data buffer
static void *master_handle = NULL;
// The function to get pointer to parameter storage (instance) according to parameter description table
@ -309,8 +313,24 @@ static void master_operation_func(void *arg)
const mb_parameter_descriptor_t *param_descriptor = NULL;
ESP_LOGI(TAG, "Start modbus test...");
char *pcustom_string = "Master";
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)
};
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED
// Send the request with custom command (vendor speciic)
// This function supports sending of only 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.");
}
#if CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT
// Command - 17 (0x11) Report Slave ID
// The command contains vendor specific data and should be interpreted accordingly.
// This version of command handler needs to define the maximum number
@ -318,12 +338,10 @@ static void master_operation_func(void *arg)
// The returned slave info data will be stored into the `info_buf`.
// Request fields: slave_addr - the UID of slave, reg_start - not used,
// reg_size = max size of buffer (registers).
mb_param_request_t req = {
.slave_addr = MB_DEVICE_ADDR1, // slave UID to retrieve ID
.command = 0x11, // the <Report Slave ID> command,
.reg_start = 0, // must be zero,
.reg_size = (CONFIG_FMB_CONTROLLER_SLAVE_ID_MAX_SIZE >> 1) // the expected length of buffer in registers
};
req.slave_addr = MB_DEVICE_ADDR1; // slave UID to retrieve ID
req.command = 0x11; // the <Report Slave ID> command,
req.reg_start = 0; // must be zero,
req.reg_size = (CONFIG_FMB_CONTROLLER_SLAVE_ID_MAX_SIZE >> 1); // the expected length of buffer in registers
uint8_t info_buf[CONFIG_FMB_CONTROLLER_SLAVE_ID_MAX_SIZE] = {0}; // The buffer to save slave ID
@ -467,6 +485,21 @@ static void master_operation_func(void *arg)
ESP_ERROR_CHECK(mbc_master_delete(master_handle));
}
// 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_handler(void *inst, 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_LOGD(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_WARN);
return MB_EX_NONE;
}
// Modbus master initialization
static esp_err_t master_init(void)
{
@ -492,6 +525,19 @@ static esp_err_t master_init(void)
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
"mb controller initialization fail, returns(0x%x).", (int)err);
const uint8_t override_command = 0x41;
// Delete the handler for specified command, if available
err = mbc_delete_handler(master_handle, override_command);
MB_RETURN_ON_FALSE((err == ESP_OK || err == ESP_ERR_INVALID_STATE), ESP_ERR_INVALID_STATE, TAG,
"could not override handler, returned (0x%x).", (int)err);
err = mbc_set_handler(master_handle, override_command, my_custom_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;
err = mbc_get_handler(master_handle, override_command, &phandler);
MB_RETURN_ON_FALSE((err == ESP_OK && phandler == my_custom_handler), ESP_ERR_INVALID_STATE, TAG,
"could not get handler for command %d, returned (0x%x).", (int)override_command, (int)err);
// Set UART pin numbers
err = uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD, CONFIG_MB_UART_RXD,
CONFIG_MB_UART_RTS, UART_PIN_NO_CHANGE);

View File

@ -43,7 +43,8 @@
#define MB_WRITE_MASK (MB_EVENT_HOLDING_REG_WR \
| MB_EVENT_COILS_WR)
#define MB_READ_WRITE_MASK (MB_READ_MASK | MB_WRITE_MASK)
#define MB_TEST_VALUE 12345.0
#define MB_TEST_VALUE (12345.0)
#define MB_CUST_DATA_MAX_LEN (100)
static const char *TAG = "SLAVE_TEST";
@ -144,6 +145,24 @@ static void setup_reg_data(void)
input_reg_params.input_data7 = 4.78;
}
// This is a simple 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_fc_handler(void *pinst, uint8_t *frame_ptr, uint16_t *plen)
{
char *str_append = ":Slave";
MB_RETURN_ON_FALSE((frame_ptr && plen && *plen < (MB_CUST_DATA_MAX_LEN - strlen(str_append))), MB_EX_ILLEGAL_DATA_VALUE, TAG,
"incorrect custom frame");
frame_ptr[*plen] = '\0';
strcat((char *)&frame_ptr[1], str_append);
*plen = (strlen(str_append) + *plen); // the length of (response + command)
return MB_EX_NONE; // Set the exception code for modbus object appropriately
}
// An example application of Modbus slave. It is based on esp-modbus stack.
// See deviceparams.h file for more information about assigned Modbus parameters.
// These parameters can be accessed from main application and also can be changed
@ -173,6 +192,19 @@ void app_main(void)
ESP_ERROR_CHECK(mbc_slave_create_serial(&comm_config, &mbc_slave_handle)); // Initialization of Modbus controller
const uint8_t custom_command = 0x41; // The custom command to be sent to slave
// Try to delete the handler for specified command.
esp_err_t err = mbc_delete_handler(mbc_slave_handle, custom_command);
MB_RETURN_ON_FALSE((err == ESP_OK || err == ESP_ERR_INVALID_STATE), ;, TAG,
"could not delete handler, returned (0x%x).", (int)err);
err = mbc_set_handler(mbc_slave_handle, custom_command, my_custom_fc_handler);
MB_RETURN_ON_FALSE((err == ESP_OK), ;, TAG,
"could not set or override handler, returned (0x%x).", (int)err);
mb_fn_handler_fp phandler = NULL;
err = mbc_get_handler(mbc_slave_handle, custom_command, &phandler);
MB_RETURN_ON_FALSE((err == ESP_OK && phandler == my_custom_fc_handler), ;, TAG,
"could not get handler for command %d, returned (0x%x).", (int)custom_command, (int)err);
// The code below initializes Modbus register area descriptors
// for Modbus Holding Registers, Input Registers, Coils and Discrete Inputs
// Initialization should be done for each supported Modbus register area according to register map.
@ -243,7 +275,7 @@ void app_main(void)
ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX));
// Starts of modbus controller and stack
esp_err_t err = mbc_slave_start(mbc_slave_handle);
err = mbc_slave_start(mbc_slave_handle);
#if CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT
// Initialize the new slave identificator structure (example)

View File

@ -14,8 +14,8 @@ from conftest import ModbusTestDut, Stages
pattern_dict_slave = {Stages.STACK_IPV4: (r'I \([0-9]+\) example_connect: - IPv4 address: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})'),
Stages.STACK_IPV6: (r'I \([0-9]+\) example_connect: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})'),
Stages.STACK_INIT: (r'I \(([0-9]+)\) MB_TCP_SLAVE_PORT: (Protocol stack initialized).'),
Stages.STACK_CONNECT: (r'I\s\(([0-9]+)\) MB_TCP_SLAVE_PORT: Socket \(#[0-9]+\), accept client connection from address: '
Stages.STACK_INIT: (r'I \(([0-9]+)\) SLAVE_TEST: (Modbus slave stack initialized)'),
Stages.STACK_CONNECT: (r'I\s\(([0-9]+)\) SLAVE_TEST: Socket \(#[0-9]+\), accept client connection from address: '
r'([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})'),
Stages.STACK_START: (r'I\s\(([0-9]+)\) SLAVE_TEST: (Start modbus test)'),
Stages.STACK_PAR_OK: (r'I\s\(([0-9]+)\) SLAVE_TEST: ([A-Z]+ [A-Z]+) \([a-zA-Z0-9_]+ us\),\s'
@ -48,16 +48,16 @@ test_configs = [
@pytest.mark.parametrize('config', test_configs, indirect=True)
@pytest.mark.parametrize(
'count, app_path', [
(2, f'{os.path.join(os.path.dirname(__file__), "mb_serial_master")}|{os.path.join(os.path.dirname(__file__), "mb_serial_slave")}')
(2, f'{os.path.join(os.path.dirname(__file__), "mb_serial_slave")}|{os.path.join(os.path.dirname(__file__), "mb_serial_master")}')
],
indirect=True
)
def test_modbus_serial_communication(config: str, dut: Tuple[ModbusTestDut, ModbusTestDut]) -> None:
dut_slave = dut[1]
dut_master = dut[0]
dut_slave = dut[0]
dut_master = dut[1]
logger.info('DUT: %s start.', dut_master.dut_get_name())
logger.info('DUT: %s start.', dut_slave.dut_get_name())
logger.info('DUT: %s start.', dut_master.dut_get_name())
dut_slave.dut_test_start(dictionary=pattern_dict_slave)
dut_master.dut_test_start(dictionary=pattern_dict_master)

View File

@ -65,6 +65,9 @@
// Options can be used as bit masks or parameter limits
#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val }
#define EACH_ITEM(array, length) \
(typeof(*(array)) *pitem = (array); (pitem < &((array)[length])); pitem++)
#define MB_ID_BYTE0(id) ((uint8_t)(id))
#define MB_ID_BYTE1(id) ((uint8_t)(((uint16_t)(id) >> 8) & 0xFF))
#define MB_ID_BYTE2(id) ((uint8_t)(((uint32_t)(id) >> 16) & 0xFF))
@ -80,8 +83,7 @@
#define MB_MDNS_INSTANCE(pref) pref"mb_master_tcp"
#define EACH_ITEM(array, length) \
(typeof(*(array)) *pitem = (array); (pitem < &((array)[length])); pitem++)
#define MB_CUST_DATA_LEN 100 // The length of custom command buffer
static const char *TAG = "MASTER_TEST";
@ -268,6 +270,7 @@ char* slave_ip_address_table[MB_DEVICE_COUNT + 1] = {
};
const size_t ip_table_sz = (size_t)(sizeof(slave_ip_address_table) / sizeof(slave_ip_address_table[0]));
static char my_custom_data[MB_CUST_DATA_LEN] = {0}; // custom data buffer to handle slave response
#if CONFIG_MB_SLAVE_IP_FROM_STDIN
@ -450,6 +453,20 @@ static void master_operation_func(void *arg)
ESP_LOGI(TAG, "Start modbus test...");
char *pcustom_string = "Master";
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)
err = mbc_master_send_request(master_handle, &req, pcustom_string);
if (err != ESP_OK) {
ESP_LOGE("CUSTOM_DATA", "Send custom request fail.");
}
for(uint16_t retry = 0; retry <= MASTER_MAX_RETRY && (!alarm_state); retry++) {
// Read all found characteristics from slave(s)
for (uint16_t cid = 0; (err != ESP_ERR_NOT_FOUND) && cid < MASTER_MAX_CIDS; cid++) {
@ -658,6 +675,21 @@ static esp_err_t destroy_services(void)
return err;
}
// 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_fc_handler(void *inst, 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;
}
// Modbus master initialization
static esp_err_t master_init(mb_communication_info_t *pcomm_info)
{
@ -670,6 +702,20 @@ static esp_err_t master_init(mb_communication_info_t *pcomm_info)
"mb controller initialization fail, returns(0x%x).",
(int)err);
// Add custom command handler
uint8_t custom_command = 0x41;
// Delete the handler for specified command, if available
err = mbc_delete_handler(master_handle, custom_command);
MB_RETURN_ON_FALSE((err == ESP_OK || err == ESP_ERR_INVALID_STATE), ESP_ERR_INVALID_STATE, TAG,
"could not override handler, returned (0x%x).", (int)err);
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;
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);
err = mbc_master_set_descriptor(master_handle, &device_parameters[0], num_device_parameters);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,

View File

@ -13,7 +13,7 @@ CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_MB_SLAVE_IP_FROM_STDIN=y
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
# CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y # intentionally keep it here to change when needed
CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_EXAMPLE_CONNECT_IPV6=n

View File

@ -7,7 +7,7 @@ CONFIG_FMB_COMM_MODE_RTU_EN=n
CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
# CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y # intentionally keep it here to change when needed
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_MB_SLAVE_IP_FROM_STDIN=y
CONFIG_EXAMPLE_CONNECT_IPV6=n

View File

@ -4,8 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
// FreeModbus Slave Example ESP32
#include <stdio.h>
#include "esp_err.h"
#include "sdkconfig.h"
@ -43,8 +41,8 @@
#define MB_REG_HOLDING_START_AREA2_SIZE ((size_t)((HOLD_OFFSET(holding_area2_end) - HOLD_OFFSET(holding_u8_a)) << 1))
#define MB_PAR_INFO_GET_TOUT (10) // Timeout for get parameter info
#define MB_CHAN_DATA_MAX_VAL (10)
#define MB_CHAN_DATA_OFFSET (1.1f)
#define MB_CHAN_DATA_MAX_VAL (60)
#define MB_CHAN_DATA_OFFSET (10.1f)
#define MB_READ_MASK (MB_EVENT_INPUT_REG_RD \
| MB_EVENT_HOLDING_REG_RD \
@ -55,6 +53,7 @@
#define MB_READ_WRITE_MASK (MB_READ_MASK | MB_WRITE_MASK)
#define MB_TEST_VALUE (12345.0)
#define MB_SLAVE_ADDR (CONFIG_MB_SLAVE_ADDR)
#define MB_CUST_DATA_MAX_LEN (100)
static const char *TAG = "SLAVE_TEST";
@ -266,10 +265,28 @@ static esp_err_t destroy_services(void)
return err;
}
// This is a simple 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_fc_handler(void *pinst, uint8_t *frame_ptr, uint16_t *plen)
{
char *str_append = ":Slave";
MB_RETURN_ON_FALSE((frame_ptr && plen && *plen < (MB_CUST_DATA_MAX_LEN - strlen(str_append))), MB_EX_ILLEGAL_DATA_VALUE, TAG,
"incorrect custom frame");
frame_ptr[*plen] = '\0';
strcat((char *)&frame_ptr[1], str_append);
*plen = (strlen(str_append) + *plen); // the length of (response + command)
return MB_EX_NONE; // Set the exception code for modbus object appropriately
}
// Modbus slave initialization
static esp_err_t slave_init(mb_communication_info_t *pcomm_info)
{
mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure
mb_register_area_descriptor_t reg_area = {0}; // Modbus register area descriptor structure
// Initialization of Modbus controller
esp_err_t err = mbc_slave_create_tcp(pcomm_info, &slave_handle);
@ -277,6 +294,19 @@ static esp_err_t slave_init(mb_communication_info_t *pcomm_info)
TAG,
"mb controller create fail.");
uint8_t custom_command = 0x41;
// Delete the handler for specified command, if available.
err = mbc_delete_handler(slave_handle, custom_command);
MB_RETURN_ON_FALSE((err == ESP_OK || err == ESP_ERR_INVALID_STATE), ESP_ERR_INVALID_STATE, TAG,
"could not reset handler, returned (0x%x).", (int)err);
err = mbc_set_handler(slave_handle, custom_command, my_custom_fc_handler);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
"could not set or override handler, returned (0x%x).", (int)err);
mb_fn_handler_fp phandler = NULL;
err = mbc_get_handler(slave_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);
// The code below initializes Modbus register area descriptors
// for Modbus Holding Registers, Input Registers, Coils and Discrete Inputs
// Initialization should be done for each supported Modbus register area according to register map.
@ -287,6 +317,7 @@ static esp_err_t slave_init(mb_communication_info_t *pcomm_info)
reg_area.start_offset = MB_REG_HOLDING_START_AREA0; // Offset of register area in Modbus protocol
reg_area.address = (void*)&holding_reg_params.holding_data0; // Set pointer to storage instance
reg_area.size = (MB_REG_HOLDING_START_AREA1 - MB_REG_HOLDING_START_AREA0) << 1; // Set the size of register storage instance
reg_area.access = MB_ACCESS_RW;
err = mbc_slave_set_descriptor(slave_handle, reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
@ -297,6 +328,7 @@ static esp_err_t slave_init(mb_communication_info_t *pcomm_info)
reg_area.start_offset = MB_REG_HOLDING_START_AREA1; // Offset of register area in Modbus protocol
reg_area.address = (void*)&holding_reg_params.holding_data4; // Set pointer to storage instance
reg_area.size = sizeof(float) << 2; // Set the size of register storage instance
reg_area.access = MB_ACCESS_RW;
err = mbc_slave_set_descriptor(slave_handle, reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
@ -309,6 +341,7 @@ static esp_err_t slave_init(mb_communication_info_t *pcomm_info)
reg_area.start_offset = MB_REG_HOLDING_START_AREA2;
reg_area.address = (void*)&holding_reg_params.holding_u8_a;
reg_area.size = MB_REG_HOLDING_START_AREA2_SIZE;
reg_area.access = MB_ACCESS_RW;
err = mbc_slave_set_descriptor(slave_handle, reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
@ -321,6 +354,7 @@ static esp_err_t slave_init(mb_communication_info_t *pcomm_info)
reg_area.start_offset = MB_REG_INPUT_START_AREA0;
reg_area.address = (void*)&input_reg_params.input_data0;
reg_area.size = sizeof(float) << 2;
reg_area.access = MB_ACCESS_RW;
err = mbc_slave_set_descriptor(slave_handle, reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
@ -330,6 +364,7 @@ static esp_err_t slave_init(mb_communication_info_t *pcomm_info)
reg_area.start_offset = MB_REG_INPUT_START_AREA1;
reg_area.address = (void*)&input_reg_params.input_data4;
reg_area.size = sizeof(float) << 2;
reg_area.access = MB_ACCESS_RW;
err = mbc_slave_set_descriptor(slave_handle, reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
@ -341,6 +376,7 @@ static esp_err_t slave_init(mb_communication_info_t *pcomm_info)
reg_area.start_offset = MB_REG_COILS_START;
reg_area.address = (void*)&coil_reg_params;
reg_area.size = sizeof(coil_reg_params);
reg_area.access = MB_ACCESS_RW;
err = mbc_slave_set_descriptor(slave_handle, reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
@ -352,6 +388,7 @@ static esp_err_t slave_init(mb_communication_info_t *pcomm_info)
reg_area.start_offset = MB_REG_DISCRETE_INPUT_START;
reg_area.address = (void*)&discrete_reg_params;
reg_area.size = sizeof(discrete_reg_params);
reg_area.access = MB_ACCESS_RW;
err = mbc_slave_set_descriptor(slave_handle, reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,

View File

@ -14,7 +14,7 @@ CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_MB_SLAVE_ADDR=1
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
# CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y # intentionally keep it here to change when needed!
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_CONNECT_ETHERNET=y

View File

@ -8,7 +8,7 @@ CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
# CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y # intentionally keep it here to change when needed!
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_MB_SLAVE_ADDR=1
CONFIG_EXAMPLE_CONNECT_IPV6=n

View File

@ -14,7 +14,7 @@ from conftest import ModbusTestDut, Stages
pattern_dict_slave = {Stages.STACK_IPV4: (r'I \([0-9]+\) example_[a-z]+: - IPv4 address: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})'),
Stages.STACK_IPV6: (r'I \([0-9]+\) example_[a-z]+: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})'),
Stages.STACK_INIT: (r'I \(([0-9]+)\) MB_TCP_SLAVE_PORT: (Protocol stack initialized).'),
Stages.STACK_INIT: (r'I \(([0-9]+)\) SLAVE_TEST: (Modbus slave stack initialized)'),
Stages.STACK_CONNECT: (r'I\s\(([0-9]+)\) MB_TCP_SLAVE_PORT: Socket \(#[0-9]+\), accept client connection from address: '
r'([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})'),
Stages.STACK_START: (r'I\s\(([0-9]+)\) SLAVE_TEST: (Start modbus test)'),
@ -36,11 +36,9 @@ pattern_dict_master = {Stages.STACK_IPV4: (r'I \([0-9]+\) example_[a-z]+: - IPv4
LOG_LEVEL = logging.DEBUG
LOGGER_NAME = 'modbus_test'
CONFORMANCE_TEST_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../tools/robot'))
logger = logging.getLogger(LOGGER_NAME)
test_configs = [
'wifi',
'ethernet'
]
@ -49,23 +47,23 @@ test_configs = [
@pytest.mark.parametrize('config', test_configs, indirect=True)
@pytest.mark.parametrize(
'count, app_path', [
(2, f'{os.path.join(os.path.dirname(__file__), "mb_tcp_master")}|{os.path.join(os.path.dirname(__file__), "mb_tcp_slave")}')
(2, f'{os.path.join(os.path.dirname(__file__), "mb_tcp_slave")}|{os.path.join(os.path.dirname(__file__), "mb_tcp_master")}')
],
indirect=True
)
def test_modbus_tcp_communication(dut: Tuple[ModbusTestDut, ModbusTestDut]) -> None:
dut_slave = dut[1]
dut_master = dut[0]
dut_slave = dut[0]
dut_master = dut[1]
logger.info('DUT: %s start.', dut_master.dut_get_name())
logger.info('DUT: %s start.', dut_slave.dut_get_name())
logger.info('DUT: %s start.', dut_master.dut_get_name())
dut_slave_ip_address = dut_slave.dut_get_ip()
dut_master.dut_send_ip(dut_slave_ip_address)
dut_slave.dut_test_start(dictionary=pattern_dict_slave)
dut_master.dut_test_start(dictionary=pattern_dict_master)
dut_slave.dut_check_errors()
dut_master.dut_check_errors()

View File

@ -0,0 +1,91 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_err.h"
#include "mbc_master.h" // for master interface define
#include "mbc_slave.h" // for slave interface define
#include "esp_modbus_common.h" // for public interface defines
static const char TAG[] __attribute__((unused)) = "MB_CONTROLLER_COMMON";
/**
* Register or override command handler for the command in object command handler table
*/
esp_err_t mbc_set_handler(void *ctx, uint8_t func_code, mb_fn_handler_fp phandler)
{
MB_RETURN_ON_FALSE((ctx && phandler && func_code), ESP_ERR_INVALID_STATE, TAG,
"Incorrect arguments for the function.");
mb_err_enum_t ret = MB_EINVAL;
mb_controller_common_t *mb_controller = (mb_controller_common_t *)(ctx);
mb_base_t *pmb_obj = (mb_base_t *)mb_controller->mb_base;
MB_RETURN_ON_FALSE(pmb_obj, ESP_ERR_INVALID_STATE, TAG,
"Controller interface is not correctly initialized.");
if (pmb_obj->descr.is_master) {
ret = mbm_set_handler(mb_controller->mb_base, func_code, phandler);
} else {
ret = mbs_set_handler(mb_controller->mb_base, func_code, phandler);
}
return MB_ERR_TO_ESP_ERR(ret);
}
/**
* Get command handler from the command handler table of the object
*/
esp_err_t mbc_get_handler(void *ctx, uint8_t func_code, mb_fn_handler_fp *phandler)
{
MB_RETURN_ON_FALSE((ctx && func_code && phandler), ESP_ERR_INVALID_STATE, TAG,
"Incorrect arguments for the function.");
mb_err_enum_t ret = MB_EINVAL;
mb_controller_common_t *mb_controller = (mb_controller_common_t *)(ctx);
mb_base_t *pmb_obj = (mb_base_t *)mb_controller->mb_base;
MB_RETURN_ON_FALSE(pmb_obj, ESP_ERR_INVALID_STATE, TAG,
"Controller interface is not correctly initialized.");
if (pmb_obj->descr.is_master) {
ret = mbm_get_handler(mb_controller->mb_base, func_code, phandler);
} else {
ret = mbs_get_handler(mb_controller->mb_base, func_code, phandler);
}
return MB_ERR_TO_ESP_ERR(ret);
}
/**
* Delete command handler from the command handler table of the object
*/
esp_err_t mbc_delete_handler(void *ctx, uint8_t func_code)
{
MB_RETURN_ON_FALSE((ctx && func_code), ESP_ERR_INVALID_STATE, TAG,
"Incorrect arguments for the function.");
mb_err_enum_t ret = MB_EINVAL;
mb_controller_common_t *mb_controller = (mb_controller_common_t *)(ctx);
mb_base_t *pmb_obj = (mb_base_t *)mb_controller->mb_base;
MB_RETURN_ON_FALSE(pmb_obj, ESP_ERR_INVALID_STATE, TAG,
"Controller interface is not correctly initialized.");
if (pmb_obj->descr.is_master) {
ret = mbm_delete_handler(mb_controller->mb_base, func_code);
} else {
ret = mbs_delete_handler(mb_controller->mb_base, func_code);
}
return MB_ERR_TO_ESP_ERR(ret);
}
/**
* Get number of registered command handlers for the object
*/
esp_err_t mbc_get_handler_count(void *ctx, uint16_t *pcount)
{
MB_RETURN_ON_FALSE((ctx && pcount), ESP_ERR_INVALID_STATE, TAG,
"Controller interface is not correctly initialized.");
mb_err_enum_t ret = MB_EINVAL;
mb_controller_common_t *mb_controller = (mb_controller_common_t *)(ctx);
mb_base_t *pmb_obj = (mb_base_t *)mb_controller->mb_base;
MB_RETURN_ON_FALSE(pmb_obj, ESP_ERR_INVALID_STATE, TAG,
"Controller interface is not correctly initialized.");
if (pmb_obj->descr.is_master) {
ret = mbm_get_handler_count(mb_controller->mb_base, pcount);
} else {
ret = mbs_get_handler_count(mb_controller->mb_base, pcount);
}
return MB_ERR_TO_ESP_ERR(ret);
}

View File

@ -732,3 +732,4 @@ esp_err_t mbc_master_set_param_data(void* dest, void* src, mb_descr_type_t param
}
return err;
}

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -76,6 +76,47 @@ extern "C" {
*(uint8_t *)(dst + 0) = *(uint8_t *)(src)++; \
}
#define mb_err_var esp_err##__func__##__line__
#define esp_err_var mb_error##__func__##__line__
#define MB_ERR_TO_ESP_ERR(error_code) (__extension__( \
{ \
mb_err_enum_t mb_err_var = (mb_err_enum_t)error_code; \
esp_err_t esp_err_var = ESP_FAIL; \
switch(mb_err_var) { \
case MB_ENOERR: \
esp_err_var = ESP_OK; \
break; \
case MB_ENOREG: \
esp_err_var = ESP_ERR_NOT_SUPPORTED; \
break; \
case MB_ETIMEDOUT: \
esp_err_var = ESP_ERR_TIMEOUT; \
break; \
case MB_EINVAL: \
esp_err_var = ESP_ERR_INVALID_ARG; \
break; \
case MB_EILLFUNC: \
esp_err_var = ESP_ERR_INVALID_RESPONSE; \
break; \
case MB_ERECVDATA: \
esp_err_var = ESP_ERR_INVALID_RESPONSE; \
break; \
case MB_EBUSY: \
case MB_EILLSTATE: \
case MB_EPORTERR: \
case MB_ENORES: \
case MB_ENOCONN: \
esp_err_var = ESP_ERR_INVALID_STATE; \
break; \
default: \
ESP_LOGE(TAG, "%s: Incorrect return code (%x) ", __FUNCTION__, (int)mb_err_var); \
esp_err_var = ESP_FAIL; \
break; \
} \
(esp_err_var); \
} \
))
/**
* @brief Types of actual Modbus implementation
*/
@ -117,45 +158,6 @@ typedef enum {
MB_PARAM_UNKNOWN = 0xFF
} mb_param_type_t;
#define mb_err_var esp_err##__func__##__line__
#define esp_err_var mb_error##__func__##__line__
#define MB_ERR_TO_ESP_ERR(error_code) (__extension__( \
{ \
mb_err_enum_t mb_err_var = (mb_err_enum_t)error_code; \
esp_err_t esp_err_var = ESP_FAIL; \
switch(mb_err_var) { \
case MB_ENOERR: \
esp_err_var = ESP_OK; \
break; \
case MB_ENOREG: \
esp_err_var = ESP_ERR_NOT_SUPPORTED; \
break; \
case MB_ETIMEDOUT: \
esp_err_var = ESP_ERR_TIMEOUT; \
break; \
case MB_EILLFUNC: \
case MB_EINVAL: \
esp_err_var = ESP_ERR_INVALID_RESPONSE; \
break; \
case MB_ERECVDATA: \
esp_err_var = ESP_ERR_INVALID_RESPONSE; \
break; \
case MB_EBUSY: \
case MB_EILLSTATE: \
case MB_EPORTERR: \
case MB_ENORES: \
case MB_ENOCONN: \
esp_err_var = ESP_ERR_INVALID_STATE; \
break; \
default: \
ESP_LOGE(TAG, "%s: Incorrect return code (%x) ", __FUNCTION__, (int)mb_err_var); \
esp_err_var = ESP_FAIL; \
break; \
} \
(esp_err_var); \
} \
))
typedef enum _mb_comm_mode mb_mode_type_t;
typedef struct mb_base_t mb_base_t;
@ -168,17 +170,17 @@ typedef enum _addr_type_enum mb_tcp_addr_type_t;
/*!
* \brief Modbus TCP communication options structure.
*/
typedef struct _port_tcp_opts mb_tcp_opts_t;
typedef struct port_tcp_opts_s mb_tcp_opts_t;
/*!
* \brief Modbus serial communication options structure.
*/
typedef struct _port_serial_opts mb_serial_opts_t;
typedef struct port_serial_opts_s mb_serial_opts_t;
/*!
* \brief Modbus common communication options structure.
*/
typedef struct _port_common_opts mb_common_opts_t;
typedef struct port_common_opts_s mb_common_opts_t;
/**
* @brief Device communication structure to setup Modbus controller
@ -199,7 +201,70 @@ typedef union
* common interface method types
*/
typedef esp_err_t (*iface_create_fp)(mb_communication_info_t*, void **); /*!< Interface method create */
typedef esp_err_t (*iface_method_default_fp)(void *ctx); /*!< Interface method default prototype */
typedef esp_err_t (*iface_method_default_fp)(void *ctx); /*!< Interface method default prototype */
/**
* @brief Modbus controller common interface structure
*/
typedef struct {
mb_base_t *mb_base; /*!< base object pointer */
} mb_controller_common_t;
/**
* @brief The function registers the new function handler for specified command
* and allows to override the existing handler for the the controller object.
*
* @param[in] ctx context pointer to the controller object (master or slave)
* @param[in] func_code the function code for the handler
* @param[in] phandler the pointer to function handler being used for command
*
* @return
* - esp_err_t ESP_OK - the function handler is correctly set the handler
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function or parameter descriptor
* - esp_err_t ESP_ERR_INVALID_STATE - can not register non-existent handler or can not
* - esp_err_t ESP_ERR_NOT_FOUND - the requested slave is not found (not connected or not configured)
*/
esp_err_t mbc_set_handler(void *ctx, uint8_t func_code, mb_fn_handler_fp phandler);
/**
* @brief The function gets function handler for specified command from the controller object handler table.
*
* @param[in] ctx context pointer to the controller object (master or slave)
* @param[in] func_code the function code for the handler
* @param[out] phandler the pointer to function handler being returned
*
* @return
* - esp_err_t ESP_OK - the function handler is returned
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function or parameter descriptor
* esp_err_t ESP_ERR_INVALID_STATE - can not register non-existent handler or incorrect configuration
*/
esp_err_t mbc_get_handler(void *ctx, uint8_t func_code, mb_fn_handler_fp *phandler);
/**
* @brief The function deletes function handler for specified command from the controller object command handler table.
*
* @param[in] ctx context pointer to the controller object (master or slave)
* @param[in] func_code the function code for the handler
*
* @return
* - esp_err_t ESP_OK - the function handler is deleted
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function or parameter descriptor
* esp_err_t ESP_ERR_INVALID_STATE - can not register non-existent handler or incorrect configuration
*/
esp_err_t mbc_delete_handler(void *ctx, uint8_t func_code);
/**
* @brief The function gets the number of registered function handlers for the controller object.
*
* @param[in] ctx context pointer to the controller object (master or slave)
* @param[out] pcount the pointer to returned counter
*
* @return
* - esp_err_t ESP_OK - the function handler is returned in the
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function or parameter descriptor
* esp_err_t ESP_ERR_INVALID_STATE - can not register non-existent handler or incorrect configuration
*/
esp_err_t mbc_get_handler_count(void *ctx, uint16_t *pcount);
#ifdef __cplusplus
}

View File

@ -14,7 +14,10 @@
#include "driver/uart.h" // for uart port number defines
#include "sdkconfig.h" // for KConfig options
#include "esp_modbus_common.h"
#include "esp_modbus_master.h"
#include "esp_modbus_slave.h"

View File

@ -18,6 +18,7 @@ extern "C" {
#include "mb_common.h" // for mb_base_t
#include "esp_modbus_slave.h" // for public type defines
#include "mb_slave.h"
/* ----------------------- Defines ------------------------------------------*/
#define MB_INST_MIN_SIZE (1) // The minimal size of Modbus registers area in bytes

View File

@ -256,9 +256,20 @@ static esp_err_t mbc_serial_master_send_request(void *ctx, mb_param_request_t *r
break;
#endif
default:
ESP_LOGE(TAG, "%s: Incorrect or unsupported function in request (%u) ",
__FUNCTION__, mb_command);
mb_error = MB_ENOREG;
mb_fn_handler_fp phandler = NULL;
// check registered function handler
mb_error = mbm_get_handler(mbm_controller_iface->mb_base, mb_command, &phandler);
if (mb_error == MB_ENOERR) {
// send the request for custom command
mb_error = mbm_rq_custom(mbm_controller_iface->mb_base, mb_slave_addr, mb_command,
data_ptr, (uint16_t)(mb_size << 1),
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
ESP_LOGD(TAG, "%s: Send custom request (%u)", __FUNCTION__, mb_command);
} else {
ESP_LOGE(TAG, "%s: Incorrect or unsupported function in request (%u), error = (0x%x)",
__FUNCTION__, mb_command, (int)mb_error);
mb_error = MB_ENOREG;
}
break;
}
} else {

View File

@ -255,9 +255,19 @@ static esp_err_t mbc_tcp_master_send_request(void *ctx, mb_param_request_t *requ
break;
#endif
default:
ESP_LOGE(TAG, "%s: Incorrect or unsupported function in request (%u) ",
__FUNCTION__, (unsigned)mb_command);
mb_error = MB_ENOREG;
mb_fn_handler_fp phandler = NULL;
// check registered function handler
mb_error = mbm_get_handler(mbm_controller_iface->mb_base, mb_command, &phandler);
if (mb_error == MB_ENOERR) {
// send the request for custom command
mb_error = mbm_rq_custom(mbm_controller_iface->mb_base, mb_slave_addr, mb_command,
data_ptr, (uint16_t)(mb_size << 1),
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
ESP_LOGD(TAG, "%s: Send custom request (%u)", __FUNCTION__, mb_command);
} else {
ESP_LOGE(TAG, "%s: Incorrect or unsupported function in request (%u), error = (0x%x) ", __FUNCTION__, mb_command, (int)mb_error);
mb_error = MB_ENOREG;
}
break;
}
} else {
@ -588,7 +598,6 @@ static esp_err_t mbc_tcp_master_delete(void *ctx)
MB_RETURN_ON_FALSE((mbc_tcp_master_stop(ctx) == ESP_OK),
ESP_ERR_INVALID_STATE, TAG, "mb stack stop failure.");
}
mbm_iface->is_active = false;
vTaskDelete(mbm_opts->task_handle);
mbm_opts->task_handle = NULL;

View File

@ -5,8 +5,8 @@
*/
#pragma once
#include "mb_config.h"
#include "mb_types.h"
#include "sdkconfig.h"
#if __has_include("esp_idf_version.h")
#include "esp_idf_version.h"
@ -39,7 +39,7 @@ typedef enum _mb_comm_mode mb_mode_type_t;
#include "driver/uart.h"
struct _port_serial_opts {
struct port_serial_opts_s {
mb_mode_type_t mode; /*!< Modbus communication mode */
uart_port_t port; /*!< Modbus communication port (UART) number */
uint8_t uid; /*!< Modbus slave address field (dummy for master) */
@ -51,7 +51,7 @@ struct _port_serial_opts {
uart_parity_t parity; /*!< Modbus UART parity settings */
} __attribute__((__packed__));
typedef struct _port_serial_opts mb_serial_opts_t;
typedef struct port_serial_opts_s mb_serial_opts_t;
#endif
@ -62,7 +62,7 @@ typedef enum _addr_type_enum {
} mb_addr_type_t;
struct _port_common_opts {
struct port_common_opts_s {
mb_mode_type_t mode; /*!< Modbus communication mode */
uint16_t port; /*!< Modbus communication port (UART) number */
uint8_t uid; /*!< Modbus slave address field (dummy for master) */
@ -70,7 +70,7 @@ struct _port_common_opts {
uint64_t test_tout_us; /*!< Modbus test timeout (reserved) */
} __attribute__((__packed__));
struct _port_tcp_opts {
struct port_tcp_opts_s {
mb_mode_type_t mode; /*!< Modbus communication mode */
uint16_t port; /*!< Modbus communication port (UART) number */
uint8_t uid; /*!< Modbus slave address field (dummy for master) */
@ -83,7 +83,7 @@ struct _port_tcp_opts {
bool start_disconnected; /*!< (Master only option) do not wait for connection to all nodes before polling */
} __attribute__((__packed__));
typedef struct _port_tcp_opts mb_tcp_opts_t;
typedef struct port_tcp_opts_s mb_tcp_opts_t;
// The common object descriptor struture (common for mb, transport, port objects)
struct _obj_descr {

View File

@ -85,6 +85,26 @@ typedef enum _mb_event_enum {
EV_MASTER_PROCESS_SUCCESS = 0x0400 /*!< Master error process. */
} mb_event_enum_t;
/*! \ingroup modbus
* \brief Modbus exception types used in the stack.
*/
typedef enum _mb_exception_enum
{
MB_EX_NONE = 0x00,
MB_EX_ILLEGAL_FUNCTION = 0x01,
MB_EX_ILLEGAL_DATA_ADDRESS = 0x02,
MB_EX_ILLEGAL_DATA_VALUE = 0x03,
MB_EX_SLAVE_DEVICE_FAILURE = 0x04,
MB_EX_ACKNOWLEDGE = 0x05,
MB_EX_SLAVE_BUSY = 0x06,
MB_EX_MEMORY_PARITY_ERROR = 0x08,
MB_EX_GATEWAY_PATH_FAILED = 0x0A,
MB_EX_GATEWAY_TGT_FAILED = 0x0B,
MB_EX_CRITICAL = 0xFF
} mb_exception_t;
typedef mb_exception_t (*mb_fn_handler_fp)(void *, uint8_t *frame_ptr, uint16_t *len_buf);
/*! \ingroup modbus
* \brief Error event type
*/

View File

@ -0,0 +1,110 @@
/*
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mb_common.h"
#include "mb_proto.h"
/* ----------------------- Defines ------------------------------------------*/
#define MB_IS_VALID_FUNC_CODE(fc) ((fc) >= MB_FUNC_CODE_MIN && (fc) <= MB_FUNC_CODE_MAX)
static const char TAG[] __attribute__((unused)) = "MB_FUNC_HANDLING";
mb_err_enum_t mb_set_handler(handler_descriptor_t *pdescriptor, uint8_t func_code, mb_fn_handler_fp phandler)
{
MB_RETURN_ON_FALSE((pdescriptor && phandler && pdescriptor->instance), MB_EINVAL, TAG, "invalid arguments.");
MB_RETURN_ON_FALSE(MB_IS_VALID_FUNC_CODE(func_code), MB_EINVAL, TAG,
"invalid function code (0x%x)", (int)func_code);
mb_command_entry_t *pitem = NULL;
LIST_FOREACH(pitem, &pdescriptor->head, entries) {
if (pitem && pitem->func_code == func_code) {
// The handler for the function already exists, rewrite it.
pitem->handler = phandler;
ESP_LOGD(TAG, "Inst: %p, set handler: 0x%x, %p", pdescriptor->instance, pitem->func_code, pitem->handler);
return MB_ENOERR;
}
}
// Insert new handler entry into list
if (pdescriptor->count >= MB_FUNC_HANDLERS_MAX) {
return MB_ENORES;
} else {
pdescriptor->count += 1;
}
pitem = (mb_command_entry_t *) heap_caps_malloc(sizeof(mb_command_entry_t), MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT);
MB_RETURN_ON_FALSE(pitem, MB_ENORES, TAG, "mb can not allocate memory for command handler 0x%x.", func_code);
pitem->func_code = func_code;
pitem->handler = phandler;
LIST_INSERT_HEAD(&pdescriptor->head, pitem, entries);
ESP_LOGD(TAG, "Inst: %p, add handler: 0x%x, %p", pdescriptor->instance, pitem->func_code, pitem->handler);
return MB_ENOERR;
}
mb_err_enum_t mb_get_handler(handler_descriptor_t *pdescriptor, uint8_t func_code, mb_fn_handler_fp *phandler)
{
MB_RETURN_ON_FALSE((pdescriptor && phandler && pdescriptor->instance), MB_EINVAL, TAG, "invalid arguments.");
MB_RETURN_ON_FALSE(MB_IS_VALID_FUNC_CODE(func_code), MB_EINVAL, TAG,
"invalid function code (0x%x)", (int)func_code);
mb_command_entry_t *pitem = NULL;
LIST_FOREACH(pitem, &pdescriptor->head, entries) {
if (pitem && pitem->func_code == func_code) {
*phandler = pitem->handler;
ESP_LOGD(TAG, "Inst: %p, get handler: 0x%x, %p", pdescriptor->instance, pitem->func_code, pitem->handler);
return MB_ENOERR;
}
}
return MB_ENORES;
}
// Helper function to get handler
mb_err_enum_t mb_delete_handler(handler_descriptor_t *pdescriptor, uint8_t func_code)
{
MB_RETURN_ON_FALSE((pdescriptor && pdescriptor->instance), MB_EINVAL, TAG, "invalid arguments.");
MB_RETURN_ON_FALSE(MB_IS_VALID_FUNC_CODE(func_code), MB_EINVAL, TAG,
"invalid function code (0x%x)", (int)func_code);
if (LIST_EMPTY(&pdescriptor->head)) {
return MB_EINVAL;
}
mb_command_entry_t *pitem = NULL;
mb_command_entry_t *ptemp = NULL;
LIST_FOREACH_SAFE(pitem, &pdescriptor->head, entries, ptemp) {
if (pitem && pitem->func_code == func_code) {
ESP_LOGD(TAG, "Inst: %p, remove handler: 0x%x, %p", pdescriptor->instance, pitem->func_code, pitem->handler);
LIST_REMOVE(pitem, entries);
free(pitem);
if (pdescriptor->count) {
pdescriptor->count--;
}
return MB_ENOERR;
}
}
return MB_ENORES;
}
// Helper function to close all registered handlers in the list
mb_err_enum_t mb_delete_command_handlers(handler_descriptor_t *pdescriptor)
{
MB_RETURN_ON_FALSE((pdescriptor), MB_EINVAL, TAG, "invalid arguments.");
if (LIST_EMPTY(&pdescriptor->head)) {
return MB_EINVAL;
}
mb_command_entry_t *pitem = NULL;
while ((pitem = LIST_FIRST(&pdescriptor->head))) {
ESP_LOGD(TAG, "Inst: %p, close handler: 0x%x, %p", pdescriptor->instance, pitem->func_code, pitem->handler);
LIST_REMOVE(pitem, entries);
free(pitem);
if (pdescriptor->count) {
pdescriptor->count--;
}
}
return MB_ENOERR;
}

View File

@ -44,6 +44,39 @@
/* ----------------------- Start implementation -----------------------------*/
mb_exception_t mb_error_to_exception(mb_err_enum_t error_code);
/**
* This function will request read coil.
*
* @param uid slave address
* @param fc custom function code
* @param buf additional data to send
* @param buf_size size of data to send
* @param timeout timeout (-1 will waiting forever)
*
* @return error code (mb_err_enum_t)
*/
mb_err_enum_t mbm_rq_custom(mb_base_t *inst, uint8_t uid, uint8_t fc, uint8_t *buf, uint16_t buf_size, uint32_t tout)
{
uint8_t *mb_frame_ptr;
if (!buf || (uid > MB_ADDRESS_MAX) || (buf_size >= (MB_BUFFER_SIZE - 2))) {
return MB_EINVAL;
}
if (!mb_port_event_res_take(inst->port_obj, tout)) {
return MB_EBUSY;
}
inst->get_send_buf(inst, &mb_frame_ptr);
inst->set_dest_addr(inst, uid);
mb_frame_ptr[MB_PDU_FUNC_OFF] = fc;
memcpy(&mb_frame_ptr[MB_PDU_DATA_OFF], buf, buf_size);
inst->set_send_len(inst, MB_PDU_SIZE_MIN + buf_size);
(void)mb_port_event_post(inst->port_obj, EVENT(EV_FRAME_TRANSMIT | EV_TRANS_START));
return mb_port_event_wait_req_finish(inst->port_obj);
}
mb_err_enum_t mbm_rq_report_slave_id(mb_base_t *inst, uint8_t slave_addr, uint32_t timeout)
{
uint8_t *mb_frame_ptr = NULL;

View File

@ -28,10 +28,11 @@
*
* File: $Id: mbutils.c, v 1.6 2007/02/18 23:49:07 wolti Exp $
*/
#include <mb_common.h>
#include <mb_proto.h>
#include "mb_common.h"
#include "mb_proto.h"
/* ----------------------- Defines ------------------------------------------*/
#define BITS_uint8_t 8U
#define BITS_uint8_t (8U)
static const char TAG[] __attribute__((unused)) = "MB_UTILS";
/* ----------------------- Start implementation -----------------------------*/
void mb_util_set_bits(uint8_t *byte_buf, uint16_t bit_offset, uint8_t but_num, uint8_t value)
@ -125,5 +126,3 @@ mb_exception_t mb_error_to_exception(mb_err_enum_t error_code)
return status;
}

View File

@ -6,6 +6,7 @@
#pragma once
#include <stdint.h>
#include <sys/queue.h>
#include "mb_config.h"
#include "mb_frame.h"
@ -63,6 +64,9 @@ extern "C" {
#endif
#define MB_CAT_BUF_SIZE (100)
#define MB_HANDLER_UNLOCK_TICKS (pdMS_TO_TICKS(5000))
#define SEMA_SECTION(sema, tout) for (int st = (int)xSemaphoreTake(sema, tout); (st > 0); xSemaphoreGive(sema), st = -1)
#define MB_STR_CAT(pref, message) (__extension__( \
{ \
@ -104,6 +108,24 @@ extern "C" {
} \
))
/**
* @brief Modbus function handlers entry
*/
typedef struct mb_command_entry_s {
uint8_t func_code; /*!< function code */
mb_fn_handler_fp handler; /*!< handler function pointer */
LIST_ENTRY(mb_command_entry_s) entries; /*!< command handler entry */
} mb_command_entry_t;
typedef LIST_HEAD(handler_head, mb_command_entry_s) handler_head_t;
typedef struct mb_handler_descriptor_s {
SemaphoreHandle_t sema;
void* instance;
handler_head_t head;
uint16_t count;
} handler_descriptor_t;
typedef struct mb_base_t mb_base_t;
typedef struct mb_trans_base_t mb_trans_base_t;
typedef struct mb_port_base_t mb_port_base_t;
@ -152,19 +174,28 @@ struct mb_base_t
mb_rw_callbacks_t rw_cbs;
};
typedef struct _port_tcp_opts mb_tcp_opts_t;
// Helper functions for command handlers registration
mb_err_enum_t mb_set_handler(handler_descriptor_t *pdescriptor, uint8_t func_code, mb_fn_handler_fp phandler);
mb_err_enum_t mb_get_handler(handler_descriptor_t *pdescriptor, uint8_t func_code, mb_fn_handler_fp *phandler);
mb_err_enum_t mb_delete_handler(handler_descriptor_t *pdescriptor, uint8_t func_code);
mb_err_enum_t mb_delete_command_handlers(handler_descriptor_t *pdescriptor);
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
typedef struct _port_serial_opts mb_serial_opts_t;
typedef struct port_serial_opts_s mb_serial_opts_t;
mb_err_enum_t mbs_rtu_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
mb_err_enum_t mbs_ascii_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
#endif
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
typedef struct port_tcp_opts_s mb_tcp_opts_t;
mb_err_enum_t mbs_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj);
#endif
mb_err_enum_t mbs_delete(mb_base_t *inst);
mb_err_enum_t mbs_enable(mb_base_t *inst);
mb_err_enum_t mbs_disable(mb_base_t *inst);

View File

@ -115,7 +115,7 @@ extern "C" {
* the sum of all enabled functions in this file and custom function
* handlers. If set to small adding more functions will fail.
*/
#define MB_FUNC_HANDLERS_MAX (16)
#define MB_FUNC_HANDLERS_MAX (CONFIG_FMB_FUNC_HANDLERS_MAX)
/*! \brief Number of bytes which should be allocated for the <em>Report Slave ID
* </em>command.

View File

@ -12,6 +12,9 @@
extern "C" {
#endif
#define MB_FUNC_CODE_MIN (0x01)
#define MB_FUNC_CODE_MAX (0x7F)
typedef struct mb_base_t mb_base_t;
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED

View File

@ -10,7 +10,7 @@
extern "C" {
#endif
typedef struct mb_base_t mb_base_t; /*!< Type of moddus object */
typedef struct mb_base_t mb_base_t; /*!< Type of modbus object */
mb_err_enum_t mbm_rq_read_inp_reg(mb_base_t *inst, uint8_t snd_addr, uint16_t reg_addr, uint16_t reg_num, uint32_t tout);
mb_err_enum_t mbm_rq_write_holding_reg(mb_base_t *inst, uint8_t snd_addr, uint16_t reg_addr, uint16_t reg_data, uint32_t tout);
@ -26,6 +26,7 @@ mb_err_enum_t mbm_rq_write_multi_coils(mb_base_t *inst, uint8_t snd_addr, uint16
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED
mb_err_enum_t mbm_rq_report_slave_id(mb_base_t *inst, uint8_t slave_addr, uint32_t timeout);
mb_exception_t mbm_fn_report_slave_id(mb_base_t *inst, uint8_t * pframe, uint16_t *usLen);
mb_err_enum_t mbm_rq_custom(mb_base_t *inst, uint8_t uid, uint8_t fc, uint8_t *buf, uint16_t buf_size, uint32_t tout);
/*! \ingroup modbus_registers
* \brief The common callback function used to transfer common data as bytes from command buffer in little endian format.
@ -44,6 +45,18 @@ mb_exception_t mbm_fn_report_slave_id(mb_base_t *inst, uint8_t * pframe, uint16_
mb_err_enum_t mbc_reg_common_cb(mb_base_t *inst, uint8_t *pdata, uint16_t address, uint16_t bytes);
#endif
// The function to register custom function handler for master
mb_err_enum_t mbm_set_handler(mb_base_t *inst, uint8_t func_code, mb_fn_handler_fp phandler);
// The helper function to get custom function handler for master
mb_err_enum_t mbm_get_handler(mb_base_t *inst, uint8_t func_code, mb_fn_handler_fp *phandler);
// The helper function to delete custom function handler for master
mb_err_enum_t mbm_delete_handler(mb_base_t *inst, uint8_t func_code);
// The helper function to get count of handlers for master
mb_err_enum_t mbm_get_handler_count(mb_base_t *inst, uint16_t *pcount);
#ifdef __cplusplus
}
#endif

View File

@ -6,6 +6,7 @@
#pragma once
#include <stdint.h>
#include "mb_func.h"
#ifdef __cplusplus
extern "C" {
@ -33,27 +34,11 @@ typedef enum _mb_commands_enum
MB_FUNC_DIAG_GET_COM_EVENT_CNT = ( 11 ),
MB_FUNC_DIAG_GET_COM_EVENT_LOG = ( 12 ),
MB_FUNC_OTHER_REPORT_SLAVEID = ( 17 ),
MB_FUNC_ERROR = ( 128U ),
MB_FUNC_ERROR = ( 0x80 )
} mb_commands_t;
/* ----------------------- Type definitions ---------------------------------*/
typedef enum
{
MB_EX_NONE = 0x00,
MB_EX_ILLEGAL_FUNCTION = 0x01,
MB_EX_ILLEGAL_DATA_ADDRESS = 0x02,
MB_EX_ILLEGAL_DATA_VALUE = 0x03,
MB_EX_SLAVE_DEVICE_FAILURE = 0x04,
MB_EX_ACKNOWLEDGE = 0x05,
MB_EX_SLAVE_BUSY = 0x06,
MB_EX_MEMORY_PARITY_ERROR = 0x08,
MB_EX_GATEWAY_PATH_FAILED = 0x0A,
MB_EX_GATEWAY_TGT_FAILED = 0x0B
} mb_exception_t;
typedef mb_exception_t (*mb_fn_handler_fp)(void *, uint8_t *frame_ptr, uint16_t *len_buf);
typedef struct
{
uint8_t func_code;

View File

@ -15,6 +15,18 @@ extern "C" {
mb_exception_t mbs_fn_report_slave_id(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *plen_buf);
#endif
// The helper function to register custom function handler for slave
mb_err_enum_t mbs_set_handler(mb_base_t *inst, uint8_t func_code, mb_fn_handler_fp phandler);
// The helper function to get custom function handler for slave
mb_err_enum_t mbs_get_handler(mb_base_t *inst, uint8_t func_code, mb_fn_handler_fp *phandler);
// The helper function to delete custom function handler for slave
mb_err_enum_t mbs_delete_handler(mb_base_t *inst, uint8_t func_code);
// The helper function to get count of handlers for slave
mb_err_enum_t mbs_get_handler_count(mb_base_t *inst, uint16_t *pcount);
#ifdef __cplusplus
}
#endif

View File

@ -16,48 +16,20 @@
static const char *TAG = "mb_object.master";
#if (MB_MASTER_ASCII_ENABLED || MB_MASTER_RTU_ENABLED || MB_MASTER_TCP_ENABLED)
#if (MB_MASTER_ASCII_ENABLED || MB_MASTER_RTU_ENABLED)
typedef struct port_serial_opts_s mb_serial_opts_t;
static const mb_fn_handler_t master_handlers[MB_FUNC_HANDLERS_MAX] =
{
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED
{MB_FUNC_OTHER_REPORT_SLAVEID, (void *)mbm_fn_report_slave_id},
#endif
#if MB_FUNC_READ_INPUT_ENABLED
{MB_FUNC_READ_INPUT_REGISTER, (void *)mbm_fn_read_inp_reg},
#endif
#if MB_FUNC_READ_HOLDING_ENABLED
{MB_FUNC_READ_HOLDING_REGISTER, (void *)mbm_fn_read_holding_reg},
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
{MB_FUNC_WRITE_MULTIPLE_REGISTERS, (void *)mbm_fn_write_multi_holding_reg},
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED
{MB_FUNC_WRITE_REGISTER, (void *)mbm_fn_write_holding_reg},
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED
{MB_FUNC_READWRITE_MULTIPLE_REGISTERS, (void *)mbm_fn_rw_multi_holding_regs},
#endif
#if MB_FUNC_READ_COILS_ENABLED
{MB_FUNC_READ_COILS, (void *)mbm_fn_read_coils},
#endif
#if MB_FUNC_WRITE_COIL_ENABLED
{MB_FUNC_WRITE_SINGLE_COIL, (void *)mbm_fn_write_coil},
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED
{MB_FUNC_WRITE_MULTIPLE_COILS, (void *)mbm_fn_write_multi_coils},
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED
{MB_FUNC_READ_DISCRETE_INPUTS, (void *)mbm_fn_read_discrete_inputs},
#endif
};
#if (MB_MASTER_ASCII_ENABLED || MB_MASTER_RTU_ENABLED || MB_MASTER_TCP_ENABLED)
typedef struct
{
mb_base_t base;
mb_comm_mode_t cur_mode;
mb_state_enum_t cur_state;
const mb_fn_handler_t *func_handlers;
uint8_t *rcv_frame;
uint8_t *snd_frame;
uint16_t pdu_snd_len;
@ -67,6 +39,7 @@ typedef struct
mb_exception_t exception;
uint8_t master_dst_addr;
uint64_t curr_trans_id;
handler_descriptor_t handler_descriptor;
} mbm_object_t;
mb_err_enum_t mbm_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj);
@ -82,7 +55,132 @@ static void mbm_set_dest_addr(mb_base_t *inst, uint8_t dest_addr);
static uint8_t mbm_get_dest_addr(mb_base_t *inst);
static void mbm_get_pdu_send_buf(mb_base_t *inst, uint8_t **pbuf);
typedef struct _port_serial_opts mb_serial_opts_t;
mb_err_enum_t mbm_set_handler(mb_base_t *inst, uint8_t func_code, mb_fn_handler_fp phandler)
{
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
mb_err_enum_t status = MB_EILLSTATE;
SEMA_SECTION(mbm_obj->handler_descriptor.sema, MB_HANDLER_UNLOCK_TICKS) {
status = mb_set_handler(&mbm_obj->handler_descriptor, func_code, phandler);
}
return status;
}
// The helper function to register custom function handler for master
mb_err_enum_t mbm_get_handler(mb_base_t *inst, uint8_t func_code, mb_fn_handler_fp *phandler)
{
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
mb_err_enum_t status = MB_EILLSTATE;
if (phandler) {
SEMA_SECTION(mbm_obj->handler_descriptor.sema, MB_HANDLER_UNLOCK_TICKS) {
status = mb_get_handler(&mbm_obj->handler_descriptor, func_code, phandler);
}
}
return status;
}
mb_err_enum_t mbm_delete_handler(mb_base_t *inst, uint8_t func_code)
{
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
mb_err_enum_t status = MB_EILLSTATE;
SEMA_SECTION(mbm_obj->handler_descriptor.sema, MB_HANDLER_UNLOCK_TICKS) {
status = mb_delete_handler(&mbm_obj->handler_descriptor, func_code);
}
return status;
}
mb_err_enum_t mbm_get_handler_count(mb_base_t *inst, uint16_t *pcount)
{
MB_RETURN_ON_FALSE((pcount && inst), MB_EINVAL, TAG, "get handler count wrong arguments.");
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
SEMA_SECTION(mbm_obj->handler_descriptor.sema, MB_HANDLER_UNLOCK_TICKS) {
*pcount = mbm_obj->handler_descriptor.count;
}
return MB_ENOERR;
}
static mb_err_enum_t mbm_check_invoke_handler(mb_base_t *inst, uint8_t func_code, uint8_t *pbuf, uint16_t *plen)
{
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
mb_exception_t exception = MB_EX_ILLEGAL_FUNCTION;
if (!func_code || !pbuf) {
return MB_EX_ILLEGAL_FUNCTION;
}
if (func_code & MB_FUNC_ERROR) {
exception = (mb_exception_t)pbuf[MB_PDU_DATA_OFF];
return exception;
}
SEMA_SECTION(mbm_obj->handler_descriptor.sema, MB_HANDLER_UNLOCK_TICKS) {
mb_fn_handler_fp phandler = NULL;
mb_err_enum_t status = mb_get_handler(&mbm_obj->handler_descriptor, func_code, &phandler);
if ((status == MB_ENOERR) && phandler) {
exception = phandler(inst, pbuf, plen);
}
}
return exception;
}
static mb_err_enum_t mbm_register_default_handlers(mb_base_t *inst)
{
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
mb_err_enum_t err = MB_EILLSTATE;
LIST_INIT(&mbm_obj->handler_descriptor.head);
mbm_obj->handler_descriptor.sema = xSemaphoreCreateBinary();
(void)xSemaphoreGive(mbm_obj->handler_descriptor.sema);
mbm_obj->handler_descriptor.instance = (void *)inst->descr.parent;
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED
err = mbm_set_handler(inst, MB_FUNC_OTHER_REPORT_SLAVEID, (void *)mbm_fn_report_slave_id);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_READ_INPUT_ENABLED
err = mbm_set_handler(inst, MB_FUNC_READ_INPUT_REGISTER, (void *)mbm_fn_read_inp_reg);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_READ_HOLDING_ENABLED
err = mbm_set_handler(inst, MB_FUNC_READ_HOLDING_REGISTER, (void *)mbm_fn_read_holding_reg);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
err = mbm_set_handler(inst, MB_FUNC_WRITE_MULTIPLE_REGISTERS, (void *)mbm_fn_write_multi_holding_reg);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED
err = mbm_set_handler(inst, MB_FUNC_WRITE_REGISTER, (void *)mbm_fn_write_holding_reg);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED
err = mbm_set_handler(inst, MB_FUNC_READWRITE_MULTIPLE_REGISTERS, (void *)mbm_fn_rw_multi_holding_regs);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_READ_COILS_ENABLED
err = mbm_set_handler(inst, MB_FUNC_READ_COILS, (void *)mbm_fn_read_coils);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_WRITE_COIL_ENABLED
err = mbm_set_handler(inst, MB_FUNC_WRITE_SINGLE_COIL, (void *)mbm_fn_write_coil);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED
err = mbm_set_handler(inst, MB_FUNC_WRITE_MULTIPLE_COILS, (void *)mbm_fn_write_multi_coils);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED
err = mbm_set_handler(inst, MB_FUNC_READ_DISCRETE_INPUTS, (void *)mbm_fn_read_discrete_inputs);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
return MB_ENOERR;
}
static mb_err_enum_t mbm_unregister_handlers(mb_base_t *inst)
{
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
(void)xSemaphoreTake(mbm_obj->handler_descriptor.sema, MB_HANDLER_UNLOCK_TICKS);
ESP_LOGD(TAG, "Close %s command handlers.", mbm_obj->base.descr.parent_name);
(void)mb_delete_command_handlers(&mbm_obj->handler_descriptor);
mbm_obj->handler_descriptor.instance = NULL;
(void)xSemaphoreGive(mbm_obj->handler_descriptor.sema);
vSemaphoreDelete(mbm_obj->handler_descriptor.sema);
return MB_ENOERR;
}
#if (MB_MASTER_RTU_ENABLED)
@ -121,7 +219,6 @@ mb_err_enum_t mbm_rtu_create(mb_serial_opts_t *ser_opts, void **in_out_obj)
ret = mbm_rtu_transp_create(ser_opts, (void **)&transp_obj);
MB_GOTO_ON_FALSE((transp_obj && (ret == MB_ENOERR)), MB_EILLSTATE, error,
TAG, "transport creation, err: %d", (int)ret);
mbm_obj->func_handlers = master_handlers;
mbm_obj->cur_mode = ser_opts->mode;
mbm_obj->cur_state = STATE_DISABLED;
transp_obj->get_tx_frm(transp_obj, (uint8_t **)&mbm_obj->snd_frame);
@ -129,6 +226,9 @@ mb_err_enum_t mbm_rtu_create(mb_serial_opts_t *ser_opts, void **in_out_obj)
mbm_obj->curr_trans_id = 0;
mbm_obj->base.port_obj = transp_obj->port_obj;
mbm_obj->base.transp_obj = transp_obj;
ret = mbm_register_default_handlers(&mbm_obj->base);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EILLSTATE, error,
TAG, "default handlers registration fail, err: %d", (int)ret);
*in_out_obj = (void *)&(mbm_obj->base);
ESP_LOGD(TAG, "created object %s", mbm_obj->base.descr.parent_name);
return MB_ENOERR;
@ -137,6 +237,7 @@ error:
if (transp_obj) {
mbm_rtu_transp_delete(transp_obj);
}
(void)mbm_unregister_handlers(&mbm_obj->base);
free(mbm_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(mbm_obj->base.lock);
free(mbm_obj);
@ -183,7 +284,9 @@ mb_err_enum_t mbm_ascii_create(mb_serial_opts_t *ser_opts, void **in_out_obj)
ret = mbm_ascii_transp_create(ser_opts, (void **)&transp_obj);
MB_GOTO_ON_FALSE((transp_obj && (ret == MB_ENOERR)), MB_EILLSTATE, error,
TAG, "transport creation, err: %d", (int)ret);
mbm_obj->func_handlers = master_handlers;
ret = mbm_register_default_handlers(&mbm_obj->base);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EILLSTATE, error,
TAG, "default handlers registration fail, err: %d", (int)ret);
mbm_obj->cur_mode = ser_opts->mode;
mbm_obj->cur_state = STATE_DISABLED;
transp_obj->get_tx_frm(transp_obj, (uint8_t **)&mbm_obj->snd_frame);
@ -199,6 +302,7 @@ error:
{
mbm_ascii_transp_delete(transp_obj);
}
(void)mbm_unregister_handlers(&mbm_obj->base);
free(mbm_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(mbm_obj->base.lock);
free(mbm_obj);
@ -245,7 +349,9 @@ mb_err_enum_t mbm_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj)
ret = mbm_tcp_transp_create(tcp_opts, (void **)&transp_obj);
MB_GOTO_ON_FALSE((transp_obj && (ret == MB_ENOERR)), MB_EILLSTATE, error,
TAG, "transport creation, err: %d", (int)ret);
mbm_obj->func_handlers = master_handlers;
ret = mbm_register_default_handlers(&mbm_obj->base);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EILLSTATE, error,
TAG, "default handlers registration fail, err: %d", (int)ret);
mbm_obj->cur_mode = tcp_opts->mode;
mbm_obj->cur_state = STATE_DISABLED;
transp_obj->get_tx_frm(transp_obj, (uint8_t **)&mbm_obj->snd_frame);
@ -260,6 +366,7 @@ error:
if (transp_obj) {
mbm_tcp_transp_delete(transp_obj);
}
(void)mbm_unregister_handlers(&mbm_obj->base);
free(mbm_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(mbm_obj->base.lock);
free(mbm_obj);
@ -271,7 +378,7 @@ error:
mb_err_enum_t mbm_delete(mb_base_t *inst)
{
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);;
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
mb_err_enum_t status = MB_ENOERR;
if (mbm_obj->cur_state == STATE_DISABLED) {
if (MB_OBJ(mbm_obj->base.transp_obj)->frm_delete) {
@ -288,6 +395,7 @@ mb_err_enum_t mbm_delete(mb_base_t *inst)
ESP_LOGW(TAG, "%p, Master object ID is not supported!", mbm_obj);
}
#endif
(void)mbm_unregister_handlers(&mbm_obj->base);
// delete the modbus instance
free(mbm_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(inst->lock);
@ -303,7 +411,7 @@ mb_err_enum_t mbm_delete(mb_base_t *inst)
mb_err_enum_t mbm_enable(mb_base_t *inst)
{
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);;
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
mb_err_enum_t status = MB_ENOERR;
CRITICAL_SECTION(inst->lock)
{
@ -322,7 +430,10 @@ mb_err_enum_t mbm_enable(mb_base_t *inst)
mb_err_enum_t mbm_disable(mb_base_t *inst)
{
mb_err_enum_t status = MB_ENOERR;
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);;
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
// Wait for function handler to be unlocked before disable the object
(void)xSemaphoreTake(mbm_obj->handler_descriptor.sema, MB_HANDLER_UNLOCK_TICKS);
(void)xSemaphoreGive(mbm_obj->handler_descriptor.sema);
CRITICAL_SECTION(inst->lock)
{
if (mbm_obj->cur_state == STATE_ENABLED) {
@ -340,7 +451,7 @@ mb_err_enum_t mbm_disable(mb_base_t *inst)
static void mbm_get_pdu_send_buf(mb_base_t *inst, uint8_t **pbuf)
{
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);;
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
MB_OBJ(mbm_obj->base.transp_obj)->get_tx_frm(mbm_obj->base.transp_obj, pbuf);
}
@ -353,7 +464,7 @@ static void mbm_get_pdu_recv_buf(mb_base_t *inst, uint8_t **pbuf)
static void mbm_set_pdu_send_length(mb_base_t *inst, uint16_t length)
{
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);;
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
CRITICAL_SECTION(inst->lock)
{
mbm_obj->pdu_snd_len = length;
@ -362,13 +473,13 @@ static void mbm_set_pdu_send_length(mb_base_t *inst, uint16_t length)
static uint16_t mbm_get_pdu_send_length(mb_base_t *inst)
{
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);;
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
return mbm_obj->pdu_snd_len;
}
static void mbm_set_dest_addr(mb_base_t *inst, uint8_t dest_addr)
{
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);;
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
CRITICAL_SECTION(inst->lock)
{
mbm_obj->master_dst_addr = dest_addr;
@ -377,7 +488,7 @@ static void mbm_set_dest_addr(mb_base_t *inst, uint8_t dest_addr)
static uint8_t mbm_get_dest_addr(mb_base_t *inst)
{
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);;
mbm_object_t *mbm_obj = MB_GET_OBJ_CTX(inst, mbm_object_t, base);
return mbm_obj->master_dst_addr;
}
@ -496,31 +607,18 @@ mb_err_enum_t mbm_poll(mb_base_t *inst)
ESP_LOGD(TAG, MB_OBJ_FMT":EV_EXECUTE", MB_OBJ_PARENT(inst));
mbm_obj->func_code = mbm_obj->rcv_frame[MB_PDU_FUNC_OFF];
exception = MB_EX_ILLEGAL_FUNCTION;
/* If receive frame has exception. The receive function code highest bit is 1.*/
if (mbm_obj->func_code & MB_FUNC_ERROR) {
exception = (mb_exception_t)mbm_obj->rcv_frame[MB_PDU_DATA_OFF];
} else {
for (int i = 0; i < MB_FUNC_HANDLERS_MAX; i++) {
/* No more function handlers registered. Abort. */
if (mbm_obj->func_handlers[i].func_code == 0) {
break;
}
if (mbm_obj->func_handlers[i].func_code == mbm_obj->func_code) {
/* If master request is broadcast,
* the master need execute function for all slave.
*/
if (MB_OBJ(inst->transp_obj)->frm_is_bcast(inst->transp_obj)) {
length = mbm_obj->pdu_snd_len;
for (int j = 1; j <= MB_MASTER_TOTAL_SLAVE_NUM; j++) {
mbm_set_dest_addr(inst, j);
exception = mbm_obj->func_handlers[i].handler(inst, mbm_obj->rcv_frame, &length);
}
} else {
exception = mbm_obj->func_handlers[i].handler(inst, mbm_obj->rcv_frame, &mbm_obj->pdu_rcv_len);
}
break;
}
/* If master request is broadcast,
* the master needs to execute function for all slaves.
*/
if (MB_OBJ(inst->transp_obj)->frm_is_bcast(inst->transp_obj)) {
length = mbm_obj->pdu_snd_len;
for (int j = 1; j <= MB_MASTER_TOTAL_SLAVE_NUM; j++) {
mbm_set_dest_addr(inst, j);
exception = mbm_check_invoke_handler(inst, mbm_obj->func_code, mbm_obj->rcv_frame, &length);
}
} else {
ESP_LOGD(TAG, MB_OBJ_FMT": function (0x%x), invoke handler.", MB_OBJ_PARENT(inst), (int)mbm_obj->func_code);
exception = mbm_check_invoke_handler(inst, mbm_obj->func_code, mbm_obj->rcv_frame, &mbm_obj->pdu_rcv_len);
}
/* If master has exception, will send error process event. Otherwise the master is idle.*/
if (exception != MB_EX_NONE) {

View File

@ -20,7 +20,7 @@ static const char *TAG = "mb_object.slave";
#if (MB_SLAVE_ASCII_ENABLED || MB_SLAVE_RTU_ENABLED)
typedef struct _port_serial_opts mb_serial_opts_t;
typedef struct port_serial_opts_s mb_serial_opts_t;
#endif
@ -30,7 +30,6 @@ typedef struct
// here are slave object properties and methods
uint8_t mb_address;
mb_comm_mode_t cur_mode;
const mb_fn_handler_t *func_handlers;
mb_state_enum_t cur_state;
uint8_t *frame;
uint16_t length;
@ -38,47 +37,139 @@ typedef struct
uint8_t rcv_addr;
uint64_t curr_trans_id;
volatile uint16_t *pdu_snd_len;
handler_descriptor_t handler_descriptor;
} mbs_object_t;
static mb_fn_handler_t slave_handlers[MB_FUNC_HANDLERS_MAX] =
{
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED
{MB_FUNC_OTHER_REPORT_SLAVEID, (void *)mbs_fn_report_slave_id},
#endif
#if MB_FUNC_READ_INPUT_ENABLED
{MB_FUNC_READ_INPUT_REGISTER, (void *)mbs_fn_read_input_reg},
#endif
#if MB_FUNC_READ_HOLDING_ENABLED
{MB_FUNC_READ_HOLDING_REGISTER, (void *)mbs_fn_read_holding_reg},
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
{MB_FUNC_WRITE_MULTIPLE_REGISTERS, (void *)mbs_fn_write_multi_holding_reg},
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED
{MB_FUNC_WRITE_REGISTER, (void *)mbs_fn_write_holding_reg},
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED
{MB_FUNC_READWRITE_MULTIPLE_REGISTERS, (void *)mbs_fn_rw_multi_holding_reg},
#endif
#if MB_FUNC_READ_COILS_ENABLED
{MB_FUNC_READ_COILS, (void *)mbs_fn_read_coils},
#endif
#if MB_FUNC_WRITE_COIL_ENABLED
{MB_FUNC_WRITE_SINGLE_COIL, (void *)mbs_fn_write_coil},
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED
{MB_FUNC_WRITE_MULTIPLE_COILS, (void *)mbs_fn_write_multi_coils},
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED
{MB_FUNC_READ_DISCRETE_INPUTS, (void *)mbs_fn_read_discrete_inp},
#endif
};
mb_err_enum_t mbs_delete(mb_base_t *inst);
mb_err_enum_t mbs_enable(mb_base_t *inst);
mb_err_enum_t mbs_disable(mb_base_t *inst);
mb_err_enum_t mbs_poll(mb_base_t *inst);
// The helper function to register custom function handler for slave
mb_err_enum_t mbs_set_handler(mb_base_t *inst, uint8_t func_code, mb_fn_handler_fp phandler)
{
mbs_object_t *mbs_obj = MB_GET_OBJ_CTX(inst, mbs_object_t, base);
mb_err_enum_t status = MB_EINVAL;
SEMA_SECTION(mbs_obj->handler_descriptor.sema, MB_HANDLER_UNLOCK_TICKS) {
status = mb_set_handler(&mbs_obj->handler_descriptor, func_code, phandler);
}
return status;
}
// The helper function to register custom function handler for slave
mb_err_enum_t mbs_get_handler(mb_base_t *inst, uint8_t func_code, mb_fn_handler_fp *phandler)
{
mbs_object_t *mbs_obj = MB_GET_OBJ_CTX(inst, mbs_object_t, base);
mb_err_enum_t status = MB_EINVAL;
if (phandler) {
SEMA_SECTION(mbs_obj->handler_descriptor.sema, MB_HANDLER_UNLOCK_TICKS) {
status = mb_get_handler(&mbs_obj->handler_descriptor, func_code, phandler);
}
}
return status;
}
mb_err_enum_t mbs_delete_handler(mb_base_t *inst, uint8_t func_code)
{
mbs_object_t *mbs_obj = MB_GET_OBJ_CTX(inst, mbs_object_t, base);
mb_err_enum_t status = MB_EILLSTATE;
SEMA_SECTION(mbs_obj->handler_descriptor.sema, MB_HANDLER_UNLOCK_TICKS) {
status = mb_delete_handler(&mbs_obj->handler_descriptor, func_code);
}
return status;
}
mb_err_enum_t mbs_get_handler_count(mb_base_t *inst, uint16_t *pcount)
{
MB_RETURN_ON_FALSE((pcount && inst), MB_EINVAL, TAG, "get handler count wrong arguments");
mbs_object_t *mbs_obj = MB_GET_OBJ_CTX(inst, mbs_object_t, base);
SEMA_SECTION(mbs_obj->handler_descriptor.sema, MB_HANDLER_UNLOCK_TICKS) {
*pcount = mbs_obj->handler_descriptor.count;
}
return MB_ENOERR;
}
static mb_exception_t mbs_check_invoke_handler(mb_base_t *inst, uint8_t func_code, uint8_t *pbuf, uint16_t *plen)
{
mbs_object_t *mbs_obj = MB_GET_OBJ_CTX(inst, mbs_object_t, base);
mb_exception_t exception = MB_EX_ILLEGAL_FUNCTION;
if (!func_code || (func_code & MB_FUNC_ERROR)) {
return MB_EX_ILLEGAL_FUNCTION;
}
SEMA_SECTION(mbs_obj->handler_descriptor.sema, MB_HANDLER_UNLOCK_TICKS) {
mb_fn_handler_fp phandler = NULL;
mb_err_enum_t status = mb_get_handler(&mbs_obj->handler_descriptor, func_code, &phandler);
if ((status == MB_ENOERR) && phandler) {
exception = phandler(inst, pbuf, plen);
ESP_LOGD(TAG, MB_OBJ_FMT": function (0x%x), invoke handler %p.", MB_OBJ_PARENT(inst), (int)func_code, phandler);
}
}
return exception;
}
static mb_err_enum_t mbs_register_default_handlers(mb_base_t *inst)
{
mbs_object_t *mbs_obj = MB_GET_OBJ_CTX(inst, mbs_object_t, base);
mb_err_enum_t err = MB_EILLSTATE;
LIST_INIT(&mbs_obj->handler_descriptor.head);
mbs_obj->handler_descriptor.sema = xSemaphoreCreateBinary();
(void)xSemaphoreGive(mbs_obj->handler_descriptor.sema);
mbs_obj->handler_descriptor.instance = (void *)inst->descr.parent;
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED
err = mbs_set_handler(inst, MB_FUNC_OTHER_REPORT_SLAVEID, (void *)mbs_fn_report_slave_id);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_READ_INPUT_ENABLED
err = mbs_set_handler(inst, MB_FUNC_READ_INPUT_REGISTER, (void *)mbs_fn_read_input_reg);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_READ_HOLDING_ENABLED
err = mbs_set_handler(inst, MB_FUNC_READ_HOLDING_REGISTER, (void *)mbs_fn_read_holding_reg);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
err = mbs_set_handler(inst, MB_FUNC_WRITE_MULTIPLE_REGISTERS, (void *)mbs_fn_write_multi_holding_reg);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED
err = mbs_set_handler(inst, MB_FUNC_WRITE_REGISTER, (void *)mbs_fn_write_holding_reg);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED
err = mbs_set_handler(inst, MB_FUNC_READWRITE_MULTIPLE_REGISTERS, (void *)mbs_fn_rw_multi_holding_reg);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_READ_COILS_ENABLED
err = mbs_set_handler(inst, MB_FUNC_READ_COILS, (void *)mbs_fn_read_coils);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_WRITE_COIL_ENABLED
err = mbs_set_handler(inst, MB_FUNC_WRITE_SINGLE_COIL, (void *)mbs_fn_write_coil);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED
err = mbs_set_handler(inst, MB_FUNC_WRITE_MULTIPLE_COILS, (void *)mbs_fn_write_multi_coils);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED
err = mbs_set_handler(inst, MB_FUNC_READ_DISCRETE_INPUTS, (void *)mbs_fn_read_discrete_inp);
MB_RETURN_ON_FALSE((err == MB_ENOERR), err, TAG, "handler registration error = (0x%x).", (int)err);
#endif
return MB_ENOERR;
}
static mb_err_enum_t mbs_unregister_handlers(mb_base_t *inst)
{
mbs_object_t *mbs_obj = MB_GET_OBJ_CTX(inst, mbs_object_t, base);
(void)xSemaphoreTake(mbs_obj->handler_descriptor.sema, MB_HANDLER_UNLOCK_TICKS);
ESP_LOGD(TAG, "Close %s command handlers.", mbs_obj->base.descr.parent_name);
(void)mb_delete_command_handlers(&mbs_obj->handler_descriptor);
mbs_obj->handler_descriptor.instance = NULL;
(void)xSemaphoreGive(mbs_obj->handler_descriptor.sema);
vSemaphoreDelete(mbs_obj->handler_descriptor.sema);
return MB_ENOERR;
}
#if (MB_SLAVE_RTU_ENABLED)
mb_err_enum_t mbs_rtu_create(mb_serial_opts_t *ser_opts, void **in_out_obj)
@ -111,7 +202,9 @@ mb_err_enum_t mbs_rtu_create(mb_serial_opts_t *ser_opts, void **in_out_obj)
ret = mbs_rtu_transp_create(ser_opts, (void **)&transp_obj);
MB_GOTO_ON_FALSE((transp_obj && (ret == MB_ENOERR)), MB_EILLSTATE, error,
TAG, "transport creation, err: %d", (int)ret);
mbs_obj->func_handlers = slave_handlers;
ret = mbs_register_default_handlers(&mbs_obj->base);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EILLSTATE, error,
TAG, "default handlers registration fail, err: %d", (int)ret);
mbs_obj->cur_mode = ser_opts->mode;
mbs_obj->mb_address = ser_opts->uid;
mbs_obj->cur_state = STATE_DISABLED;
@ -126,6 +219,7 @@ error:
if (transp_obj) {
mbs_rtu_transp_delete(transp_obj);
}
(void)mbs_unregister_handlers(&mbs_obj->base);
free(mbs_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(mbs_obj->base.lock);
free(mbs_obj);
@ -166,7 +260,9 @@ mb_err_enum_t mbs_ascii_create(mb_serial_opts_t *ser_opts, void **in_out_obj)
ret = mbs_ascii_transp_create(ser_opts, (void **)&transp_obj);
MB_GOTO_ON_FALSE((transp_obj && (ret == MB_ENOERR)), MB_EILLSTATE, error,
TAG, "transport creation, err: %d", (int)ret);
mbs_obj->func_handlers = slave_handlers;
ret = mbs_register_default_handlers(&mbs_obj->base);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EILLSTATE, error,
TAG, "default handlers registration fail, err: %d", (int)ret);
mbs_obj->cur_mode = ser_opts->mode;
mbs_obj->mb_address = ser_opts->uid;
mbs_obj->cur_state = STATE_DISABLED;
@ -181,6 +277,7 @@ error:
if (transp_obj) {
mbs_ascii_transp_delete(transp_obj);
}
(void)mbs_unregister_handlers(&mbs_obj->base);
free(mbs_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(mbs_obj->base.lock);
free(mbs_obj);
@ -222,7 +319,9 @@ mb_err_enum_t mbs_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj)
ret = mbs_tcp_transp_create(tcp_opts, (void **)&transp_obj);
MB_GOTO_ON_FALSE((transp_obj && (ret == MB_ENOERR)), MB_EILLSTATE, error,
TAG, "transport creation, err: %d", (int)ret);
mbs_obj->func_handlers = slave_handlers;
ret = mbs_register_default_handlers(&mbs_obj->base);
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EILLSTATE, error,
TAG, "default handlers registration fail, err: %d", (int)ret);
mbs_obj->cur_mode = tcp_opts->mode;
mbs_obj->mb_address = tcp_opts->uid;
mbs_obj->cur_state = STATE_DISABLED;
@ -237,6 +336,7 @@ error:
if (transp_obj) {
mbs_tcp_transp_delete(transp_obj);
}
(void)mbs_unregister_handlers(&mbs_obj->base);
free(mbs_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(mbs_obj->base.lock);
free(mbs_obj);
@ -264,6 +364,7 @@ mb_err_enum_t mbs_delete(mb_base_t *inst)
mbs_obj->base.obj_id_chunks = 0;
}
#endif
(void)mbs_unregister_handlers(&mbs_obj->base);
// delete the modbus instance
free(mbs_obj->base.descr.parent_name);
CRITICAL_SECTION_CLOSE(inst->lock);
@ -301,7 +402,10 @@ mb_err_enum_t mbs_enable(mb_base_t *inst)
mb_err_enum_t mbs_disable(mb_base_t *inst)
{
mb_err_enum_t status = MB_ENOERR;
mbs_object_t *mbs_obj = MB_GET_OBJ_CTX(inst, mbs_object_t, base);;
mbs_object_t *mbs_obj = MB_GET_OBJ_CTX(inst, mbs_object_t, base);
// Wait for handler to be unlocked before disable the object
(void)xSemaphoreTake(mbs_obj->handler_descriptor.sema, MB_HANDLER_UNLOCK_TICKS);
(void)xSemaphoreGive(mbs_obj->handler_descriptor.sema);
CRITICAL_SECTION(inst->lock) {
if (mbs_obj->cur_state == STATE_ENABLED) {
MB_OBJ(mbs_obj->base.transp_obj)->frm_stop(mbs_obj->base.transp_obj);
@ -358,20 +462,7 @@ mb_err_enum_t mbs_poll(mb_base_t *inst)
MB_RETURN_ON_FALSE(mbs_obj->frame, MB_EILLSTATE, TAG, "receive buffer fail.");
ESP_LOGD(TAG, MB_OBJ_FMT":EV_EXECUTE", MB_OBJ_PARENT(inst));
mbs_obj->func_code = mbs_obj->frame[MB_PDU_FUNC_OFF];
exception = MB_EX_ILLEGAL_FUNCTION;
// If receive frame has exception. The receive function code highest bit is 1.
for (int i = 0; (i < MB_FUNC_HANDLERS_MAX); i++) {
// No more function handlers registered. Abort.
if (mbs_obj->func_handlers[i].func_code == 0) {
ESP_LOGD(TAG, MB_OBJ_FMT": function (0x%x), handler is not found.", MB_OBJ_PARENT(inst), (int)mbs_obj->func_code);
break;
}
if ((mbs_obj->func_handlers[i].func_code) == mbs_obj->func_code) {
ESP_LOGD(TAG, MB_OBJ_FMT": function (0x%x), start handler.", MB_OBJ_PARENT(inst), (int)mbs_obj->func_code);
exception = mbs_obj->func_handlers[i].handler(inst, mbs_obj->frame, &mbs_obj->length);
break;
}
}
exception = mbs_check_invoke_handler(inst, mbs_obj->func_code, mbs_obj->frame, &mbs_obj->length);
// If the request was not sent to the broadcast address, return a reply.
if ((mbs_obj->rcv_addr != MB_ADDRESS_BROADCAST) || (mbs_obj->cur_mode == MB_TCP)) {
if (exception != MB_EX_NONE) {

View File

@ -19,8 +19,7 @@
#include "mb_port_types.h"
#ifdef __cplusplus
extern "C"
{
extern "C" {
#endif
#define MB_SER_PDU_SIZE_MIN (3)
@ -29,6 +28,7 @@ extern "C"
#define MB_EVENT_QUEUE_TIMEOUT_MAX_MS (3000)
#define MB_EVENT_QUEUE_TIMEOUT (pdMS_TO_TICKS(CONFIG_FMB_EVENT_QUEUE_TIMEOUT))
#define MB_EVENT_QUEUE_TIMEOUT_MAX (pdMS_TO_TICKS(MB_EVENT_QUEUE_TIMEOUT_MAX_MS))
#define MB_MS_TO_TICKS(time_ms) (pdMS_TO_TICKS(time_ms))
int lock_obj(_lock_t *plock);
void unlock_obj(_lock_t *plock);
@ -65,29 +65,6 @@ void unlock_obj(_lock_t *plock);
spinlock_initialize(&lock); \
} while (0)
#define CRITICAL_STORE(LOCK, PTR, VAL) \
__extension__ \
({ \
__auto_type __atomic_ptr = (PTR); \
__typeof__ ((void)0, *__atomic_ptr) __atomic_tmp = (VAL); \
_lock_acquire((_lock_t *)&LOCK); \
*__atomic_ptr = __atomic_tmp; \
_lock_release((_lock_t *)&LOCK); \
(__atomic_tmp); \
})
#define CRITICAL_LOAD(LOCK, PTR) \
__extension__ \
({ \
__auto_type __atomic_ptr = (PTR); \
__typeof__ ((void)0, *__atomic_ptr) __atomic_tmp; \
_lock_acquire((_lock_t *)&LOCK); \
__atomic_tmp = (*__atomic_ptr); \
_lock_release((_lock_t *)&LOCK); \
(__atomic_tmp); \
})
#define SPIN_LOCK_ENTER(lock) \
do \
{ \

View File

@ -28,7 +28,7 @@ extern "C" {
#define MB_ASCII_SER_PDU_ADDR_OFF 0 /*!< Offset of slave address in Ser-PDU. */
#define MB_ASCII_SER_PDU_PDU_OFF 1 /*!< Offset of Modbus-PDU in Ser-PDU. */
typedef struct _port_serial_opts mb_serial_opts_t;
typedef struct port_serial_opts_s mb_serial_opts_t;
typedef struct mb_trans_base_t mb_trans_base_t;
mb_err_enum_t mbm_ascii_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst);

View File

@ -53,7 +53,7 @@ typedef enum
MB_RTU_STATE_ERROR /*!< If the frame is invalid. */
} mb_rtu_state_enum_t;
typedef struct _port_serial_opts mb_serial_opts_t;
typedef struct port_serial_opts_s mb_serial_opts_t;
typedef struct mb_trans_base_t mb_trans_base_t;
mb_err_enum_t mbm_rtu_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst);

View File

@ -18,6 +18,6 @@ CONFIG_MB_PORT_ADAPTER_EN=y
CONFIG_MB_TEST_SLAVE_TASK_PRIO=4
CONFIG_MB_TEST_MASTER_TASK_PRIO=4
CONFIG_MB_TEST_COMM_CYCLE_COUNTER=10
CONFIG_MB_TEST_LEAK_CRITICAL_LEVEL=128
CONFIG_MB_TEST_LEAK_WARN_LEVEL=128
CONFIG_MB_TEST_LEAK_CRITICAL_LEVEL=256
CONFIG_MB_TEST_LEAK_WARN_LEVEL=256

View File

@ -18,6 +18,6 @@ CONFIG_MB_PORT_ADAPTER_EN=y
CONFIG_MB_TEST_SLAVE_TASK_PRIO=4
CONFIG_MB_TEST_MASTER_TASK_PRIO=4
CONFIG_MB_TEST_COMM_CYCLE_COUNTER=10
CONFIG_MB_TEST_LEAK_CRITICAL_LEVEL=128
CONFIG_MB_TEST_LEAK_WARN_LEVEL=128
CONFIG_MB_TEST_LEAK_CRITICAL_LEVEL=256
CONFIG_MB_TEST_LEAK_WARN_LEVEL=256

View File

@ -105,6 +105,44 @@ static esp_err_t slave_serial_init(void **pinst)
return err;
}
mb_exception_t test_handler(void *pinst, uint8_t *frame_ptr, uint16_t *plen)
{
return MB_EX_CRITICAL; // Set the exception code for slave appropriately
}
static int check_custom_handlers(void *pinst)
{
mb_fn_handler_fp phandler = NULL;
int entry;
uint16_t count = 0;
esp_err_t err = ESP_FAIL;
err = mbc_get_handler_count(pinst, &count);
MB_RETURN_ON_FALSE((err == ESP_OK), 0, TAG,
"mbc slave get handler count, returns(0x%x).", (int)err);
ESP_LOGI(TAG,"Object %p, custom handler test, (registered:max) handlers: %d:%d.", pinst, count, CONFIG_FMB_FUNC_HANDLERS_MAX);
for (entry = 0x01; entry < CONFIG_FMB_FUNC_HANDLERS_MAX; entry++) {
// Try to remove the handler
err = mbc_delete_handler(pinst, (uint8_t)entry);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Could not remove handler for command: (0x%x), returned (0x%x), already empty?", entry, (int)err);
}
err = mbc_set_handler(pinst, (uint8_t)entry, test_handler);
if (err != ESP_OK) {
ESP_LOGE(TAG,"Could not set handler for command 0x%x, returned (0x%x).", entry, (int)err);
break;
} else {
ESP_LOGI(TAG,"Set handler for command 0x%x, returned (0x%x).", entry, (int)err);
}
err = mbc_get_handler(pinst, (uint8_t)entry, &phandler);
if (err != ESP_OK || phandler != test_handler) {
ESP_LOGE(TAG, "Could not get handler for command (0x%x) = (%p), returned (0x%x).", entry, phandler, (int)err);
break;
}
}
ESP_LOGI(TAG, "Last entry processed: %d.", entry);
return entry;
}
// Intentionally verify that atomic values are layout compatible with original types
static_assert(
sizeof(std::atomic<int>) == sizeof(int),
@ -117,12 +155,18 @@ extern "C" void app_main(void)
ESP_LOGI(TAG, "Setup master cpp....");
ESP_ERROR_CHECK(master_serial_init(&pmaster_handle));
ESP_ERROR_CHECK(mbc_master_stop(pmaster_handle));
int last_entry = check_custom_handlers(pmaster_handle);
MB_RETURN_ON_FALSE((last_entry >= CONFIG_FMB_FUNC_HANDLERS_MAX), ;, TAG,
"Incorrect number of command entries for master: %d.", (int)last_entry);
ESP_ERROR_CHECK(mbc_master_delete(pmaster_handle));
ESP_LOGI(TAG, "Master test passed successfully.");
ESP_LOGI(TAG, "Setup slave cpp....");
ESP_ERROR_CHECK(slave_serial_init(&pslave_handle));
last_entry = check_custom_handlers(pslave_handle);
// explicitly check stop method before delete
ESP_ERROR_CHECK(mbc_slave_stop(pslave_handle));
ESP_ERROR_CHECK(mbc_slave_delete(pslave_handle));
MB_RETURN_ON_FALSE((last_entry >= CONFIG_FMB_FUNC_HANDLERS_MAX), ;, TAG,
"Incorrect number of command entries for slave: %d.", (int)last_entry);
ESP_LOGI(TAG, "Slave test passed successfully.");
}

View File

@ -15,7 +15,7 @@ def test_modbus_comm_multi_dev_serial(case_tester) -> None: # typ
@pytest.mark.esp32
@pytest.mark.multi_dut_modbus_tcp
@pytest.mark.parametrize('count, config', [(2, 'wifi'), (2, 'ethernet')], indirect=True)
@pytest.mark.parametrize('count, config', [(2, 'ethernet')], indirect=True)
def test_modbus_comm_multi_dev_tcp(case_tester) -> None: # type: ignore
for case in case_tester.test_menu:
if case.attributes.get('test_env', 'multi_dut_modbus_tcp') == 'multi_dut_modbus_tcp':

View File

@ -11,6 +11,7 @@ target_include_directories(mb_ut_lib PUBLIC
"${dir}/modbus/mb_controller/common/include"
"${dir}/modbus/mb_controller/serial"
"${dir}/modbus/mb_controller/tcp"
"${dir}/modbus/mb_objects/common"
"${dir}/modbus/mb_objects/include"
"${dir}/modbus/mb_ports/common"
"${dir}/modbus/mb_ports/serial"

View File

@ -30,7 +30,7 @@ mb_err_enum_t mb_port_event_wait_req_finish(mb_port_base_t *inst);
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
typedef struct _port_serial_opts mb_serial_opts_t;
typedef struct port_serial_opts_s mb_serial_opts_t;
mb_err_enum_t mbs_rtu_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
mb_err_enum_t mbs_ascii_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
@ -93,7 +93,7 @@ mb_exception_t mbs_fn_rw_multi_holding_reg(mb_base_t *inst, uint8_t *frame_ptr,u
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
typedef struct _port_tcp_opts mb_tcp_opts_t;
typedef struct port_tcp_opts_s mb_tcp_opts_t;
mb_err_enum_t mbs_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj);

View File

@ -81,7 +81,7 @@ mb_err_enum_t mb_port_event_wait_req_finish(mb_port_base_t *inst);
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
typedef struct _port_serial_opts mb_serial_opts_t;
typedef struct port_serial_opts_s mb_serial_opts_t;
mb_err_enum_t mbs_rtu_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
mb_err_enum_t mbs_ascii_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
@ -102,7 +102,7 @@ mb_err_enum_t mbs_set_slv_id(mb_base_t *inst, uint8_t slv_id, bool is_running, u
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
typedef struct _port_tcp_opts mb_tcp_opts_t;
typedef struct port_tcp_opts_s mb_tcp_opts_t;
mb_err_enum_t mbm_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj);

View File

@ -79,7 +79,7 @@ error:
#if (MB_MASTER_ASCII_ENABLED || MB_MASTER_RTU_ENABLED)
typedef struct _port_serial_opts mb_serial_opts_t;
typedef struct port_serial_opts_s mb_serial_opts_t;
mb_err_enum_t mb_stub_serial_create(mb_serial_opts_t *ser_opts, void **in_out_obj)
{

View File

@ -36,8 +36,8 @@ class ModbusMBAP(Packet):
name = "Modbus TCP"
fields_desc = [ ShortField("transId", 0),
ShortField("protoId", 0),
ShortField("len", 6),
XByteField("UnitId", 247),
ShortField("len", 0),
XByteField("unitId", 0),
]
# Can be used to replace all Modbus read
@ -252,10 +252,27 @@ class ModbusPDUXX_Custom_Request(Packet):
FieldListField("customBytes", [0x00], XByteField("", 0x00))
]
class ModbusPDUXX_Custom_Exception(Packet):
name = "Custom Command Exception"
fields_desc = [
XByteField("funcCode", 0x00),
ByteEnumField("exceptCode", 1, modbus_exceptions)
]
# Custom command respond
class ModbusPDUXX_Custom_Answer(Packet):
name = "Custom Command Answer"
fields_desc = [
ConditionalField(XByteField("funcCode", 0x00), lambda pkt: (type(pkt.underlayer) is ModbusADU_Response)),
ConditionalField(FieldListField("customBytes", [0x00], XByteField("", 0x00), count_from = lambda pkt: pkt.underlayer.len if pkt.underlayer is not None else 0), lambda pkt: type(pkt.underlayer) is ModbusADU_Response)
]
# 0x11 - Report Slave Id
class ModbusPDU11_Report_Slave_Id(Packet):
name = "Report Slave Id"
fields_desc = [ XByteField("funcCode", 0x11) ]
fields_desc = [
XByteField("funcCode", 0x11)
]
class ModbusPDU11_Report_Slave_Id_Answer(Packet):
name = "Report Slave Id Answer"
@ -279,7 +296,8 @@ class ModbusADU_Request(ModbusMBAP):
XShortField("transId", 0x0000), # needs to be unique
XShortField("protoId", 0x0000), # needs to be zero (Modbus)
XShortField("len", None), # is calculated with payload
XByteField("unitId", 0x00)] # 0xFF or 0x00 should be used for Modbus over TCP/IP
XByteField("unitId", 0x00) # 0xFF or 0x00 should be used for Modbus over TCP/IP
]
def mb_get_last_exception(self):
return _mb_exception
@ -372,7 +390,8 @@ class ModbusADU_Request(ModbusMBAP):
class ModbusADU_Response(ModbusMBAP):
name = "ModbusADU Response"
_mb_exception: modbus_exceptions = 0
_current_main_packet: Packet = None
_modbus_pdu: Packet = None
fields_desc = [
XShortField("transId", 0x0000), # needs to be unique
XShortField("protoId", 0x0000), # needs to be zero (Modbus)
@ -381,6 +400,15 @@ class ModbusADU_Response(ModbusMBAP):
def mb_get_last_exception(self):
return _mb_exception
# def extract_padding(self, s):
# print(f'Extract pedding: {self, s, self.len, self.underlayer}')
# return self.guess_payload_class( s) #, s #self.extract_pedding(self, s)
def pre_dissect(self, s):
print(f'Pre desect: {self, s, self.len, self.underlayer}')
_current_main_packet = self
return s
# Dissects packets
def guess_payload_class(self, payload):
@ -443,11 +471,14 @@ class ModbusADU_Response(ModbusMBAP):
return ModbusPDU10_Write_Multiple_Registers_Exception
elif funcCode == 0x11:
print(f'Packet answer: {payload}, func: {funcCode}')
return ModbusPDU11_Report_Slave_Id_Answer
elif funcCode == 0x91:
self._mb_exception = int(payload[1])
return ModbusPDU11_Report_Slave_Id_Exception
else:
return Packet.guess_payload_class(self, payload)
if (funcCode & 0x80):
self._mb_exception = int(payload[1])
return ModbusPDUXX_Custom_Exception
return ModbusPDUXX_Custom_Answer
#return Packet.guess_payload_class(self, payload)

View File

@ -42,6 +42,8 @@ MB_LOGGING_PATH = '.'
# The constructed packets for self testing
TEST_PACKET_REPORT_CUSTOM_0X41 = 'ModbusADU_Request(transId=MB_DEF_TRANS_ID, unitId=0x01, protoId=0)/\
ModbusPDUXX_Custom_Request(customBytes=[0x41])'
TEST_PACKET_REPORT_SLAVE_ID_CUSTOM = 'ModbusADU_Request(transId=MB_DEF_TRANS_ID, unitId=0x01, protoId=0)/\
ModbusPDUXX_Custom_Request(customBytes=[0x11])'
TEST_PACKET_REPORT_SLAVE_ID = 'ModbusADU_Request(transId=MB_DEF_TRANS_ID, unitId=0x01, protoId=0)/\
@ -431,6 +433,15 @@ class ModbusTestLib:
def self_test(self) -> None:
# type: () -> None
self.connect(ip_addr=MB_DEF_SERVER_IP, port=MB_DEF_PORT)
packet = self.create_request(TEST_PACKET_REPORT_CUSTOM_0X41)
print(f"Test: 0x41 <Custom command> packet: {packet}")
response = self.send_packet_and_get_response(packet, timeout=1, verbose=0)
assert response and len(response) > 1, "No response from slave"
print(f"Test: received: {bytes(response)}")
pdu = self.translate_response(response)
if pdu is not None:
print(f"Received: {pdu}")
#print(f"PDU Exception: {self.check_response(pdu, packet.customBytes[0])}")
packet = self.create_request(TEST_PACKET_REPORT_SLAVE_ID_CUSTOM)
print(f"Test: 0x11 <Report Slave ID> packet: {packet}")
response = self.send_packet_and_get_response(packet, timeout=1, verbose=0)

View File

@ -18,6 +18,11 @@ Library Collections
Library ModbusTestLib.py WITH NAME ModbusTestLib
*** Keywords ***
Create Custom Command Request
[Arguments] ${uid} ${customData}
${packet} = Create Request ModbusADU_Request(unitId=${uid}, protoId=0)/ModbusPDUXX_Custom_Request(customBytes=${customData})
RETURN ${packet}
Create Report Slave Id Request
[Arguments] ${uid} ${customData}
#${packet} = Create Request ModbusADU_Request(unitId=${uid}, protoId=0)/ModbusPDU11_Report_Slave_Id(funcCode=${FUNC_REPORT_SLAVE_ID})
@ -64,6 +69,48 @@ Create Discrete Read Request
Log Packet: ${packet}
RETURN ${packet}
Lists Should Be Equal
[Arguments] ${get_list} ${exp_list}
Should Not Be Empty ${get_list}
Should Not Be Empty ${exp_list}
${get_length} = Get length ${get_list}
${exp_length} = Get length ${exp_list}
Should Be Equal As Integers ${get_length} ${exp_length}
FOR ${i} IN RANGE ${exp_length}
${get_item} = Get From List ${get_list} ${i}
${exp_item} = Get From List ${exp_list} ${i}
Should Be Equal As Integers ${get_item} ${exp_item}
END
Custom Command
[Arguments] ${uid} ${customData} ${exception_expected} ${expected_list}
${classId} = Get Class Id
Log Library ClassId: ${classId}
Log Get Slave Identificator UID:${uid}, Custom bytes: ${customData}
${req} = Create Custom Command Request ${uid} ${customData}
#Create Connection ${server} ${port}
${response_frame} = Send Packet And Get Response ${req}
Should Not Be Empty ${response_frame}
${packet} = Translate Response ${response_frame}
Should Be Equal As Integers ${req.transId} ${packet.transId}
${exception} ${exp_message} = Check Response ${packet} ${req.customBytes[0]}
Should Be Equal As Integers ${exception} ${exception_expected}
Log exception: (${exception}: ${exp_message}), expected: ${exception_expected}
IF ${exception} == ${0}
Log SlaveUID:${uid}, Custom_data received:${packet.customBytes}
IF ${expected_list} != ${None}
Log ${expected_list}
Log ${packet.customBytes}
${get_list} = Convert To List ${packet.customBytes}
${exp_list} = Evaluate ${expected_list}
Lists Should Be Equal ${get_list} ${exp_list}
ELSE
Log "Skip comparison with expected list"
END
ELSE
Log "Exception is evaluated correctly (${exception}: ${exp_message}) == ${exception_expected}"
END
Report Slave Id
[Arguments] ${uid} ${customData} ${exception_expected}
${classId} = Get Class Id

View File

@ -11,6 +11,13 @@ Suite Teardown Disconnect
${suiteConnection} None
*** Test Cases ***
Test Cusom Command Request
[Documentation] Test reading slave UID, running status, identificator structure (use custom frame template)
[Template] Custom Command
0x01 [0x41] 0 ${None} # Try to send shortest request for custom command, do not check the buffer
0x01 [0x41, 0x11, 0x22, 0x33, 0x44] 0 ${None} # Send custom data, do not compare the response buffer
0x01 [0x41, 0x11, 0x22, 0x33] 0 [17, 34, 51, 0x3A, 83, 108, 97, 118, 101] # Send the custom command and compare expected response (can use hex or dec values)
Test Report Slave Id
[Documentation] Test reading slave UID, running status, identificator structure (use custom frame template)
[Template] Report Slave Id