forked from espressif/esp-modbus
add test applications and unit tests
This commit is contained in:
1 parent
581573f6d4
commit
c343e08600
70 files changed
+4436
-2
No files matched your search
@@ -0,0 +1,9 @@
|
||||
# This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "../../test_common")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "../test_stubs")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(test_mb_controller_common_unit)
|
||||
@@ -0,0 +1,4 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
This test app is used to test modbus interface.
|
||||
@@ -0,0 +1,9 @@
|
||||
# NOTE: This kind of mocking currently works on Linux targets only.
|
||||
# On Espressif chips, too many dependencies are missing at the moment.
|
||||
|
||||
idf_component_mock(INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
REQUIRES test_common cmock
|
||||
MOCK_HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_mbm_object.h)
|
||||
|
||||
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
:cmock:
|
||||
:plugins:
|
||||
- expect
|
||||
- expect_any_args
|
||||
- return_thru_ptr
|
||||
- array
|
||||
- ignore
|
||||
- ignore_arg
|
||||
- callback
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "mb_config.h"
|
||||
#include "mb_common.h"
|
||||
#include "mb_port_types.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
/* Common definitions */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct mb_base_t mb_base_t; /*!< Type of modbus object */
|
||||
typedef struct mb_port_base_t mb_port_base_t;
|
||||
|
||||
bool mb_port_evt_get(mb_port_base_t *inst, mb_event_t *pevent);
|
||||
bool mb_port_evt_post(mb_port_base_t *inst, mb_event_t event);
|
||||
bool mb_port_evt_res_take(mb_port_base_t *inst, uint32_t timeout);
|
||||
void mb_port_evt_res_release(mb_port_base_t *inst);
|
||||
mb_err_enum_t mb_port_evt_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;
|
||||
|
||||
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);
|
||||
|
||||
mb_err_enum_t mbm_rtu_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
|
||||
mb_err_enum_t mbm_ascii_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
|
||||
|
||||
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);
|
||||
mb_err_enum_t mbm_rq_write_multi_holding_reg(mb_base_t *inst, uint8_t snd_addr, uint16_t reg_addr, uint16_t reg_wr_addr, uint16_t *data_ptr, uint32_t tout);
|
||||
mb_err_enum_t mbm_rq_read_holding_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_rw_multi_holding_reg(mb_base_t *inst, uint8_t snd_addr, uint16_t rd_reg_addr,
|
||||
uint16_t rd_reg_num, uint16_t *data_ptr, uint16_t wr_reg_addr, uint16_t wr_reg_num, uint32_t tout);
|
||||
mb_err_enum_t mbm_rq_read_discrete_inputs(mb_base_t *inst, uint8_t snd_addr, uint16_t discrete_addr, uint16_t discrete_num, uint32_t tout);
|
||||
mb_err_enum_t mbm_rq_read_coils(mb_base_t *inst, uint8_t snd_addr, uint16_t coil_addr, uint16_t coil_num, uint32_t tout);
|
||||
mb_err_enum_t mbm_rq_write_coil(mb_base_t *inst, uint8_t snd_addr, uint16_t coil_addr, uint16_t coil_data, uint32_t tout);
|
||||
mb_err_enum_t mbm_rq_write_multi_coils(mb_base_t *inst, uint8_t snd_addr, uint16_t coil_addr, uint16_t coil_num, uint8_t *data_ptr, uint32_t tout);
|
||||
|
||||
#if MB_FUNC_OTHER_REP_SLAVEID_BUF
|
||||
mb_exception_t mb_fn_report_slv_id(mb_base_t *inst, uint8_t *frame_ptr,uint16_t *len_buf);
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_READ_INPUT_ENABLED
|
||||
mb_exception_t mbs_fn_read_input_reg(mb_base_t *inst, uint8_t *frame_ptr,uint16_t *len_buf);
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_READ_HOLDING_ENABLED
|
||||
mb_exception_t mbs_fn_read_holding_reg(mb_base_t *inst, uint8_t *frame_ptr,uint16_t *len_buf);
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_WRITE_HOLDING_ENABLED
|
||||
mb_exception_t mbs_fn_write_holding_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
|
||||
mb_exception_t mbs_fn_write_multi_holding_reg(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_READ_COILS_ENABLED
|
||||
mb_exception_t mbs_fn_read_coils(mb_base_t *inst, uint8_t *frame_ptr,uint16_t *len_buf);
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_WRITE_COIL_ENABLED
|
||||
mb_exception_t mbs_fn_write_coil(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED
|
||||
mb_exception_t mbs_fn_write_multi_coils(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED
|
||||
mb_exception_t mbs_fn_read_discrete_inp(mb_base_t *inst, uint8_t *frame_ptr, uint16_t *len_buf);
|
||||
#endif
|
||||
|
||||
#if MB_FUNC_READWRITE_HOLDING_ENABLED
|
||||
mb_exception_t mbs_fn_rw_multi_holding_reg(mb_base_t *inst, uint8_t *frame_ptr,uint16_t *len_buf);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
mb_err_enum_t mbs_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj);
|
||||
|
||||
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);
|
||||
mb_err_enum_t mbs_set_slv_id(mb_base_t *inst, uint8_t slv_id, bool is_running, uint8_t const *slv_idstr, uint16_t slv_idstr_len);
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
|
||||
|
||||
typedef struct _port_tcp_opts mb_tcp_opts_t;
|
||||
|
||||
mb_err_enum_t mbm_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj);
|
||||
|
||||
#endif
|
||||
|
||||
mb_err_enum_t mbm_delete(mb_base_t *inst);
|
||||
mb_err_enum_t mbm_enable(mb_base_t *inst);
|
||||
mb_err_enum_t mbm_disable(mb_base_t *inst);
|
||||
mb_err_enum_t mbm_poll(mb_base_t *inst);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_mb_controller_common.c"
|
||||
)
|
||||
|
||||
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
|
||||
idf_component_register(SRCS ${srcs}
|
||||
PRIV_REQUIRES test_stubs mocked_esp_modbus test_common cmock test_utils unity)
|
||||
|
||||
# The workaround for WHOLE_ARCHIVE is absent in v4.4
|
||||
set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u mb_test_include_impl")
|
||||
#target_compile_options(${COMPONENT_LIB} PUBLIC -fsanitize=address = -lasan)
|
||||
@@ -0,0 +1,45 @@
|
||||
menu "Modbus Test Configuration"
|
||||
|
||||
config MB_PORT_ADAPTER_EN
|
||||
bool "Enable Modbus port adapter to substitute hardware layer for test."
|
||||
default y
|
||||
help
|
||||
When option is enabled the port communication layer is substituted by
|
||||
port adapter layer to allow testing of higher layers without access to physical layer.
|
||||
|
||||
config MB_TEST_SLAVE_TASK_PRIO
|
||||
int "Modbus master test task priority"
|
||||
range 4 23
|
||||
default 4
|
||||
help
|
||||
Modbus master task priority for the test.
|
||||
|
||||
config MB_TEST_MASTER_TASK_PRIO
|
||||
int "Modbus slave test task priority"
|
||||
range 4 23
|
||||
default 4
|
||||
help
|
||||
Modbus slave task priority for the test.
|
||||
|
||||
config MB_TEST_COMM_CYCLE_COUNTER
|
||||
int "Modbus communication cycle counter for test"
|
||||
range 10 1000
|
||||
default 10
|
||||
help
|
||||
Modbus communication cycle counter for test.
|
||||
|
||||
config MB_TEST_LEAK_WARN_LEVEL
|
||||
int "Modbus test leak warning level"
|
||||
range 4 256
|
||||
default 32
|
||||
help
|
||||
Modbus test leak warning level.
|
||||
|
||||
config MB_TEST_LEAK_CRITICAL_LEVEL
|
||||
int "Modbus test leak critical level"
|
||||
range 4 1024
|
||||
default 64
|
||||
help
|
||||
Modbus test leak critical level.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,6 @@
|
||||
dependencies:
|
||||
idf: ">=4.3"
|
||||
espressif/esp-modbus:
|
||||
version: "^2.0"
|
||||
override_path: "../../../../"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "unity_fixture.h"
|
||||
|
||||
static void run_all_tests(void)
|
||||
{
|
||||
RUN_TEST_GROUP(unit_test_controller);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
UNITY_MAIN_FUNC(run_all_tests);
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include "unity_fixture.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "test_common.h"
|
||||
#include "mbc_master.h"
|
||||
#include "mbc_slave.h"
|
||||
|
||||
#include "Mocktest_mbm_object.h"
|
||||
#include "mb_object_stub.h"
|
||||
|
||||
#define TEST_SER_PORT_NUM 1
|
||||
#define TEST_TCP_PORT_NUM 1502
|
||||
#define TEST_TASKS_NUM 3
|
||||
#define TEST_TASK_TIMEOUT_MS 30000
|
||||
#define TEST_ALLOWED_LEAK 32
|
||||
#define TEST_SLAVE_SEND_TOUT_US 30000
|
||||
#define TEST_MASTER_SEND_TOUT_US 30000
|
||||
|
||||
#define TEST_MASTER_RESPOND_TOUT_MS CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND
|
||||
|
||||
#define TAG "MODBUS_SERIAL_TEST"
|
||||
|
||||
// The workaround to statically link whole test library
|
||||
__attribute__((unused)) bool mb_test_include_impl = 1;
|
||||
|
||||
enum {
|
||||
CID_DEV_REG0_INPUT,
|
||||
CID_DEV_REG0_HOLD,
|
||||
CID_DEV_REG1_INPUT,
|
||||
CID_DEV_REG0_COIL,
|
||||
CID_DEV_REG0_DISCRITE,
|
||||
CID_DEV_REG_CNT
|
||||
};
|
||||
|
||||
// Example Data (Object) Dictionary for Modbus parameters
|
||||
static const mb_parameter_descriptor_t descriptors[] = {
|
||||
{CID_DEV_REG0_INPUT, STR("MB_input_reg-0"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 1,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0, 0, 0), PAR_PERMS_READ},
|
||||
{CID_DEV_REG0_HOLD, STR("MB_hold_reg-0"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 1, 1,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
{CID_DEV_REG1_INPUT, STR("MB_input_reg-1"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 1,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
{CID_DEV_REG0_COIL, STR("MB_coil_reg-0"), STR("bit"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 3, 8,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
{CID_DEV_REG0_DISCRITE, STR("MB_discr_reg-0"), STR("bit"), MB_DEVICE_ADDR1, MB_PARAM_DISCRETE, 4, 1,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
{CID_DEV_REG_CNT, STR("CYCLE_COUNTER"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 4, 1,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
};
|
||||
|
||||
// Calculate number of parameters in the table
|
||||
const uint16_t num_descriptors = (sizeof(descriptors) / sizeof(descriptors[0]));
|
||||
|
||||
TEST_GROUP(unit_test_controller);
|
||||
|
||||
TEST_SETUP(unit_test_controller)
|
||||
{
|
||||
test_common_start();
|
||||
}
|
||||
|
||||
TEST_TEAR_DOWN(unit_test_controller)
|
||||
{
|
||||
test_common_stop();
|
||||
}
|
||||
|
||||
TEST(unit_test_controller, test_setup_destroy_master)
|
||||
{
|
||||
mb_communication_info_t master_config = {
|
||||
.ser_opts.port = TEST_SER_PORT_NUM,
|
||||
.ser_opts.mode = MB_RTU,
|
||||
.ser_opts.uid = MB_DEVICE_ADDR1,
|
||||
.ser_opts.data_bits = UART_DATA_8_BITS,
|
||||
.ser_opts.stop_bits = UART_STOP_BITS_2,
|
||||
.ser_opts.baudrate = 115200,
|
||||
.ser_opts.parity = UART_PARITY_DISABLE,
|
||||
.ser_opts.response_tout_ms = 1,
|
||||
.ser_opts.test_tout_us = TEST_SLAVE_SEND_TOUT_US
|
||||
};
|
||||
|
||||
ESP_LOGI(TAG, "TEST: Verify master create-destroy sequence.");
|
||||
|
||||
void *mbm_handle = NULL;
|
||||
mb_base_t *pmb_base = NULL;
|
||||
TEST_ESP_ERR(MB_ENOERR, mb_stub_create(&master_config.ser_opts, (void *)&pmb_base));
|
||||
|
||||
mbm_rtu_create_ExpectAnyArgsAndReturn(MB_ENOERR);
|
||||
mbm_rtu_create_ReturnThruPtr_in_out_obj((void **)&pmb_base);
|
||||
TEST_ESP_OK(mbc_master_create_serial(&master_config, &mbm_handle));
|
||||
TEST_ESP_OK(mbc_master_set_descriptor(mbm_handle, &descriptors[0], num_descriptors));
|
||||
TEST_ESP_OK(mbc_master_delete(mbm_handle));
|
||||
|
||||
master_config.ser_opts.mode = MB_ASCII;
|
||||
mbm_handle = NULL;
|
||||
pmb_base = NULL;
|
||||
|
||||
mbm_ascii_create_ExpectAnyArgsAndReturn(MB_EINVAL);
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mbc_master_create_serial(&master_config, &mbm_handle));
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mbc_master_set_descriptor(mbm_handle, &descriptors[0], num_descriptors));
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mbc_master_delete(mbm_handle));
|
||||
ESP_LOGI(TAG, "Test passed successfully.");
|
||||
}
|
||||
|
||||
TEST(unit_test_controller, test_setup_destroy_slave)
|
||||
{
|
||||
// Initialize and start Modbus controller
|
||||
mb_communication_info_t slave_config = {
|
||||
.ser_opts.port = TEST_SER_PORT_NUM,
|
||||
.ser_opts.mode = MB_RTU,
|
||||
.ser_opts.data_bits = UART_DATA_8_BITS,
|
||||
.ser_opts.stop_bits = UART_STOP_BITS_2,
|
||||
.ser_opts.baudrate = 115200,
|
||||
.ser_opts.parity = UART_PARITY_DISABLE,
|
||||
.ser_opts.response_tout_ms = TEST_MASTER_RESPOND_TOUT_MS,
|
||||
.ser_opts.test_tout_us = TEST_MASTER_SEND_TOUT_US
|
||||
};
|
||||
|
||||
ESP_LOGI(TAG, "TEST: Verify slave create-destroy sequence.");
|
||||
void *mbs_handle = NULL;
|
||||
mb_base_t *pmb_base = mbs_handle;
|
||||
TEST_ESP_ERR(MB_ENOERR, mb_stub_create(&slave_config.ser_opts, (void *)&pmb_base));
|
||||
mbs_rtu_create_ExpectAndReturn(&slave_config.ser_opts, (void *)pmb_base, MB_ENOERR);
|
||||
mbs_rtu_create_IgnoreArg_in_out_obj();
|
||||
mbs_rtu_create_ReturnThruPtr_in_out_obj((void **)&pmb_base);
|
||||
TEST_ESP_OK(mbc_slave_create_serial(&slave_config, &mbs_handle));
|
||||
TEST_ESP_OK(mbc_slave_delete(mbs_handle));
|
||||
|
||||
slave_config.ser_opts.mode = MB_ASCII;
|
||||
mbs_handle = NULL;
|
||||
mbs_ascii_create_ExpectAnyArgsAndReturn(MB_EILLSTATE);
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mbc_slave_create_serial(&slave_config, &mbs_handle));
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mbc_slave_delete(mbs_handle));
|
||||
ESP_LOGI(TAG, "Test passed successfully.");
|
||||
}
|
||||
|
||||
esp_err_t test_master_registers(int par_index, mb_err_enum_t mb_err)
|
||||
{
|
||||
mb_communication_info_t master_config = {
|
||||
.ser_opts.port = TEST_SER_PORT_NUM,
|
||||
.ser_opts.mode = MB_RTU,
|
||||
.ser_opts.uid = MB_DEVICE_ADDR1,
|
||||
.ser_opts.data_bits = UART_DATA_8_BITS,
|
||||
.ser_opts.stop_bits = UART_STOP_BITS_2,
|
||||
.ser_opts.baudrate = 115200,
|
||||
.ser_opts.parity = UART_PARITY_DISABLE,
|
||||
.ser_opts.response_tout_ms = 1,
|
||||
.ser_opts.test_tout_us = TEST_SLAVE_SEND_TOUT_US
|
||||
};
|
||||
mb_base_t *pmb_base = NULL; // fake mb_base handle
|
||||
void *mbm_handle = NULL;
|
||||
|
||||
TEST_ESP_ERR(MB_ENOERR, mb_stub_create(&master_config.ser_opts, (void *)&pmb_base));
|
||||
pmb_base->port_obj = (mb_port_base_t *)0x44556677;
|
||||
mbm_rtu_create_ExpectAnyArgsAndReturn(MB_ENOERR);
|
||||
mbm_rtu_create_ReturnThruPtr_in_out_obj((void **)&pmb_base);
|
||||
TEST_ESP_OK(mbc_master_create_serial(&master_config, &mbm_handle));
|
||||
TEST_ESP_OK(mbc_master_set_descriptor(mbm_handle, &descriptors[0], num_descriptors));
|
||||
mb_port_evt_res_take_ExpectAnyArgsAndReturn(true);
|
||||
mb_port_evt_res_release_ExpectAnyArgs();
|
||||
TEST_ESP_OK(mbc_master_start(mbm_handle));
|
||||
|
||||
const mb_parameter_descriptor_t *param_descriptor = NULL;
|
||||
esp_err_t err = mbc_master_get_cid_info(mbm_handle, par_index, ¶m_descriptor);
|
||||
if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL))
|
||||
{
|
||||
TEST_ASSERT_EQUAL_HEX32(&descriptors[par_index], param_descriptor);
|
||||
uint8_t type = 0; // type of parameter from dictionary
|
||||
uint8_t *pdata = (uint8_t *)calloc(1, param_descriptor->mb_size + 1);
|
||||
ESP_LOGI(TAG, "Test CID #%d, %s, %s", param_descriptor->cid, param_descriptor->param_key, param_descriptor->param_units);
|
||||
// This is to check the request function is called with appropriate params.
|
||||
switch(param_descriptor->mb_param_type) { \
|
||||
case MB_PARAM_INPUT: \
|
||||
mbm_rq_read_inp_reg_ExpectAndReturn(pmb_base, \
|
||||
param_descriptor->mb_slave_addr, \
|
||||
param_descriptor->mb_reg_start, \
|
||||
param_descriptor->mb_size, \
|
||||
1, \
|
||||
mb_err); \
|
||||
mbm_rq_read_inp_reg_IgnoreArg_tout(); \
|
||||
break; \
|
||||
case MB_PARAM_HOLDING:
|
||||
mbm_rq_read_holding_reg_ExpectAndReturn(pmb_base, \
|
||||
param_descriptor->mb_slave_addr, \
|
||||
param_descriptor->mb_reg_start, \
|
||||
param_descriptor->mb_size, \
|
||||
1, \
|
||||
mb_err); \
|
||||
mbm_rq_read_holding_reg_IgnoreArg_tout(); \
|
||||
break; \
|
||||
case MB_PARAM_COIL: \
|
||||
mbm_rq_read_coils_ExpectAndReturn(pmb_base, \
|
||||
param_descriptor->mb_slave_addr, \
|
||||
param_descriptor->mb_reg_start, \
|
||||
param_descriptor->mb_size, \
|
||||
1, \
|
||||
mb_err); \
|
||||
mbm_rq_read_coils_IgnoreArg_tout(); \
|
||||
break; \
|
||||
case MB_PARAM_DISCRETE: \
|
||||
mbm_rq_read_discrete_inputs_ExpectAndReturn(pmb_base, \
|
||||
param_descriptor->mb_slave_addr, \
|
||||
param_descriptor->mb_reg_start, \
|
||||
param_descriptor->mb_size, \
|
||||
1, \
|
||||
mb_err); \
|
||||
mbm_rq_read_discrete_inputs_IgnoreArg_tout(); \
|
||||
break; \
|
||||
default:
|
||||
TEST_FAIL(); \
|
||||
break; \
|
||||
}
|
||||
err = mbc_master_get_parameter(mbm_handle, par_index, pdata, &type); \
|
||||
free(pdata);
|
||||
}
|
||||
TEST_ESP_OK(mbc_master_stop(mbm_handle));
|
||||
TEST_ESP_OK(mbc_master_delete(mbm_handle));
|
||||
ESP_LOGI(TAG, "Test passed successfully.");
|
||||
return err;
|
||||
}
|
||||
|
||||
// Check if modbus controller object forms correct modbus request from data dictionary
|
||||
// and is able to transfer data using mb_object. Check possible errors returned back from
|
||||
// mb_object and make sure the modbus controller handles them correctly.
|
||||
TEST(unit_test_controller, test_master_send_request)
|
||||
{
|
||||
TEST_ESP_ERR(ESP_OK, test_master_registers(CID_DEV_REG0_INPUT, MB_ENOERR));
|
||||
TEST_ESP_ERR(ESP_ERR_TIMEOUT, test_master_registers(CID_DEV_REG0_INPUT, MB_ETIMEDOUT));
|
||||
TEST_ESP_ERR(ESP_OK, test_master_registers(CID_DEV_REG0_HOLD, MB_ENOERR));
|
||||
TEST_ESP_ERR(ESP_ERR_TIMEOUT, test_master_registers(CID_DEV_REG0_HOLD, MB_ETIMEDOUT));
|
||||
TEST_ESP_ERR(ESP_OK, test_master_registers(CID_DEV_REG0_COIL, MB_ENOERR));
|
||||
TEST_ESP_ERR(ESP_ERR_TIMEOUT, test_master_registers(CID_DEV_REG0_COIL, MB_ETIMEDOUT));
|
||||
TEST_ESP_ERR(ESP_OK, test_master_registers(CID_DEV_REG0_DISCRITE, MB_ENOERR));
|
||||
TEST_ESP_ERR(ESP_ERR_TIMEOUT, test_master_registers(CID_DEV_REG0_DISCRITE, MB_ETIMEDOUT));
|
||||
}
|
||||
|
||||
TEST_GROUP_RUNNER(unit_test_controller)
|
||||
{
|
||||
RUN_TEST_CASE(unit_test_controller, test_setup_destroy_master);
|
||||
RUN_TEST_CASE(unit_test_controller, test_setup_destroy_slave);
|
||||
RUN_TEST_CASE(unit_test_controller, test_master_send_request);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
CONFIGS = [
|
||||
pytest.param('serial', marks=[pytest.mark.esp32, pytest.mark.esp32s2, pytest.mark.esp32s3, pytest.mark.esp32c3]),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.generic_multi_device
|
||||
@pytest.mark.parametrize('config', CONFIGS, indirect=True)
|
||||
def test_modbus_controller_common(dut: Dut) -> None:
|
||||
dut.expect_unity_test_output()
|
||||
@@ -0,0 +1,23 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
#
|
||||
# Modbus configuration
|
||||
#
|
||||
CONFIG_UNITY_ENABLE_FIXTURE=y
|
||||
CONFIG_FMB_PORT_TASK_STACK_SIZE=4096
|
||||
CONFIG_FMB_PORT_TASK_PRIO=10
|
||||
CONFIG_FMB_COMM_MODE_RTU_EN=y
|
||||
CONFIG_FMB_COMM_MODE_ASCII_EN=y
|
||||
CONFIG_FMB_COMM_MODE_TCP_EN=n
|
||||
CONFIG_FMB_TCP_UID_ENABLED=y
|
||||
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=2000
|
||||
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
|
||||
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
|
||||
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
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "../../test_common")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "../test_stubs")
|
||||
|
||||
#set(COMPONENTS driver esp_timer esp_event esp_netif main)
|
||||
|
||||
# list(APPEND EXTRA_COMPONENT_DIRS
|
||||
# "$ENV{IDF_PATH}/tools/mocks/lwip/"
|
||||
# "$ENV{IDF_PATH}/tools/mocks/freertos/"
|
||||
# "$ENV{IDF_PATH}/tools/mocks/esp_timer/"
|
||||
# )
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(test_mb_controller_mapping_unit)
|
||||
@@ -0,0 +1,4 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
This test app is used to test modbus interface.
|
||||
@@ -0,0 +1,9 @@
|
||||
# NOTE: This kind of mocking currently works on Linux targets only.
|
||||
# On Espressif chips, too many dependencies are missing at the moment.
|
||||
|
||||
idf_component_mock(INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
REQUIRES cmock test_common
|
||||
MOCK_HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_mbm_object.h)
|
||||
|
||||
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
:cmock:
|
||||
:plugins:
|
||||
- expect
|
||||
- expect_any_args
|
||||
- return_thru_ptr
|
||||
- array
|
||||
- ignore
|
||||
- ignore_arg
|
||||
- callback
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "mb_config.h"
|
||||
#include "mb_common.h"
|
||||
#include "mb_port_types.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
/* Common definitions */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MB_PDU_REQ_READ_ADDR_OFF (MB_PDU_DATA_OFF + 0)
|
||||
#define MB_PDU_REQ_READ_REGCNT_OFF (MB_PDU_DATA_OFF + 2)
|
||||
#define MB_PDU_REQ_READ_SIZE (4)
|
||||
#define MB_PDU_FUNC_READ_REGCNT_MAX (0x007D)
|
||||
#define MB_PDU_FUNC_READ_BYTECNT_OFF (MB_PDU_DATA_OFF + 0)
|
||||
#define MB_PDU_FUNC_READ_VALUES_OFF (MB_PDU_DATA_OFF + 1)
|
||||
#define MB_PDU_FUNC_READ_SIZE_MIN (1)
|
||||
|
||||
#define MB_PDU_REQ_WRITE_ADDR_OFF (MB_PDU_DATA_OFF + 0)
|
||||
#define MB_PDU_REQ_WRITE_VALUE_OFF (MB_PDU_DATA_OFF + 2)
|
||||
#define MB_PDU_REQ_WRITE_SIZE (4)
|
||||
#define MB_PDU_FUNC_WRITE_ADDR_OFF (MB_PDU_DATA_OFF + 0)
|
||||
#define MB_PDU_FUNC_WRITE_VALUE_OFF (MB_PDU_DATA_OFF + 2)
|
||||
#define MB_PDU_FUNC_WRITE_SIZE (4)
|
||||
|
||||
#define MB_PDU_REQ_WRITE_MUL_ADDR_OFF (MB_PDU_DATA_OFF + 0)
|
||||
#define MB_PDU_REQ_WRITE_MUL_REGCNT_OFF (MB_PDU_DATA_OFF + 2)
|
||||
#define MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF (MB_PDU_DATA_OFF + 4)
|
||||
#define MB_PDU_REQ_WRITE_MUL_VALUES_OFF (MB_PDU_DATA_OFF + 5)
|
||||
#define MB_PDU_REQ_WRITE_MUL_SIZE_MIN (5)
|
||||
#define MB_PDU_REQ_WRITE_MUL_REGCNT_MAX (0x0078)
|
||||
#define MB_PDU_FUNC_WRITE_MUL_ADDR_OFF (MB_PDU_DATA_OFF + 0)
|
||||
#define MB_PDU_FUNC_WRITE_MUL_REGCNT_OFF (MB_PDU_DATA_OFF + 2)
|
||||
#define MB_PDU_FUNC_WRITE_MUL_SIZE (4)
|
||||
|
||||
#define MB_PDU_REQ_READWRITE_READ_ADDR_OFF (MB_PDU_DATA_OFF + 0)
|
||||
#define MB_PDU_REQ_READWRITE_READ_REGCNT_OFF (MB_PDU_DATA_OFF + 2)
|
||||
#define MB_PDU_REQ_READWRITE_WRITE_ADDR_OFF (MB_PDU_DATA_OFF + 4)
|
||||
#define MB_PDU_REQ_READWRITE_WRITE_REGCNT_OFF (MB_PDU_DATA_OFF + 6)
|
||||
#define MB_PDU_REQ_READWRITE_WRITE_BYTECNT_OFF (MB_PDU_DATA_OFF + 8)
|
||||
#define MB_PDU_REQ_READWRITE_WRITE_VALUES_OFF (MB_PDU_DATA_OFF + 9)
|
||||
#define MB_PDU_REQ_READWRITE_SIZE_MIN (9)
|
||||
#define MB_PDU_FUNC_READWRITE_READ_BYTECNT_OFF (MB_PDU_DATA_OFF + 0)
|
||||
#define MB_PDU_FUNC_READWRITE_READ_VALUES_OFF (MB_PDU_DATA_OFF + 1)
|
||||
#define MB_PDU_FUNC_READWRITE_SIZE_MIN (1)
|
||||
|
||||
#define MB_PDU_REQ_READ_ADDR_OFF (MB_PDU_DATA_OFF + 0)
|
||||
#define MB_PDU_REQ_READ_COILCNT_OFF (MB_PDU_DATA_OFF + 2)
|
||||
#define MB_PDU_REQ_READ_SIZE (4)
|
||||
#define MB_PDU_FUNC_READ_COILCNT_OFF (MB_PDU_DATA_OFF + 0)
|
||||
#define MB_PDU_FUNC_READ_VALUES_OFF (MB_PDU_DATA_OFF + 1)
|
||||
#define MB_PDU_FUNC_READ_SIZE_MIN (1)
|
||||
|
||||
#define MB_PDU_REQ_READ_ADDR_OFF (MB_PDU_DATA_OFF + 0)
|
||||
#define MB_PDU_REQ_READ_DISCCNT_OFF (MB_PDU_DATA_OFF + 2)
|
||||
#define MB_PDU_REQ_READ_SIZE (4)
|
||||
#define MB_PDU_FUNC_READ_DISCCNT_OFF (MB_PDU_DATA_OFF + 0)
|
||||
#define MB_PDU_FUNC_READ_VALUES_OFF (MB_PDU_DATA_OFF + 1)
|
||||
#define MB_PDU_FUNC_READ_SIZE_MIN (1)
|
||||
#define MB_PDU_REQ_WRITE_MUL_COILCNT_OFF (MB_PDU_DATA_OFF + 2)
|
||||
|
||||
typedef struct mb_base_t mb_base_t; /*!< Type of modbus object */
|
||||
typedef struct mb_port_base_t mb_port_base_t;
|
||||
|
||||
bool mb_port_evt_get(mb_port_base_t *inst, mb_event_t *pevent);
|
||||
bool mb_port_evt_post(mb_port_base_t *inst, mb_event_t event);
|
||||
bool mb_port_evt_res_take(mb_port_base_t *inst, uint32_t timeout);
|
||||
void mb_port_evt_res_release(mb_port_base_t *inst);
|
||||
mb_err_enum_t mb_port_evt_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;
|
||||
|
||||
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);
|
||||
|
||||
mb_err_enum_t mbm_rtu_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
|
||||
mb_err_enum_t mbm_ascii_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
mb_err_enum_t mbs_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj);
|
||||
|
||||
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);
|
||||
mb_err_enum_t mbs_set_slv_id(mb_base_t *inst, uint8_t slv_id, bool is_running, uint8_t const *slv_idstr, uint16_t slv_idstr_len);
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
|
||||
|
||||
typedef struct _port_tcp_opts mb_tcp_opts_t;
|
||||
|
||||
mb_err_enum_t mbm_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj);
|
||||
|
||||
#endif
|
||||
|
||||
mb_err_enum_t mbm_delete(mb_base_t *inst);
|
||||
mb_err_enum_t mbm_enable(mb_base_t *inst);
|
||||
mb_err_enum_t mbm_disable(mb_base_t *inst);
|
||||
mb_err_enum_t mbm_poll(mb_base_t *inst);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_mb_controller_unit.c"
|
||||
)
|
||||
|
||||
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
|
||||
idf_component_register(SRCS ${srcs}
|
||||
PRIV_REQUIRES cmock unity test_stubs test_utils mocked_esp_modbus ) # test_common
|
||||
|
||||
# The workaround for WHOLE_ARCHIVE, which is absent in v4.4
|
||||
set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u mb_test_include_impl")
|
||||
@@ -0,0 +1,45 @@
|
||||
menu "Modbus Test Configuration"
|
||||
|
||||
config MB_PORT_ADAPTER_EN
|
||||
bool "Enable Modbus port adapter to substitute hardware layer for test."
|
||||
default y
|
||||
help
|
||||
When option is enabled the port communication layer is substituted by
|
||||
port adapter layer to allow testing of higher layers without access to physical layer.
|
||||
|
||||
config MB_TEST_SLAVE_TASK_PRIO
|
||||
int "Modbus master test task priority"
|
||||
range 4 23
|
||||
default 4
|
||||
help
|
||||
Modbus master task priority for the test.
|
||||
|
||||
config MB_TEST_MASTER_TASK_PRIO
|
||||
int "Modbus slave test task priority"
|
||||
range 4 23
|
||||
default 4
|
||||
help
|
||||
Modbus slave task priority for the test.
|
||||
|
||||
config MB_TEST_COMM_CYCLE_COUNTER
|
||||
int "Modbus communication cycle counter for test"
|
||||
range 10 1000
|
||||
default 10
|
||||
help
|
||||
Modbus communication cycle counter for test.
|
||||
|
||||
config MB_TEST_LEAK_WARN_LEVEL
|
||||
int "Modbus test leak warning level"
|
||||
range 4 256
|
||||
default 32
|
||||
help
|
||||
Modbus test leak warning level.
|
||||
|
||||
config MB_TEST_LEAK_CRITICAL_LEVEL
|
||||
int "Modbus test leak critical level"
|
||||
range 4 1024
|
||||
default 64
|
||||
help
|
||||
Modbus test leak critical level.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,6 @@
|
||||
dependencies:
|
||||
idf: ">=4.3"
|
||||
espressif/esp-modbus:
|
||||
version: "^2.0"
|
||||
override_path: "../../../../"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "unity_fixture.h"
|
||||
|
||||
static void run_all_tests(void)
|
||||
{
|
||||
RUN_TEST_GROUP(unit_test_controller);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
UNITY_MAIN_FUNC(run_all_tests);
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include "unity_fixture.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "test_common.h"
|
||||
#include "mbc_master.h"
|
||||
#include "mbc_slave.h"
|
||||
|
||||
#include "Mocktest_mbm_object.h"
|
||||
#include "mb_object_stub.h"
|
||||
|
||||
#define TEST_SER_PORT_NUM 1
|
||||
#define TEST_TCP_PORT_NUM 1502
|
||||
#define TEST_TASKS_NUM 3
|
||||
#define TEST_TASK_TIMEOUT_MS 30000
|
||||
#define TEST_ALLOWED_LEAK 32
|
||||
#define TEST_SLAVE_SEND_TOUT_US 30000
|
||||
#define TEST_MASTER_SEND_TOUT_US 30000
|
||||
|
||||
#define TEST_MASTER_RESPOND_TOUT_MS CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND
|
||||
|
||||
#define TAG "MB_CONTROLLER_TEST"
|
||||
|
||||
// The workaround to statically link whole test library
|
||||
__attribute__((unused)) bool mb_test_include_impl = 1;
|
||||
|
||||
enum
|
||||
{
|
||||
CID_DEV_REG0_INPUT,
|
||||
CID_DEV_REG0_HOLD,
|
||||
CID_DEV_REG1_INPUT,
|
||||
CID_DEV_REG0_COIL,
|
||||
CID_DEV_REG0_DISCRITE,
|
||||
CID_DEV_INPUT_AREA,
|
||||
CID_DEV_HOLD_AREA,
|
||||
CID_DEV_COIL_AREA,
|
||||
CID_DEV_DISCR_AREA,
|
||||
};
|
||||
|
||||
#define TEST_AREA0_REG_OFFS 2
|
||||
#define TEST_HOLD_AREA0_REG_SZ 10
|
||||
#define TEST_COIL_AREA0_REG_SZ 10
|
||||
#define TEST_INPUT_AREA0_REG_SZ 10
|
||||
#define TEST_DISCR_AREA0_REG_SZ 10
|
||||
|
||||
static uint16_t input_registers[TEST_INPUT_AREA0_REG_SZ + 1] = {0};
|
||||
static uint16_t hold_registers[TEST_HOLD_AREA0_REG_SZ + 1] = {0};
|
||||
static uint16_t coil_registers[TEST_COIL_AREA0_REG_SZ + 1] = {0};
|
||||
static uint16_t discr_registers[TEST_COIL_AREA0_REG_SZ + 1] = {0};
|
||||
|
||||
// Example Data (Object) Dictionary for Modbus parameters
|
||||
static const mb_parameter_descriptor_t descriptors[] = {
|
||||
{CID_DEV_REG0_INPUT, STR("MB_input_reg-0"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2,
|
||||
(uint32_t)&input_registers[0], PARAM_TYPE_U32, 4, OPTS(0, 0, 0), PAR_PERMS_READ},
|
||||
{CID_DEV_REG0_HOLD, STR("MB_hold_reg-0"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 1, 2,
|
||||
(uint32_t)&hold_registers[1], PARAM_TYPE_U32, 4, OPTS(0, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
{CID_DEV_REG1_INPUT, STR("MB_input_reg-1"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 1,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
{CID_DEV_REG0_COIL, STR("MB_coil_reg-0"), STR("Bit"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 3, TEST_COIL_AREA0_REG_SZ,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0x03ff, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
{CID_DEV_REG0_DISCRITE, STR("MB_discr_reg-0"), STR("Bit"), MB_DEVICE_ADDR1, MB_PARAM_DISCRETE, 4, TEST_DISCR_AREA0_REG_SZ,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0x03fe, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
{CID_DEV_INPUT_AREA, STR("MB_input_area"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, TEST_AREA0_REG_OFFS, TEST_INPUT_AREA0_REG_SZ,
|
||||
(uint32_t)&input_registers[1], PARAM_TYPE_ASCII, (TEST_INPUT_AREA0_REG_SZ * 2), OPTS(0, 0, 0), PAR_PERMS_READ},
|
||||
{CID_DEV_HOLD_AREA, STR("MB_holding_area"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, TEST_AREA0_REG_OFFS, TEST_HOLD_AREA0_REG_SZ,
|
||||
(uint32_t)&hold_registers[1], PARAM_TYPE_ASCII, (TEST_HOLD_AREA0_REG_SZ * 2), OPTS(0, 0, 0), PAR_PERMS_READ},
|
||||
{CID_DEV_COIL_AREA, STR("MB_coil_area"), STR("Bit"), MB_DEVICE_ADDR1, MB_PARAM_COIL, TEST_AREA0_REG_OFFS, TEST_COIL_AREA0_REG_SZ,
|
||||
(uint32_t)&coil_registers[1], PARAM_TYPE_U16, ((TEST_COIL_AREA0_REG_SZ >> 3) + 1), OPTS(0, 0, 0), PAR_PERMS_READ},
|
||||
{CID_DEV_DISCR_AREA, STR("MB_discr_area"), STR("Bit"), MB_DEVICE_ADDR1, MB_PARAM_COIL, TEST_AREA0_REG_OFFS, TEST_DISCR_AREA0_REG_SZ,
|
||||
(uint32_t)&discr_registers[1], PARAM_TYPE_U16, ((TEST_DISCR_AREA0_REG_SZ >> 3) + 1), OPTS(0, 0, 0), PAR_PERMS_READ},
|
||||
};
|
||||
|
||||
// Calculate number of parameters in the table
|
||||
const uint16_t num_descriptors = (sizeof(descriptors) / sizeof(descriptors[0]));
|
||||
|
||||
TEST_GROUP(unit_test_controller);
|
||||
|
||||
TEST_SETUP(unit_test_controller)
|
||||
{
|
||||
test_common_start();
|
||||
}
|
||||
|
||||
TEST_TEAR_DOWN(unit_test_controller)
|
||||
{
|
||||
test_common_stop();
|
||||
}
|
||||
|
||||
static void test_slave_check_descriptor(int par_index)
|
||||
{
|
||||
mb_communication_info_t slave_config = {
|
||||
.ser_opts.port = TEST_SER_PORT_NUM,
|
||||
.ser_opts.mode = MB_RTU,
|
||||
.ser_opts.uid = MB_DEVICE_ADDR1,
|
||||
.ser_opts.data_bits = UART_DATA_8_BITS,
|
||||
.ser_opts.stop_bits = UART_STOP_BITS_1,
|
||||
.ser_opts.baudrate = 115200,
|
||||
.ser_opts.parity = UART_PARITY_DISABLE,
|
||||
.ser_opts.response_tout_ms = 0,
|
||||
.ser_opts.test_tout_us = 0
|
||||
};
|
||||
|
||||
void *mbs_handle = NULL;
|
||||
mb_base_t *pmb_base = NULL; // fake mb_base handle
|
||||
|
||||
TEST_ESP_ERR(MB_ENOERR, mb_stub_create(&slave_config.ser_opts, (void *)&pmb_base));
|
||||
pmb_base->port_obj = (mb_port_base_t *)0x44556677;
|
||||
mbs_rtu_create_ExpectAnyArgsAndReturn(MB_ENOERR);
|
||||
mbs_rtu_create_ReturnThruPtr_in_out_obj((void **)&pmb_base);
|
||||
|
||||
TEST_ESP_OK(mbc_slave_create_serial(&slave_config, &mbs_handle));
|
||||
TEST_ASSERT(mbs_handle);
|
||||
|
||||
mbs_controller_iface_t *mbs_iface = (mbs_controller_iface_t *)mbs_handle;
|
||||
//mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(mbs_iface);
|
||||
TEST_ASSERT_EQUAL_HEX32(mbs_iface->mb_base, pmb_base);
|
||||
|
||||
TEST_ASSERT_EQUAL_HEX32(pmb_base->rw_cbs.reg_input_cb, mbc_reg_input_slave_cb);
|
||||
TEST_ASSERT_EQUAL_HEX32(pmb_base->rw_cbs.reg_holding_cb, mbc_reg_holding_slave_cb);
|
||||
TEST_ASSERT_EQUAL_HEX32(pmb_base->rw_cbs.reg_coils_cb, mbc_reg_coils_slave_cb);
|
||||
TEST_ASSERT_EQUAL_HEX32(pmb_base->rw_cbs.reg_discrete_cb, mbc_reg_discrete_slave_cb);
|
||||
|
||||
mb_parameter_descriptor_t *pdescr = (mb_parameter_descriptor_t *)&descriptors[par_index];
|
||||
mb_register_area_descriptor_t reg_area;
|
||||
ESP_LOGI(TAG, "Test CID #%d, %s, %s", pdescr->cid, pdescr->param_key, pdescr->param_units);
|
||||
|
||||
uint16_t n_bytes = ((pdescr->mb_param_type == MB_PARAM_INPUT) || (pdescr->mb_param_type == MB_PARAM_HOLDING))
|
||||
? (pdescr->mb_size << 1) : ((pdescr->mb_size >> 3) + 1);
|
||||
|
||||
// First define the correct area
|
||||
reg_area.type = pdescr->mb_param_type;
|
||||
reg_area.start_offset = pdescr->mb_reg_start;
|
||||
reg_area.address = (void *)pdescr->param_offset;
|
||||
reg_area.size = n_bytes;
|
||||
ESP_LOGI(TAG, "Area (type, reg_start, address, size): %d, %u, 0x%" PRIx32 ", %d, is defined.",
|
||||
(int)reg_area.type, (unsigned)reg_area.start_offset, (uint32_t)reg_area.address, (int)reg_area.size);
|
||||
TEST_ESP_OK(mbc_slave_set_descriptor(mbs_handle, reg_area));
|
||||
|
||||
// Check additional area overlapped
|
||||
reg_area.start_offset = (pdescr->mb_reg_start + pdescr->mb_size - 2);
|
||||
reg_area.size = 2;
|
||||
ESP_LOGI(TAG, "Area overlapped (type, reg_start, address, size): %d, %u, 0x%" PRIx32 ", %d.",
|
||||
(int)reg_area.type, (unsigned)reg_area.start_offset, (uint32_t)reg_area.address, (int)reg_area.size);
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, mbc_slave_set_descriptor(mbs_handle, reg_area));
|
||||
|
||||
reg_area.start_offset = pdescr->mb_reg_start;
|
||||
reg_area.size = n_bytes;
|
||||
reg_area.address = (void *)pdescr->param_offset - 2;
|
||||
ESP_LOGI(TAG, "Area redefine (type, reg_start, address, size): %d, %u, 0x%" PRIx32 ", %d.",
|
||||
(int)reg_area.type, (unsigned)reg_area.start_offset, (uint32_t)reg_area.address, (int)reg_area.size);
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, mbc_slave_set_descriptor(mbs_handle, reg_area));
|
||||
|
||||
TEST_ESP_OK(mbc_slave_delete(mbs_handle)); // the destructor of mb controller destroys the fake mb_object as well
|
||||
TEST_ASSERT_EQUAL_HEX(mb_port_get_inst_counter(), 0);
|
||||
ESP_LOGI(TAG, "Test passed successfully.");
|
||||
}
|
||||
|
||||
static esp_err_t test_master_read_req(int par_index, mb_err_enum_t mb_err)
|
||||
{
|
||||
mb_communication_info_t master_config = {
|
||||
.ser_opts.port = TEST_SER_PORT_NUM,
|
||||
.ser_opts.mode = MB_RTU,
|
||||
.ser_opts.uid = MB_DEVICE_ADDR1,
|
||||
.ser_opts.data_bits = UART_DATA_8_BITS,
|
||||
.ser_opts.stop_bits = UART_STOP_BITS_2,
|
||||
.ser_opts.baudrate = 115200,
|
||||
.ser_opts.parity = UART_PARITY_DISABLE,
|
||||
.ser_opts.response_tout_ms = 1,
|
||||
.ser_opts.test_tout_us = TEST_SLAVE_SEND_TOUT_US};
|
||||
mb_base_t *pmb_base = NULL; // fake mb_base handle
|
||||
void *mbm_handle = NULL;
|
||||
|
||||
TEST_ESP_ERR(MB_ENOERR, mb_stub_create(&master_config.ser_opts, (void *)&pmb_base));
|
||||
pmb_base->port_obj = (mb_port_base_t *)0x44556677;
|
||||
mbm_rtu_create_ExpectAnyArgsAndReturn(MB_ENOERR);
|
||||
mbm_rtu_create_ReturnThruPtr_in_out_obj((void **)&pmb_base);
|
||||
TEST_ESP_OK(mbc_master_create_serial(&master_config, &mbm_handle));
|
||||
TEST_ESP_OK(mbc_master_set_descriptor(mbm_handle, &descriptors[0], num_descriptors));
|
||||
mb_port_evt_post_ExpectAndReturn(pmb_base->port_obj, EVENT(EV_FRAME_TRANSMIT | EV_TRANS_START), true);
|
||||
TEST_ESP_OK(mbc_master_start(mbm_handle));
|
||||
mb_port_evt_wait_req_finish_ExpectAndReturn(pmb_base->port_obj, mb_err);
|
||||
|
||||
const mb_parameter_descriptor_t *param_descriptor = NULL;
|
||||
TEST_ESP_OK(mbc_master_get_cid_info(mbm_handle, par_index, ¶m_descriptor));
|
||||
TEST_ASSERT_EQUAL_HEX32(&descriptors[par_index], param_descriptor);
|
||||
uint8_t type = 0; // type of parameter from dictionary
|
||||
uint8_t pdata[100] = {0};
|
||||
ESP_LOGI(TAG, "Test CID #%d, %s, %s", param_descriptor->cid, param_descriptor->param_key, param_descriptor->param_units);
|
||||
mb_port_evt_res_take_ExpectAnyArgsAndReturn(true);
|
||||
mb_port_evt_res_release_ExpectAnyArgs();
|
||||
mb_port_evt_res_take_ExpectAnyArgsAndReturn(true);
|
||||
mb_port_evt_res_release_ExpectAnyArgs();
|
||||
|
||||
// Call the read method of modbus controller
|
||||
esp_err_t err = mbc_master_get_parameter(mbm_handle, par_index, pdata, &type);
|
||||
uint8_t *mb_frame_ptr = NULL;
|
||||
// get send buffer back using the fake mb_object
|
||||
pmb_base->get_send_buf(pmb_base, &mb_frame_ptr);
|
||||
TEST_ASSERT_EQUAL_HEX8(pmb_base->get_dest_addr(pmb_base), param_descriptor->mb_slave_addr);
|
||||
uint8_t send_len = pmb_base->get_send_len(pmb_base);
|
||||
TEST_ASSERT_EQUAL_HEX8(send_len, (MB_PDU_SIZE_MIN + MB_PDU_REQ_READ_SIZE));
|
||||
// Check that request function forms correct buffer
|
||||
switch (param_descriptor->mb_param_type)
|
||||
{
|
||||
case MB_PARAM_INPUT:
|
||||
// TEST_CHECK_EQ
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_FUNC_OFF], MB_FUNC_READ_INPUT_REGISTER);
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF], (param_descriptor->mb_reg_start >> 8));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF + 1], (param_descriptor->mb_reg_start & 0x00FF));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_REGCNT_OFF], (param_descriptor->mb_size >> 8));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_REGCNT_OFF + 1], (param_descriptor->mb_size & 0x00FF));
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG, (void *)mb_frame_ptr, send_len, ESP_LOG_INFO);
|
||||
break;
|
||||
case MB_PARAM_HOLDING:
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_FUNC_OFF], MB_FUNC_READ_HOLDING_REGISTER);
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF], (param_descriptor->mb_reg_start >> 8));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF + 1], (param_descriptor->mb_reg_start & 0x00FF));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_REGCNT_OFF], (param_descriptor->mb_size >> 8));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_REGCNT_OFF + 1], (param_descriptor->mb_size & 0x00FF));
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG, (void *)mb_frame_ptr, send_len, ESP_LOG_INFO);
|
||||
break;
|
||||
case MB_PARAM_COIL:
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_FUNC_OFF], MB_FUNC_READ_COILS);
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF], (param_descriptor->mb_reg_start >> 8));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF + 1], (param_descriptor->mb_reg_start & 0x00FF));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_COILCNT_OFF], (param_descriptor->mb_size >> 8));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_COILCNT_OFF + 1], (param_descriptor->mb_size & 0x00FF));
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG, (void *)mb_frame_ptr, send_len, ESP_LOG_INFO);
|
||||
break;
|
||||
case MB_PARAM_DISCRETE:
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_FUNC_OFF], MB_FUNC_READ_DISCRETE_INPUTS);
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF], (param_descriptor->mb_reg_start >> 8));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_ADDR_OFF + 1], (param_descriptor->mb_reg_start & 0x00FF));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_DISCCNT_OFF], (param_descriptor->mb_size >> 8));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_READ_DISCCNT_OFF + 1], (param_descriptor->mb_size & 0x00FF));
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG, (void *)mb_frame_ptr, send_len, ESP_LOG_INFO);
|
||||
break;
|
||||
default:
|
||||
TEST_FAIL();
|
||||
break;
|
||||
}
|
||||
TEST_ESP_OK(mbc_master_stop(mbm_handle));
|
||||
TEST_ESP_OK(mbc_master_delete(mbm_handle)); // the destructor of mb controller destroys the fake mb_object as well
|
||||
TEST_ASSERT_EQUAL_HEX(mb_port_get_inst_counter(), 0);
|
||||
ESP_LOGI(TAG, "Test passed successfully.");
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t test_master_write_req(int par_index, mb_err_enum_t mb_err)
|
||||
{
|
||||
mb_communication_info_t master_config = {
|
||||
.ser_opts.port = TEST_SER_PORT_NUM,
|
||||
.ser_opts.mode = MB_RTU,
|
||||
.ser_opts.uid = MB_DEVICE_ADDR1,
|
||||
.ser_opts.data_bits = UART_DATA_8_BITS,
|
||||
.ser_opts.stop_bits = UART_STOP_BITS_2,
|
||||
.ser_opts.baudrate = 115200,
|
||||
.ser_opts.parity = UART_PARITY_DISABLE,
|
||||
.ser_opts.response_tout_ms = 1,
|
||||
.ser_opts.test_tout_us = TEST_SLAVE_SEND_TOUT_US};
|
||||
mb_base_t *pmb_base = NULL; // fake mb_base handle
|
||||
void *mbm_handle = NULL;
|
||||
|
||||
TEST_ESP_ERR(MB_ENOERR, mb_stub_create(&master_config.ser_opts, (void *)&pmb_base));
|
||||
pmb_base->port_obj = (mb_port_base_t *)0x44556677;
|
||||
mbm_rtu_create_ExpectAnyArgsAndReturn(MB_ENOERR);
|
||||
mbm_rtu_create_ReturnThruPtr_in_out_obj((void **)&pmb_base);
|
||||
TEST_ESP_OK(mbc_master_create_serial(&master_config, &mbm_handle));
|
||||
TEST_ESP_OK(mbc_master_set_descriptor(mbm_handle, &descriptors[0], num_descriptors));
|
||||
mb_port_evt_post_ExpectAndReturn(pmb_base->port_obj, EVENT(EV_FRAME_TRANSMIT | EV_TRANS_START), true);
|
||||
mb_port_evt_wait_req_finish_ExpectAndReturn(pmb_base->port_obj, mb_err);
|
||||
TEST_ESP_OK(mbc_master_start(mbm_handle));
|
||||
|
||||
const mb_parameter_descriptor_t *param_descriptor = NULL;
|
||||
TEST_ESP_OK(mbc_master_get_cid_info(mbm_handle, par_index, ¶m_descriptor));
|
||||
TEST_ASSERT_EQUAL_HEX32(&descriptors[par_index], param_descriptor);
|
||||
uint8_t type = 0; // type of parameter from dictionary
|
||||
uint8_t reg_data[] = {0x11, 0x22, 0x33, 0x44};
|
||||
ESP_LOGI(TAG, "Test CID #%d, %s, %s", param_descriptor->cid, param_descriptor->param_key, param_descriptor->param_units);
|
||||
mb_port_evt_res_take_ExpectAnyArgsAndReturn(true);
|
||||
mb_port_evt_res_release_ExpectAnyArgs();
|
||||
mb_port_evt_res_take_ExpectAnyArgsAndReturn(true);
|
||||
mb_port_evt_res_release_ExpectAnyArgs();
|
||||
|
||||
// Call the read method of modbus controller
|
||||
esp_err_t err = mbc_master_set_parameter(mbm_handle, par_index, reg_data, &type);
|
||||
uint8_t *mb_frame_ptr = NULL;
|
||||
// get send buffer back using the fake mb_object
|
||||
pmb_base->get_send_buf(pmb_base, &mb_frame_ptr);
|
||||
TEST_ASSERT_EQUAL_HEX8(pmb_base->get_dest_addr(pmb_base), param_descriptor->mb_slave_addr);
|
||||
uint8_t send_len = pmb_base->get_send_len(pmb_base);
|
||||
// Check that request function forms correct buffer
|
||||
switch (param_descriptor->mb_param_type)
|
||||
{
|
||||
case MB_PARAM_HOLDING:
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_FUNC_OFF], MB_FUNC_WRITE_MULTIPLE_REGISTERS);
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_ADDR_OFF], (param_descriptor->mb_reg_start >> 8));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_ADDR_OFF + 1], (param_descriptor->mb_reg_start & 0x00FF));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_REGCNT_OFF], (param_descriptor->mb_size >> 8));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_REGCNT_OFF + 1], (param_descriptor->mb_size & 0x00FF));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF], (param_descriptor->mb_size << 1));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF], sizeof(reg_data));
|
||||
TEST_ASSERT_EQUAL_HEX8(send_len, ((MB_PDU_SIZE_MIN + MB_PDU_REQ_WRITE_MUL_SIZE_MIN + 2 * param_descriptor->mb_size)));
|
||||
for (int i = 0; (i < param_descriptor->mb_size); i++)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_HEX8(reg_data[0], mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_VALUES_OFF + 1]);
|
||||
TEST_ASSERT_EQUAL_HEX8(reg_data[1], mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_VALUES_OFF]);
|
||||
}
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG, (void *)mb_frame_ptr, send_len, ESP_LOG_INFO);
|
||||
// TEST_ESP_ERR(MB_ENOERR, mbs_fn_write_holding_reg(pmb_base, mb_frame_ptr, &send_len));
|
||||
break;
|
||||
case MB_PARAM_COIL:
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_FUNC_OFF], MB_FUNC_WRITE_MULTIPLE_COILS);
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_ADDR_OFF], (param_descriptor->mb_reg_start >> 8));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_ADDR_OFF + 1], (param_descriptor->mb_reg_start & 0x00FF));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_COILCNT_OFF], (param_descriptor->mb_size >> 8));
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_COILCNT_OFF + 1], (param_descriptor->mb_size & 0x00FF));
|
||||
uint8_t byte_cnt = (param_descriptor->mb_size & 0x0007) ? ((param_descriptor->mb_size >> 3) + 1) : (param_descriptor->mb_size >> 3);
|
||||
TEST_ASSERT_EQUAL_HEX8(mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF], byte_cnt);
|
||||
TEST_ASSERT_EQUAL_HEX8(send_len, (MB_PDU_SIZE_MIN + MB_PDU_REQ_WRITE_MUL_SIZE_MIN + byte_cnt));
|
||||
TEST_ASSERT_EQUAL_HEX8(reg_data[0], mb_frame_ptr[MB_PDU_REQ_WRITE_MUL_VALUES_OFF]);
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG, (void *)mb_frame_ptr, send_len, ESP_LOG_INFO);
|
||||
break;
|
||||
default:
|
||||
TEST_FAIL();
|
||||
break;
|
||||
}
|
||||
TEST_ESP_OK(mbc_master_stop(mbm_handle));
|
||||
TEST_ESP_OK(mbc_master_delete(mbm_handle));
|
||||
TEST_ASSERT_EQUAL_HEX(mb_port_get_inst_counter(), 0);
|
||||
ESP_LOGI(TAG, "Test passed successfully.");
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t test_master_check_callback(int par_index, mb_err_enum_t mb_err)
|
||||
{
|
||||
mb_communication_info_t master_config = {
|
||||
.ser_opts.port = TEST_SER_PORT_NUM,
|
||||
.ser_opts.mode = MB_RTU,
|
||||
.ser_opts.uid = MB_DEVICE_ADDR1,
|
||||
.ser_opts.data_bits = UART_DATA_8_BITS,
|
||||
.ser_opts.stop_bits = UART_STOP_BITS_2,
|
||||
.ser_opts.baudrate = 115200,
|
||||
.ser_opts.parity = UART_PARITY_DISABLE,
|
||||
.ser_opts.response_tout_ms = 1,
|
||||
.ser_opts.test_tout_us = TEST_SLAVE_SEND_TOUT_US};
|
||||
mb_base_t *pmb_base = NULL; // fake mb_base handle
|
||||
void *mbm_handle = NULL;
|
||||
|
||||
TEST_ESP_ERR(MB_ENOERR, mb_stub_create(&master_config.ser_opts, (void *)&pmb_base));
|
||||
pmb_base->port_obj = (mb_port_base_t *)0x44556677;
|
||||
mbm_rtu_create_ExpectAnyArgsAndReturn(MB_ENOERR);
|
||||
mbm_rtu_create_ReturnThruPtr_in_out_obj((void **)&pmb_base);
|
||||
TEST_ESP_OK(mbc_master_create_serial(&master_config, &mbm_handle));
|
||||
TEST_ESP_OK(mbc_master_set_descriptor(mbm_handle, &descriptors[0], num_descriptors));
|
||||
mbm_controller_iface_t *mbm_controller_iface = (mbm_controller_iface_t *)mbm_handle;
|
||||
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(mbm_controller_iface);
|
||||
TEST_ASSERT_EQUAL_HEX32(mbm_controller_iface->mb_base, pmb_base);
|
||||
|
||||
TEST_ASSERT_EQUAL_HEX32(pmb_base->rw_cbs.reg_input_cb, mbc_reg_input_master_cb);
|
||||
TEST_ASSERT_EQUAL_HEX32(pmb_base->rw_cbs.reg_holding_cb, mbc_reg_holding_master_cb);
|
||||
TEST_ASSERT_EQUAL_HEX32(pmb_base->rw_cbs.reg_coils_cb, mbc_reg_coils_master_cb);
|
||||
TEST_ASSERT_EQUAL_HEX32(pmb_base->rw_cbs.reg_discrete_cb, mbc_reg_discrete_master_cb);
|
||||
|
||||
TEST_ESP_OK(mbc_master_start(mbm_handle));
|
||||
|
||||
const mb_parameter_descriptor_t *param_descriptor = NULL;
|
||||
TEST_ESP_OK(mbc_master_get_cid_info(mbm_handle, par_index, ¶m_descriptor));
|
||||
TEST_ASSERT_EQUAL_HEX32(&descriptors[par_index], param_descriptor);
|
||||
uint8_t reg_data_in[] = {0x11, 0x22, 0x33, 0x44};
|
||||
uint8_t reg_data_out[4] = {0};
|
||||
mbm_opts->reg_buffer_size = param_descriptor->mb_size;
|
||||
mbm_opts->reg_buffer_ptr = ®_data_out[0];
|
||||
esp_err_t err = ESP_FAIL;
|
||||
uint8_t byte_cnt = 0;
|
||||
// Check that request function forms correct buffer
|
||||
switch (param_descriptor->mb_param_type)
|
||||
{
|
||||
case MB_PARAM_HOLDING:
|
||||
err = mbc_reg_holding_master_cb(pmb_base, reg_data_in, param_descriptor->mb_reg_start,
|
||||
param_descriptor->mb_size, MB_REG_READ);
|
||||
for (int i = 0; (i < param_descriptor->mb_size); i++)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_HEX8(reg_data_in[(i << 1)], reg_data_out[(i << 1) + 1]);
|
||||
TEST_ASSERT_EQUAL_HEX8(reg_data_in[(i << 1) + 1], reg_data_out[(i << 1)]);
|
||||
}
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG ", INPUT_BUFF", (void *)reg_data_in, (param_descriptor->mb_size << 1), ESP_LOG_INFO);
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG ", OUTPUT_BUFF", (void *)reg_data_out, (param_descriptor->mb_size << 1), ESP_LOG_INFO);
|
||||
break;
|
||||
|
||||
case MB_PARAM_INPUT:
|
||||
err = mbc_reg_input_master_cb(pmb_base, reg_data_in, param_descriptor->mb_reg_start,
|
||||
param_descriptor->mb_size);
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG ", INPUT_BUFF", (void *)reg_data_in, (param_descriptor->mb_size << 1), ESP_LOG_INFO);
|
||||
for (int i = 0; (i < param_descriptor->mb_size); i++)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_HEX8(reg_data_in[(i << 1)], reg_data_out[(i << 1) + 1]);
|
||||
TEST_ASSERT_EQUAL_HEX8(reg_data_in[(i << 1) + 1], reg_data_out[(i << 1)]);
|
||||
}
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG ", OUTPUT_BUFF", (void *)reg_data_out, (param_descriptor->mb_size << 1), ESP_LOG_INFO);
|
||||
break;
|
||||
|
||||
case MB_PARAM_COIL:
|
||||
reg_data_in[0] = 0xFF;
|
||||
reg_data_in[1] = 0xFF;
|
||||
err = mbc_reg_coils_master_cb(pmb_base, reg_data_in, param_descriptor->mb_reg_start, param_descriptor->mb_size, MB_REG_READ);
|
||||
byte_cnt = (param_descriptor->mb_size & 0x0007) ? ((param_descriptor->mb_size >> 3) + 1) : (param_descriptor->mb_size >> 3);
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG ", INPUT_BUFF", (void *)reg_data_in, byte_cnt, ESP_LOG_INFO);
|
||||
TEST_ASSERT_EQUAL_HEX8((reg_data_out[0] & param_descriptor->param_opts.opt1), param_descriptor->param_opts.opt1);
|
||||
TEST_ASSERT_EQUAL_HEX8((reg_data_out[1] & ((param_descriptor->param_opts.opt1 >> 8) & 0xFF)), ((param_descriptor->param_opts.opt1 >> 8) & 0xFF));
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG ", OUTPUT_BUFF", (void *)reg_data_out, byte_cnt, ESP_LOG_INFO);
|
||||
break;
|
||||
|
||||
case MB_PARAM_DISCRETE:
|
||||
reg_data_in[0] = 0xFF;
|
||||
reg_data_in[1] = 0xFF;
|
||||
err = mbc_reg_discrete_master_cb(pmb_base, reg_data_in, param_descriptor->mb_reg_start, param_descriptor->mb_size);
|
||||
byte_cnt = (param_descriptor->mb_size & 0x0007) ? ((param_descriptor->mb_size >> 3) + 1) : (param_descriptor->mb_size >> 3);
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG ", INPUT_BUFF", (void *)reg_data_in, byte_cnt, ESP_LOG_INFO);
|
||||
TEST_ASSERT_EQUAL_HEX8((reg_data_out[0] & param_descriptor->param_opts.opt1), param_descriptor->param_opts.opt1);
|
||||
TEST_ASSERT_EQUAL_HEX8((reg_data_out[1] & ((param_descriptor->param_opts.opt1 >> 8) & 0xFF)), ((param_descriptor->param_opts.opt1 >> 8) & 0xFF));
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG ", OUTPUT_BUFF", (void *)reg_data_out, byte_cnt, ESP_LOG_INFO);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
TEST_ESP_OK(mbc_master_stop(mbm_handle));
|
||||
TEST_ESP_OK(mbc_master_delete(mbm_handle));
|
||||
ESP_LOGI(TAG, "Test passed successfully.");
|
||||
return err;
|
||||
}
|
||||
|
||||
// Check if modbus controller object forms correct modbus request from data dictionary
|
||||
// and is able to transfer data using mb_object. Check possible errors returned back from
|
||||
// mb_object and make sure the modbus controller handles them correctly.
|
||||
TEST(unit_test_controller, test_master_send_read_request)
|
||||
{
|
||||
ESP_LOGI(TAG, "TEST: Check the modbus master controller handles read requests correctly.");
|
||||
TEST_ESP_ERR(ESP_OK, test_master_read_req(CID_DEV_REG0_INPUT, MB_ENOERR));
|
||||
TEST_ESP_ERR(ESP_ERR_TIMEOUT, test_master_read_req(CID_DEV_REG0_INPUT, MB_ETIMEDOUT));
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_RESPONSE, test_master_read_req(CID_DEV_REG0_INPUT, MB_ERECVDATA));
|
||||
TEST_ESP_ERR(ESP_OK, test_master_read_req(CID_DEV_REG0_HOLD, MB_ENOERR));
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_RESPONSE, test_master_read_req(CID_DEV_REG0_HOLD, MB_ERECVDATA));
|
||||
TEST_ESP_ERR(ESP_OK, test_master_read_req(CID_DEV_REG0_COIL, MB_ENOERR));
|
||||
TEST_ESP_ERR(ESP_ERR_TIMEOUT, test_master_read_req(CID_DEV_REG0_COIL, MB_ETIMEDOUT));
|
||||
TEST_ESP_ERR(ESP_OK, test_master_read_req(CID_DEV_REG0_DISCRITE, MB_ENOERR));
|
||||
TEST_ESP_ERR(ESP_ERR_TIMEOUT, test_master_read_req(CID_DEV_REG0_DISCRITE, MB_ETIMEDOUT));
|
||||
}
|
||||
|
||||
TEST(unit_test_controller, test_master_send_write_request)
|
||||
{
|
||||
ESP_LOGI(TAG, "TEST: Check the modbus master controller handles write requests correctly.");
|
||||
TEST_ESP_ERR(ESP_OK, test_master_write_req(CID_DEV_REG0_HOLD, MB_ENOERR));
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_RESPONSE, test_master_write_req(CID_DEV_REG0_HOLD, MB_ERECVDATA));
|
||||
TEST_ESP_ERR(ESP_OK, test_master_write_req(CID_DEV_REG0_COIL, MB_ENOERR));
|
||||
TEST_ESP_ERR(ESP_ERR_TIMEOUT, test_master_write_req(CID_DEV_REG0_COIL, MB_ETIMEDOUT));
|
||||
}
|
||||
|
||||
TEST(unit_test_controller, test_slave_check_area_descriptor)
|
||||
{
|
||||
ESP_LOGI(TAG, "TEST: Check the modbus master controller defines the area descriptors correctly.");
|
||||
test_slave_check_descriptor(CID_DEV_INPUT_AREA);
|
||||
test_slave_check_descriptor(CID_DEV_HOLD_AREA);
|
||||
test_slave_check_descriptor(CID_DEV_COIL_AREA);
|
||||
test_slave_check_descriptor(CID_DEV_DISCR_AREA);
|
||||
}
|
||||
|
||||
TEST(unit_test_controller, test_master_register_callbacks)
|
||||
{
|
||||
ESP_LOGI(TAG, "TEST: Check the modbus master controller handles mapping callback functions correctly.");
|
||||
TEST_ESP_ERR(ESP_OK, test_master_check_callback(CID_DEV_REG0_HOLD, MB_ENOERR));
|
||||
TEST_ESP_ERR(ESP_OK, test_master_check_callback(CID_DEV_REG0_INPUT, MB_ENOERR));
|
||||
TEST_ESP_ERR(ESP_OK, test_master_check_callback(CID_DEV_REG0_COIL, MB_ENOERR));
|
||||
TEST_ESP_ERR(ESP_OK, test_master_check_callback(CID_DEV_REG0_DISCRITE, MB_ENOERR));
|
||||
}
|
||||
|
||||
TEST_GROUP_RUNNER(unit_test_controller)
|
||||
{
|
||||
RUN_TEST_CASE(unit_test_controller, test_master_send_read_request);
|
||||
RUN_TEST_CASE(unit_test_controller, test_master_send_write_request);
|
||||
RUN_TEST_CASE(unit_test_controller, test_master_register_callbacks);
|
||||
RUN_TEST_CASE(unit_test_controller, test_slave_check_area_descriptor);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
CONFIGS = [
|
||||
pytest.param('serial', marks=[pytest.mark.esp32, pytest.mark.esp32s2, pytest.mark.esp32s3, pytest.mark.esp32c3]),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.generic_multi_device
|
||||
@pytest.mark.parametrize('config', CONFIGS, indirect=True)
|
||||
def test_modbus_controller_mapping(dut: Dut) -> None:
|
||||
dut.expect_unity_test_output()
|
||||
@@ -0,0 +1,19 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
#
|
||||
# Modbus configuration
|
||||
#
|
||||
CONFIG_UNITY_ENABLE_FIXTURE=y
|
||||
CONFIG_FMB_PORT_TASK_STACK_SIZE=4096
|
||||
CONFIG_FMB_PORT_TASK_PRIO=10
|
||||
CONFIG_FMB_COMM_MODE_RTU_EN=y
|
||||
CONFIG_FMB_COMM_MODE_ASCII_EN=y
|
||||
CONFIG_FMB_COMM_MODE_TCP_EN=n
|
||||
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=2000
|
||||
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
|
||||
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
|
||||
CONFIG_MB_PORT_ADAPTER_EN=y
|
||||
CONFIG_MB_TEST_LEAK_CRITICAL_LEVEL=128
|
||||
CONFIG_MB_TEST_LEAK_WARN_LEVEL=128
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
#
|
||||
# Modbus configuration
|
||||
#
|
||||
CONFIG_UNITY_ENABLE_FIXTURE=y
|
||||
CONFIG_FMB_PORT_TASK_STACK_SIZE=4096
|
||||
CONFIG_FMB_PORT_TASK_PRIO=10
|
||||
CONFIG_FMB_COMM_MODE_RTU_EN=y
|
||||
CONFIG_FMB_COMM_MODE_ASCII_EN=y
|
||||
CONFIG_FMB_COMM_MODE_TCP_EN=n
|
||||
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=2000
|
||||
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
|
||||
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
|
||||
CONFIG_MB_PORT_ADAPTER_EN=y
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
set(srcs "src/mb_object_stub.c")
|
||||
|
||||
idf_component_register( SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES test_common unity)
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sdkconfig.h>
|
||||
#include "esp_log.h"
|
||||
#include "mb_common.h"
|
||||
#include "mb_port_types.h"
|
||||
|
||||
mb_err_enum_t mb_stub_create(mb_serial_opts_t *ser_opts, void **in_out_obj);
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mb_types.h"
|
||||
#include "port_common.h"
|
||||
#include "mb_port_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct mb_trans_base_t mb_trans_base_t; /*!< Type of moddus transport object */
|
||||
typedef struct _obj_descr obj_descr_t;
|
||||
|
||||
typedef void (*mb_frm_start_fp)(mb_trans_base_t *transport);
|
||||
typedef void (*mb_frm_stop_fp)(mb_trans_base_t *transport);
|
||||
typedef mb_err_enum_t (*mb_frm_rcv_fp)(mb_trans_base_t *transport, uint8_t *rcv_addr_buf, uint8_t **frame_ptr_buf, uint16_t *len_buf);
|
||||
typedef mb_err_enum_t (*mb_frm_snd_fp)(mb_trans_base_t *transport, uint8_t slv_addr, const uint8_t *frame_ptr, uint16_t len);
|
||||
typedef void (*mb_get_rx_frm_fp) (mb_trans_base_t *transport, uint8_t **frame_ptr_buf);
|
||||
typedef void (*mb_get_tx_frm_fp) (mb_trans_base_t *transport, uint8_t **frame_ptr_buf);
|
||||
typedef bool (*mb_get_fp)(mb_trans_base_t *inst);
|
||||
|
||||
struct mb_trans_base_t
|
||||
{
|
||||
obj_descr_t descr;
|
||||
|
||||
_lock_t lock;
|
||||
mb_port_base_t *port_obj;
|
||||
|
||||
mb_frm_start_fp frm_start;
|
||||
mb_frm_stop_fp frm_stop;
|
||||
mb_get_fp frm_delete;
|
||||
mb_frm_snd_fp frm_send;
|
||||
mb_frm_rcv_fp frm_rcv;
|
||||
mb_get_rx_frm_fp get_rx_frm;
|
||||
mb_get_rx_frm_fp get_tx_frm;
|
||||
mb_get_fp frm_is_bcast;
|
||||
}; //!< Transport methods
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "mb_config.h"
|
||||
#include "mb_common.h"
|
||||
#include "mb_proto.h"
|
||||
#include "mb_func.h"
|
||||
#include "mb_master.h"
|
||||
#include "transport_common.h"
|
||||
#include "port_common.h"
|
||||
#include "ascii_transport.h"
|
||||
#include "rtu_transport.h"
|
||||
#include "tcp_transport.h"
|
||||
|
||||
static const char *TAG = "mb_object.master.stub";
|
||||
|
||||
#if (MB_MASTER_ASCII_ENABLED || MB_MASTER_RTU_ENABLED || MB_MASTER_TCP_ENABLED)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
mb_base_t base;
|
||||
uint16_t pdu_snd_len;
|
||||
uint8_t dst_addr;
|
||||
uint8_t snd_buf[MB_RTU_SER_PDU_SIZE_MAX];
|
||||
} mb_object_t;
|
||||
|
||||
mb_err_enum_t mb_tcp_create(mb_tcp_opts_t *tcp_opts, void **in_out_obj);
|
||||
|
||||
mb_err_enum_t mb_delete(mb_base_t *inst);
|
||||
mb_err_enum_t mb_enable(mb_base_t *inst);
|
||||
mb_err_enum_t mb_disable(mb_base_t *inst);
|
||||
mb_err_enum_t mb_poll(mb_base_t *inst);
|
||||
|
||||
static void mb_set_pdu_send_length(mb_base_t *inst, uint16_t length);
|
||||
static uint16_t mb_get_pdu_send_length(mb_base_t *inst);
|
||||
static void mb_set_dest_addr(mb_base_t *inst, uint8_t dest_addr);
|
||||
static uint8_t mb_get_dest_addr(mb_base_t *inst);
|
||||
static void mb_get_pdu_send_buf(mb_base_t *inst, uint8_t **pbuf);
|
||||
|
||||
#if (MB_MASTER_ASCII_ENABLED || MB_MASTER_RTU_ENABLED)
|
||||
|
||||
typedef struct _port_serial_opts mb_serial_opts_t;
|
||||
|
||||
mb_err_enum_t mb_stub_create(mb_serial_opts_t *ser_opts, void **in_out_obj)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((ser_opts && in_out_obj), MB_EINVAL, TAG, "invalid options for the instance.");
|
||||
mb_err_enum_t ret = MB_ENOERR;
|
||||
mb_object_t *mb_obj = NULL;
|
||||
mb_obj = (mb_object_t *)calloc(1, sizeof(mb_object_t));
|
||||
MB_GOTO_ON_FALSE((mb_obj), MB_EILLSTATE, error, TAG, "no mem for mb master instance.");
|
||||
CRITICAL_SECTION_INIT(mb_obj->base.lock);
|
||||
ESP_LOGW(TAG, "Create fake mb_base object.");
|
||||
mb_obj->base.delete = mb_delete;
|
||||
mb_obj->base.enable = mb_enable;
|
||||
mb_obj->base.disable = mb_disable;
|
||||
mb_obj->base.poll = mb_poll;
|
||||
mb_obj->base.set_dest_addr = mb_set_dest_addr;
|
||||
mb_obj->base.get_dest_addr = mb_get_dest_addr;
|
||||
mb_obj->base.set_send_len = mb_set_pdu_send_length;
|
||||
mb_obj->base.get_send_len = mb_get_pdu_send_length;
|
||||
mb_obj->base.get_send_buf = mb_get_pdu_send_buf;
|
||||
mb_obj->base.descr.parent = *in_out_obj;
|
||||
mb_obj->base.descr.is_master = true;
|
||||
mb_obj->base.descr.obj_name = (char *)TAG;
|
||||
mb_obj->base.descr.inst_index = mb_port_get_inst_counter_inc();
|
||||
*in_out_obj = (void *)mb_obj;
|
||||
return MB_ENOERR;
|
||||
|
||||
error:
|
||||
CRITICAL_SECTION_CLOSE(mb_obj->base.lock);
|
||||
free(mb_obj);
|
||||
mb_port_get_inst_counter_dec();
|
||||
ESP_LOGW(TAG, "Delete fake mb_base object.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
mb_err_enum_t mb_delete(mb_base_t *inst)
|
||||
{
|
||||
mb_object_t *mb_obj = __containerof(inst, mb_object_t, base);
|
||||
CRITICAL_SECTION_CLOSE(mb_obj->base.lock);
|
||||
free(mb_obj);
|
||||
mb_port_get_inst_counter_dec();
|
||||
ESP_LOGW(TAG, "Delete fake mb_base object.");
|
||||
return MB_ENOERR;
|
||||
}
|
||||
|
||||
mb_err_enum_t mb_enable(mb_base_t *inst)
|
||||
{
|
||||
ESP_LOGW(TAG, "Enable fake mb_base object.");
|
||||
mb_err_enum_t status = MB_ENOERR;
|
||||
return status;
|
||||
}
|
||||
|
||||
mb_err_enum_t mb_disable(mb_base_t *inst)
|
||||
{
|
||||
mb_err_enum_t status = MB_ENOERR;
|
||||
ESP_LOGW(TAG, "Disable fake mb_base object.");
|
||||
return status;
|
||||
}
|
||||
|
||||
static void mb_get_pdu_send_buf(mb_base_t *inst, uint8_t **pbuf)
|
||||
{
|
||||
mb_object_t *mb_obj = __containerof(inst, mb_object_t, base);
|
||||
if (pbuf) {
|
||||
*pbuf = mb_obj->snd_buf;
|
||||
}
|
||||
}
|
||||
|
||||
// __attribute__((unused))
|
||||
// static void mb_get_pdu_recv_buf(mb_base_t *inst, uint8_t **pbuf)
|
||||
// {
|
||||
// //mb_object_t *mb_obj = __containerof(inst, mb_object_t, base);
|
||||
// }
|
||||
|
||||
static void mb_set_pdu_send_length(mb_base_t *inst, uint16_t length)
|
||||
{
|
||||
mb_object_t *mb_obj = __containerof(inst, mb_object_t, base);
|
||||
mb_obj->pdu_snd_len = length;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static uint16_t mb_get_pdu_send_length(mb_base_t *inst)
|
||||
{
|
||||
mb_object_t *mb_obj = __containerof(inst, mb_object_t, base);
|
||||
return mb_obj->pdu_snd_len;
|
||||
}
|
||||
|
||||
static void mb_set_dest_addr(mb_base_t *inst, uint8_t dest_addr)
|
||||
{
|
||||
mb_object_t *mb_obj = __containerof(inst, mb_object_t, base);
|
||||
mb_obj->dst_addr = dest_addr;
|
||||
}
|
||||
|
||||
static uint8_t mb_get_dest_addr(mb_base_t *inst)
|
||||
{
|
||||
mb_object_t *mb_obj = __containerof(inst, mb_object_t, base);
|
||||
return mb_obj->dst_addr;
|
||||
}
|
||||
|
||||
mb_err_enum_t mb_poll(mb_base_t *inst)
|
||||
{
|
||||
mb_err_enum_t status = MB_ENOERR;
|
||||
ESP_LOGW(TAG, "Poll function called of fake mb_base object.");
|
||||
//mb_object_t *mb_obj = __containerof(inst, mb_object_t, base);
|
||||
vTaskDelay(1);
|
||||
//mb_port_evt_get_ExpectAnyArgsAndReturn(true);
|
||||
// mb_event_t event = {0};
|
||||
// if (mb_port_evt_get(mb_obj->base.port_obj, &event)) {
|
||||
|
||||
// }
|
||||
return status;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "rtu_transport.h"
|
||||
#include "port_serial_common.h"
|
||||
#include "port_common.h"
|
||||
|
||||
#include "mb_config.h"
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_RTU_EN)
|
||||
|
||||
static const char *TAG = "mb_transp.rtu_master";
|
||||
|
||||
typedef struct
|
||||
{
|
||||
mb_trans_base_t base;
|
||||
mb_port_base_t *port_obj;
|
||||
uint8_t snd_buf[MB_RTU_SER_PDU_SIZE_MAX];
|
||||
uint8_t rcv_buf[MB_RTU_SER_PDU_SIZE_MAX];
|
||||
uint16_t snd_pdu_len;
|
||||
uint8_t *snd_buf_cur;
|
||||
uint16_t snd_buf_cnt;
|
||||
uint16_t rcv_buf_pos;
|
||||
bool frame_is_broadcast;
|
||||
volatile mb_tmr_mode_enum_t cur_tmr_mode;
|
||||
mb_rtu_state_enum_t state;
|
||||
} mbm_rtu_transp_t;
|
||||
|
||||
mb_err_enum_t mbm_rtu_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst);
|
||||
static void mbm_rtu_transp_start(mb_trans_base_t *inst);
|
||||
static void mbm_rtu_transp_stop(mb_trans_base_t *inst);
|
||||
static mb_err_enum_t mbm_rtu_transp_receive(mb_trans_base_t *inst, uint8_t *rcv_addr_buf, uint8_t **frame_ptr_buf, uint16_t *len_buf);
|
||||
static mb_err_enum_t mbm_rtu_transp_send(mb_trans_base_t *inst, uint8_t slv_addr, const uint8_t *frame_ptr, uint16_t len);
|
||||
static bool mbm_rtu_transp_rcv_fsm(mb_trans_base_t *inst);
|
||||
static bool mbm_rtu_transp_snd_fsm(mb_trans_base_t *inst);
|
||||
static bool mbm_rtu_transp_tmr_35_expired(void *inst);
|
||||
static void mbm_rtu_transp_get_snd_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf);
|
||||
static void mbm_rtu_transp_get_rcv_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf);
|
||||
// static uint16_t mbm_rtu_transp_get_snd_len(mb_trans_base_t *inst);
|
||||
static void mbm_rtu_transp_set_snd_len(mb_trans_base_t *inst, uint16_t snd_pdu_len);
|
||||
static bool mbm_rtu_transp_rq_is_bcast(mb_trans_base_t *inst);
|
||||
bool mbm_rtu_transp_delete(mb_trans_base_t *inst);
|
||||
|
||||
mb_err_enum_t mbm_rtu_transp_create(mb_serial_opts_t *ser_opts, void **in_out_inst)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((ser_opts && in_out_inst), MB_EINVAL, TAG, "invalid options for the instance.");
|
||||
mb_err_enum_t ret = MB_ENOERR;
|
||||
mbm_rtu_transp_t *transp = NULL;
|
||||
transp = (mbm_rtu_transp_t *)calloc(1, sizeof(mbm_rtu_transp_t));
|
||||
MB_RETURN_ON_FALSE(transp, MB_EILLSTATE, TAG, "no mem for %s instance.", TAG);
|
||||
CRITICAL_SECTION_INIT(transp->base.lock);
|
||||
CRITICAL_SECTION_LOCK(transp->base.lock);
|
||||
transp->base.frm_rcv = mbm_rtu_transp_receive;
|
||||
transp->base.frm_send = mbm_rtu_transp_send;
|
||||
transp->base.frm_start = mbm_rtu_transp_start;
|
||||
transp->base.frm_stop = mbm_rtu_transp_stop;
|
||||
transp->base.get_rx_frm = mbm_rtu_transp_get_rcv_buf;
|
||||
transp->base.get_tx_frm = mbm_rtu_transp_get_snd_buf;
|
||||
transp->base.frm_delete = mbm_rtu_transp_delete;
|
||||
transp->base.frm_is_bcast = mbm_rtu_transp_rq_is_bcast;
|
||||
// Copy parent object descriptor
|
||||
transp->base.descr = ((mb_port_base_t *)*in_out_inst)->descr;
|
||||
transp->base.descr.obj_name = (char *)TAG;
|
||||
mb_port_base_t *port_obj = (mb_port_base_t *)*in_out_inst;
|
||||
ret = mb_port_ser_create(ser_opts, &port_obj);
|
||||
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EILLSTATE, error, TAG, "serial port creation, err: %d", ret);
|
||||
ret = mb_port_tmr_create(port_obj, MB_RTU_GET_T35_VAL(ser_opts->baudrate));
|
||||
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EILLSTATE, error, TAG, "timer port creation, err: %d", ret);
|
||||
// Override default response time if defined
|
||||
if (ser_opts->response_tout_ms) {
|
||||
mb_port_tmr_set_response_time(port_obj, ser_opts->response_tout_ms);
|
||||
}
|
||||
ret = mb_port_evt_create(port_obj);
|
||||
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EILLSTATE, error, TAG, "event port creation, err: %d", ret);
|
||||
transp->base.port_obj = port_obj;
|
||||
// Set callback function pointer for the timer
|
||||
port_obj->cb.tmr_expired = mbm_rtu_transp_tmr_35_expired;
|
||||
port_obj->cb.tx_empty = NULL;
|
||||
port_obj->cb.byte_rcvd = NULL;
|
||||
port_obj->arg = (void *)transp;
|
||||
transp->port_obj = port_obj; // register the created port object
|
||||
*in_out_inst = &(transp->base);
|
||||
ESP_LOGD(TAG, "created %s object @%p", TAG, transp);
|
||||
CRITICAL_SECTION_UNLOCK(transp->base.lock);
|
||||
return MB_ENOERR;
|
||||
|
||||
error:
|
||||
if (port_obj->timer_obj) {
|
||||
mb_port_tmr_delete(port_obj);
|
||||
}
|
||||
if (port_obj->event_obj) {
|
||||
mb_port_evt_delete(port_obj);
|
||||
}
|
||||
if (port_obj) {
|
||||
mb_port_ser_delete(port_obj);
|
||||
}
|
||||
CRITICAL_SECTION_CLOSE(transp->base.lock);
|
||||
free(transp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool mbm_rtu_transp_delete(mb_trans_base_t *inst)
|
||||
{
|
||||
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
|
||||
mb_port_ser_delete(transp->base.port_obj);
|
||||
mb_port_tmr_delete(transp->base.port_obj);
|
||||
mb_port_evt_delete(transp->base.port_obj);
|
||||
CRITICAL_SECTION_CLOSE(inst->lock);
|
||||
free(transp);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mbm_rtu_transp_start(mb_trans_base_t *inst)
|
||||
{
|
||||
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
|
||||
transp->state = MB_RTU_STATE_INIT;
|
||||
CRITICAL_SECTION(inst->lock) {
|
||||
mb_port_ser_enable(inst->port_obj);
|
||||
mb_port_tmr_enable(inst->port_obj);
|
||||
};
|
||||
/* No special startup required for RTU. */
|
||||
(void)mb_port_evt_post(transp->base.port_obj, EVENT(EV_READY));
|
||||
}
|
||||
|
||||
static void mbm_rtu_transp_stop(mb_trans_base_t *inst)
|
||||
{
|
||||
CRITICAL_SECTION(inst->lock) {
|
||||
mb_port_ser_disable(inst->port_obj);
|
||||
mb_port_tmr_disable(inst->port_obj);
|
||||
};
|
||||
}
|
||||
|
||||
static mb_err_enum_t mbm_rtu_transp_receive(mb_trans_base_t *inst, uint8_t *prcv_addr, uint8_t **ppframe_buf, uint16_t *pbuf_len)
|
||||
{
|
||||
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
|
||||
|
||||
if (!pbuf_len || !prcv_addr || !ppframe_buf || !pbuf_len) {
|
||||
return MB_EIO;
|
||||
}
|
||||
|
||||
mb_err_enum_t status = MB_ENOERR;
|
||||
|
||||
uint8_t *pbuf = (uint8_t *)transp->rcv_buf;
|
||||
uint16_t length = *pbuf_len;
|
||||
|
||||
if (mb_port_ser_recv_data(inst->port_obj, &pbuf, &length) == false) {
|
||||
*pbuf_len = 0;
|
||||
return MB_EPORTERR;
|
||||
}
|
||||
|
||||
assert(length < MB_RTU_SER_PDU_SIZE_MAX);
|
||||
assert(pbuf);
|
||||
|
||||
/* Check length and CRC checksum */
|
||||
if ((length >= MB_RTU_SER_PDU_SIZE_MIN)
|
||||
&& (mb_crc16((uint8_t *)pbuf, length) == 0)) {
|
||||
/* Save the address field. All frames are passed to the upper layed
|
||||
* and the decision if a frame is used is done there.
|
||||
*/
|
||||
*prcv_addr = pbuf[MB_SER_PDU_ADDR_OFF];
|
||||
|
||||
/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
|
||||
* size of address field and CRC checksum.
|
||||
*/
|
||||
*pbuf_len = (uint16_t)(length - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC);
|
||||
transp->rcv_buf_pos = length;
|
||||
|
||||
/* Return the start of the Modbus PDU to the caller. */
|
||||
*ppframe_buf = (uint8_t *)&pbuf[MB_SER_PDU_PDU_OFF];
|
||||
} else {
|
||||
status = MB_EIO;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static mb_err_enum_t mbm_rtu_transp_send(mb_trans_base_t *inst, uint8_t slv_addr, const uint8_t *frame_ptr, uint16_t frame_len)
|
||||
{
|
||||
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
|
||||
mb_err_enum_t status = MB_ENOERR;
|
||||
uint16_t crc16 = 0;
|
||||
|
||||
if (slv_addr > MB_MASTER_TOTAL_SLAVE_NUM) {
|
||||
return MB_EINVAL;
|
||||
}
|
||||
|
||||
if (frame_ptr && frame_len) {
|
||||
/* First byte before the Modbus-PDU is the slave address. */
|
||||
transp->snd_buf_cur = (uint8_t *)frame_ptr - 1;
|
||||
transp->snd_buf_cnt = 1;
|
||||
|
||||
/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
|
||||
transp->snd_buf_cur[MB_SER_PDU_ADDR_OFF] = slv_addr;
|
||||
transp->snd_buf_cnt += frame_len;
|
||||
/* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
|
||||
crc16 = mb_crc16((uint8_t *) transp->snd_buf_cur, transp->snd_buf_cnt);
|
||||
transp->snd_buf_cur[transp->snd_buf_cnt++] = (uint8_t)(crc16 & 0xFF);
|
||||
transp->snd_buf_cur[transp->snd_buf_cnt++] = (uint8_t)(crc16 >> 8);
|
||||
|
||||
bool ret = mb_port_ser_send_data(inst->port_obj, (uint8_t *)transp->snd_buf_cur, transp->snd_buf_cnt);
|
||||
if (!ret) {
|
||||
return MB_EPORTERR;
|
||||
}
|
||||
transp->frame_is_broadcast = (slv_addr == MB_ADDRESS_BROADCAST) ? true : false;
|
||||
// If the frame is broadcast, master will enable timer of convert delay,
|
||||
// else master will enable timer of respond timeout. */
|
||||
if (transp->frame_is_broadcast) {
|
||||
mb_port_tmr_convert_delay_enable(transp->base.port_obj);
|
||||
} else {
|
||||
mb_port_tmr_respond_timeout_enable(transp->base.port_obj);
|
||||
}
|
||||
|
||||
} else {
|
||||
status = MB_EIO;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static bool mbm_rtu_transp_rcv_fsm(mb_trans_base_t *inst)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static bool mbm_rtu_transp_snd_fsm(mb_trans_base_t *inst)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool mbm_rtu_transp_tmr_35_expired(void *inst)
|
||||
{
|
||||
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
|
||||
|
||||
bool need_poll = false;
|
||||
mb_tmr_mode_enum_t timer_mode = mb_port_get_cur_tmr_mode(transp->base.port_obj);
|
||||
|
||||
mb_port_tmr_disable(transp->base.port_obj);
|
||||
|
||||
switch(timer_mode) {
|
||||
case MB_TMODE_T35:
|
||||
//need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_READY));
|
||||
//ESP_EARLY_LOGD(TAG, "%p:EV_READY", transp->base.descr.parent);
|
||||
break;
|
||||
|
||||
case MB_TMODE_RESPOND_TIMEOUT:
|
||||
mb_port_evt_set_err_type(transp->base.port_obj, EV_ERROR_RESPOND_TIMEOUT);
|
||||
need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_ERROR_PROCESS));
|
||||
ESP_EARLY_LOGW(TAG, "%p:EV_ERROR_RESPOND_TIMEOUT", transp->base.descr.parent);
|
||||
break;
|
||||
|
||||
case MB_TMODE_CONVERT_DELAY:
|
||||
/* If timer mode is convert delay, the master event then turns EV_MASTER_EXECUTE status. */
|
||||
need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_EXECUTE));
|
||||
ESP_EARLY_LOGD(TAG, "%p:MB_TMODE_CONVERT_DELAY", transp->base.descr.parent);
|
||||
break;
|
||||
|
||||
default:
|
||||
need_poll = mb_port_evt_post(transp->base.port_obj, EVENT(EV_READY));
|
||||
break;
|
||||
}
|
||||
|
||||
return need_poll;
|
||||
}
|
||||
|
||||
static void mbm_rtu_transp_get_rcv_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf)
|
||||
{
|
||||
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
|
||||
CRITICAL_SECTION(inst->lock) {
|
||||
*frame_ptr_buf = (uint8_t *)&transp->rcv_buf[MB_PDU_FUNC_OFF];
|
||||
}
|
||||
}
|
||||
|
||||
static void mbm_rtu_transp_get_snd_buf(mb_trans_base_t *inst, uint8_t **frame_ptr_buf)
|
||||
{
|
||||
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
|
||||
CRITICAL_SECTION(inst->lock) {
|
||||
*frame_ptr_buf = (uint8_t *)&transp->snd_buf[MB_RTU_SER_PDU_PDU_OFF];
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static void mbm_rtu_transp_set_snd_len(mb_trans_base_t *inst, uint16_t snd_pdu_len)
|
||||
{
|
||||
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
|
||||
CRITICAL_SECTION(inst->lock) {
|
||||
transp->snd_buf_cnt = snd_pdu_len;
|
||||
}
|
||||
}
|
||||
|
||||
static bool mbm_rtu_transp_rq_is_bcast(mb_trans_base_t *inst)
|
||||
{
|
||||
mbm_rtu_transp_t *transp = __containerof(inst, mbm_rtu_transp_t, base);
|
||||
return transp->frame_is_broadcast;
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user