diff --git a/examples/serial/mb_serial_master/main/serial_master.c b/examples/serial/mb_serial_master/main/serial_master.c index 4909f89..72f2224 100644 --- a/examples/serial/mb_serial_master/main/serial_master.c +++ b/examples/serial/mb_serial_master/main/serial_master.c @@ -58,6 +58,8 @@ #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"; // Enumeration of modbus device addresses accessed by master device @@ -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 @@ -301,6 +305,19 @@ static void *master_get_param_data(const mb_parameter_descriptor_t *param_descri } \ )) +mb_exception_t my_custom_handler(void *, uint8_t *frame_ptr, uint16_t *plen) +{ + MB_RETURN_ON_FALSE((frame_ptr && plen && *plen && *plen < (MB_CUST_DATA_LEN - 1)), MB_EX_CRITICAL, TAG, + "incorrect custom frame buffer"); + ESP_LOGW(TAG, "Custom handler, Frame ptr: %p, len: %u", frame_ptr, *plen); + // This error handler will be executed to handle the request for the registered custom command + // Refer the handler functions in `esp-modbus/modbus/mb_objects/functions/mbfuncinput_master.c` for more information. + // Parameters: pframe: is pointer to incoming frame buffer, plen: is pointer to length including the function code + 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; +} + // User operation function to read slave values and check alarm static void master_operation_func(void *arg) { @@ -309,6 +326,20 @@ 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) + }; + + // 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."); + } #if MB_FUNC_OTHER_REP_SLAVEID_ENABLED // Command - 17 (0x11) Report Slave ID @@ -318,11 +349,11 @@ 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 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 + .command = 0x11, // the command, + .reg_start = 0, // must be zero, + .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 @@ -492,6 +523,18 @@ 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); + uint8_t override_command = 0x41; + err = mbc_master_set_handler(master_handle, override_command, NULL); + 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_master_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_master_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); diff --git a/examples/serial/mb_serial_slave/main/serial_slave.c b/examples/serial/mb_serial_slave/main/serial_slave.c index 73262a6..0598108 100644 --- a/examples/serial/mb_serial_slave/main/serial_slave.c +++ b/examples/serial/mb_serial_slave/main/serial_slave.c @@ -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,22 @@ static void setup_reg_data(void) input_reg_params.input_data7 = 4.78; } +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"); + //ESP_LOGW("CUSTOM_DATA", "Custom handler, frame ptr: %p, len: %u", frame_ptr, *plen); + //ESP_LOG_BUFFER_HEXDUMP("CUSTOM_DATA", frame_ptr, *plen, ESP_LOG_WARN); + // This command handler will be executed to check the request for the custom command + // See the `esp-modbus/modbus/mb_objects/functions/functions/mbfuncinput.c` for more information + // pframe is pointer to the buffer starting from function code, plen - is pointer to length of the data + 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 slave 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 +190,18 @@ void app_main(void) ESP_ERROR_CHECK(mbc_slave_create_serial(&comm_config, &mbc_slave_handle)); // Initialization of Modbus controller + uint8_t custom_command = 0x41; // The custom command to be sent to slave + esp_err_t err = mbc_slave_set_handler(mbc_slave_handle, custom_command, NULL); + MB_RETURN_ON_FALSE((err == ESP_OK || err == ESP_ERR_INVALID_STATE), ;, TAG, + "could not reset handler, returned (0x%x).", (int)err); + err = mbc_slave_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_slave_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 +272,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) diff --git a/examples/serial/pytest_mb_master_slave.py b/examples/serial/pytest_mb_master_slave.py index fc3159d..901c3a0 100644 --- a/examples/serial/pytest_mb_master_slave.py +++ b/examples/serial/pytest_mb_master_slave.py @@ -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) diff --git a/examples/tcp/mb_tcp_master/main/tcp_master.c b/examples/tcp/mb_tcp_master/main/tcp_master.c index 71125a1..cd8ca34 100644 --- a/examples/tcp/mb_tcp_master/main/tcp_master.c +++ b/examples/tcp/mb_tcp_master/main/tcp_master.c @@ -83,6 +83,8 @@ #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"; // Enumeration of modbus device addresses accessed by master device @@ -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,19 @@ static esp_err_t destroy_services(void) return err; } +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_CRITICAL, TAG, + "incorrect custom frame buffer"); + ESP_LOGW(TAG, "Custom handler, Frame ptr: %p, len: %u", frame_ptr, *plen); + // This error handler will be executed to handle the request for the registered custom command + // Refer the handler functions in `esp-modbus/modbus/mb_objects/functions/mbfuncinput_master.c` for more information. + // Parameters: pframe: is pointer to incoming frame buffer, plen: is pointer to length including the function code + 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(mb_communication_info_t *pcomm_info) { @@ -670,6 +700,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; + // Make sure the handler is undefined for the command + err = mbc_master_set_handler(master_handle, custom_command, NULL); + 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_master_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_master_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, diff --git a/examples/tcp/mb_tcp_slave/main/tcp_slave.c b/examples/tcp/mb_tcp_slave/main/tcp_slave.c index 26d0361..466febf 100644 --- a/examples/tcp/mb_tcp_slave/main/tcp_slave.c +++ b/examples/tcp/mb_tcp_slave/main/tcp_slave.c @@ -43,8 +43,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 (50) +#define MB_CHAN_DATA_OFFSET (10.1f) #define MB_READ_MASK (MB_EVENT_INPUT_REG_RD \ | MB_EVENT_HOLDING_REG_RD \ @@ -55,6 +55,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"; @@ -159,7 +160,7 @@ static void slave_operation_func(void *arg) (unsigned)reg_info.size); if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0) { - (void)mbc_slave_unlock(slave_handle); + (void)mbc_slave_lock(slave_handle); holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET; if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - MB_CHAN_DATA_OFFSET)) { coil_reg_params.coils_port1 = 0xFF; @@ -266,6 +267,23 @@ static esp_err_t destroy_services(void) return err; } +// This custom command handler will be executed to check the request for the custom command +// See the `esp-modbus/modbus/mb_objects/functions/mbfuncinput.c` for more information +// pframe is pointer to the buffer starting from function code, plen - is pointer to length of the data +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"); + ESP_LOGW("CUSTOM_DATA", "Custom handler, frame ptr: %p, len: %u", frame_ptr, *plen); + ESP_LOG_BUFFER_HEXDUMP("CUSTOM_DATA", frame_ptr, *plen, ESP_LOG_WARN); + 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 slave appropriately +} + // Modbus slave initialization static esp_err_t slave_init(mb_communication_info_t *pcomm_info) { @@ -277,6 +295,18 @@ static esp_err_t slave_init(mb_communication_info_t *pcomm_info) TAG, "mb controller create fail."); + uint8_t custom_command = 0x41; + err = mbc_slave_set_handler(slave_handle, custom_command, NULL); + 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_slave_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_slave_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. diff --git a/examples/tcp/pytest_mb_tcp_master_slave.py b/examples/tcp/pytest_mb_tcp_master_slave.py index 459c116..0766f9f 100644 --- a/examples/tcp/pytest_mb_tcp_master_slave.py +++ b/examples/tcp/pytest_mb_tcp_master_slave.py @@ -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,12 +36,11 @@ 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' +# 'ethernet' ] @pytest.mark.esp32 @@ -49,23 +48,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()