forked from espressif/esp-modbus
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:
@ -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")
|
||||
|
8
Kconfig
8
Kconfig
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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`
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
91
modbus/mb_controller/common/esp_modbus_common.c
Normal file
91
modbus/mb_controller/common/esp_modbus_common.c
Normal 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);
|
||||
}
|
@ -732,3 +732,4 @@ esp_err_t mbc_master_set_param_data(void* dest, void* src, mb_descr_type_t param
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
@ -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
|
||||
*/
|
110
modbus/mb_objects/functions/mbfunc_handling.c
Normal file
110
modbus/mb_objects/functions/mbfunc_handling.c
Normal 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;
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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;
|
||||
|
@ -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
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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 \
|
||||
{ \
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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':
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user