forked from espressif/esp-modbus
add test applications and unit tests
This commit is contained in:
@@ -191,12 +191,12 @@ void app_main(void)
|
||||
(unsigned)reg_info.size);
|
||||
if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0)
|
||||
{
|
||||
(void)mbc_slave_lock(slave_handle);
|
||||
(void)mbc_slave_lock(mbc_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;
|
||||
}
|
||||
(void)mbc_slave_unlock(slave_handle);
|
||||
(void)mbc_slave_unlock(mbc_slave_handle);
|
||||
}
|
||||
} else if (reg_info.type & MB_EVENT_INPUT_REG_RD) {
|
||||
ESP_LOGI(TAG, "INPUT READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
|
||||
|
14
test_apps/.build-test-rules.yml
Normal file
14
test_apps/.build-test-rules.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
physical_tests:
|
||||
disable:
|
||||
- if: CONFIG_NAME != "serial" or IDF_TARGET != "esp32"
|
||||
# disable_test:
|
||||
# - if: IDF_TARGET != "esp32"
|
||||
# reason: only manual test is performed for other targets
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
1
test_apps/adapter_tests/.gitignore
vendored
Normal file
1
test_apps/adapter_tests/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/__pycache__/
|
8
test_apps/adapter_tests/CMakeLists.txt
Normal file
8
test_apps/adapter_tests/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# 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")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(test_comm_adapter)
|
4
test_apps/adapter_tests/README.md
Normal file
4
test_apps/adapter_tests/README.md
Normal file
@@ -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.
|
10
test_apps/adapter_tests/main/CMakeLists.txt
Normal file
10
test_apps/adapter_tests/main/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_modbus_adapter_comm.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 test_common unity test_utils
|
||||
) #
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u mb_test_include_impl")
|
45
test_apps/adapter_tests/main/Kconfig.projbuild
Normal file
45
test_apps/adapter_tests/main/Kconfig.projbuild
Normal file
@@ -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"
|
||||
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
|
4
test_apps/adapter_tests/main/component.mk
Normal file
4
test_apps/adapter_tests/main/component.mk
Normal file
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
6
test_apps/adapter_tests/main/idf_component.yml
Normal file
6
test_apps/adapter_tests/main/idf_component.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
dependencies:
|
||||
idf: ">=4.3"
|
||||
espressif/esp-modbus:
|
||||
version: "^2.0"
|
||||
override_path: "../../../"
|
||||
|
19
test_apps/adapter_tests/main/test_app_main.c
Normal file
19
test_apps/adapter_tests/main/test_app_main.c
Normal file
@@ -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(modbus_adapter_comm_basic);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
UNITY_MAIN_FUNC(run_all_tests);
|
||||
}
|
249
test_apps/adapter_tests/main/test_modbus_adapter_comm.c
Normal file
249
test_apps/adapter_tests/main/test_modbus_adapter_comm.c
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include "unity_fixture.h"
|
||||
|
||||
#include "test_utils.h"
|
||||
|
||||
#if __has_include("unity_test_utils.h")
|
||||
#define UNITY_TEST_UTILS_INCLUDED
|
||||
// unity test utils are used
|
||||
#include "unity_test_utils.h"
|
||||
#include "unity_test_utils_memory.h"
|
||||
#else
|
||||
// Unit_test_app utils from test_utils ("test_utils.h"), v4.4
|
||||
#define unity_utils_task_delete test_utils_task_delete
|
||||
#endif
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "test_common.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_LEAK_WARN 32
|
||||
#define TEST_LEAK_CRITICAL 64
|
||||
#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;
|
||||
|
||||
// Example Data (Object) Dictionary for Modbus parameters
|
||||
static const mb_parameter_descriptor_t descriptors[] = {
|
||||
{CID_DEV_REG0, STR("MB_hold_reg-0"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 1,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
{CID_DEV_REG1, STR("MB_hold_reg-1"), 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_REG2, STR("MB_hold_reg-2"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 1,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
{CID_DEV_REG3, STR("MB_hold_reg-3"), STR("Data"), MB_DEVICE_ADDR2, MB_PARAM_HOLDING, 3, 1,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
{CID_DEV_REG_COUNT, 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]));
|
||||
|
||||
TaskHandle_t task_handles[TEST_TASKS_NUM] = {};
|
||||
|
||||
TEST_GROUP(modbus_adapter_comm_basic);
|
||||
|
||||
TEST_SETUP(modbus_adapter_comm_basic)
|
||||
{
|
||||
test_common_start();
|
||||
}
|
||||
|
||||
TEST_TEAR_DOWN(modbus_adapter_comm_basic)
|
||||
{
|
||||
uint32_t test_task = 0;
|
||||
uint32_t test_task_count = 0;
|
||||
int i = 0;
|
||||
|
||||
// Trigger start of test task intentionally
|
||||
for (i = 0; i < TEST_TASKS_NUM; i++) {
|
||||
test_common_task_notify_start(task_handles[i], 1);
|
||||
}
|
||||
|
||||
for (i = 0; (i < TEST_TASKS_NUM); i++) {
|
||||
test_task = test_common_wait_done(pdMS_TO_TICKS(TEST_TASK_TIMEOUT_MS));
|
||||
if (test_task) {
|
||||
unity_utils_task_delete((TaskHandle_t)test_task);
|
||||
ESP_LOGI(TAG, "Task %" PRIx32 " is complited.", test_task);
|
||||
test_task_count++;
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(5); // Let the test tasks with lower priority to suspend or delete itself from test_common
|
||||
|
||||
TEST_ASSERT_EQUAL(TEST_TASKS_NUM, test_task_count);
|
||||
ESP_LOGI(TAG, "Test done successfully.");
|
||||
|
||||
test_common_stop();
|
||||
}
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_RTU_EN || CONFIG_FMB_COMM_MODE_ASCII_EN)
|
||||
|
||||
TEST(modbus_adapter_comm_basic, test_modbus_adapter_rtu)
|
||||
{
|
||||
mb_communication_info_t slave_config1 = {
|
||||
.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
|
||||
};
|
||||
|
||||
task_handles[0] = test_slave_serial_create(&slave_config1);
|
||||
|
||||
mb_communication_info_t slave_config2 = {
|
||||
.ser_opts.port = TEST_SER_PORT_NUM,
|
||||
.ser_opts.mode = MB_RTU,
|
||||
.ser_opts.uid = MB_DEVICE_ADDR2,
|
||||
.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
|
||||
};
|
||||
|
||||
task_handles[1] = test_slave_serial_create(&slave_config2);
|
||||
|
||||
// Initialize and start Modbus controller
|
||||
mb_communication_info_t master_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
|
||||
};
|
||||
|
||||
task_handles[2] = test_master_serial_create(&master_config, &descriptors[0], num_descriptors);
|
||||
}
|
||||
|
||||
TEST(modbus_adapter_comm_basic, test_modbus_adapter_ascii)
|
||||
{
|
||||
mb_communication_info_t slave_config1 = {
|
||||
.ser_opts.port = TEST_SER_PORT_NUM,
|
||||
.ser_opts.mode = MB_ASCII,
|
||||
.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
|
||||
};
|
||||
|
||||
task_handles[0] = test_slave_serial_create(&slave_config1);
|
||||
|
||||
mb_communication_info_t slave_config2 = {
|
||||
.ser_opts.port = TEST_SER_PORT_NUM,
|
||||
.ser_opts.mode = MB_ASCII,
|
||||
.ser_opts.uid = MB_DEVICE_ADDR2,
|
||||
.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
|
||||
};
|
||||
|
||||
task_handles[1] = test_slave_serial_create(&slave_config2);
|
||||
|
||||
mb_communication_info_t master_config = {
|
||||
.ser_opts.port = TEST_SER_PORT_NUM,
|
||||
.ser_opts.mode = MB_ASCII,
|
||||
.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
|
||||
};
|
||||
|
||||
task_handles[2] = test_master_serial_create(&master_config, &descriptors[0], num_descriptors);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
|
||||
|
||||
const char *slave_tcp_addr_table[] = {
|
||||
"01;mb_slave_tcp_01;1502", // Corresponds to characteristic MB_DEVICE_ADDR1 "mb_slave_tcp_01"
|
||||
"200;mb_slave_tcp_c8;1502", // Corresponds to characteristic MB_DEVICE_ADDR2 "mb_slave_tcp_C8"
|
||||
NULL // End of table condition (must be included)
|
||||
};
|
||||
|
||||
TEST(modbus_adapter_comm_basic, test_modbus_adapter_tcp)
|
||||
{
|
||||
mb_communication_info_t tcp_slave_cfg_1 = {
|
||||
.tcp_opts.port = TEST_TCP_PORT_NUM,
|
||||
.tcp_opts.mode = MB_TCP,
|
||||
.tcp_opts.addr_type = MB_IPV4,
|
||||
.tcp_opts.ip_addr_table = NULL,
|
||||
.tcp_opts.uid = MB_DEVICE_ADDR1,
|
||||
.tcp_opts.start_disconnected = true,
|
||||
.tcp_opts.response_tout_ms = 1,
|
||||
.tcp_opts.test_tout_us = TEST_SLAVE_SEND_TOUT_US
|
||||
};
|
||||
|
||||
task_handles[0] = test_slave_tcp_create(&tcp_slave_cfg_1);
|
||||
|
||||
mb_communication_info_t tcp_slave_cfg_2 = {
|
||||
.tcp_opts.port = TEST_TCP_PORT_NUM,
|
||||
.tcp_opts.mode = MB_TCP,
|
||||
.tcp_opts.addr_type = MB_IPV4,
|
||||
.tcp_opts.ip_addr_table = NULL,
|
||||
.tcp_opts.uid = MB_DEVICE_ADDR2,
|
||||
.tcp_opts.start_disconnected = true,
|
||||
.tcp_opts.response_tout_ms = TEST_MASTER_RESPOND_TOUT_MS,
|
||||
.tcp_opts.test_tout_us = TEST_SLAVE_SEND_TOUT_US
|
||||
};
|
||||
|
||||
task_handles[1] = test_slave_tcp_create(&tcp_slave_cfg_2);
|
||||
|
||||
// Initialize and start Modbus controller
|
||||
mb_communication_info_t tcp_master_cfg_1 = {
|
||||
.tcp_opts.port = TEST_TCP_PORT_NUM,
|
||||
.tcp_opts.mode = MB_TCP,
|
||||
.tcp_opts.addr_type = MB_IPV4,
|
||||
.tcp_opts.ip_addr_table = (void *)slave_tcp_addr_table,
|
||||
.tcp_opts.uid = 0,
|
||||
.tcp_opts.start_disconnected = false,
|
||||
.tcp_opts.response_tout_ms = TEST_MASTER_RESPOND_TOUT_MS,
|
||||
.tcp_opts.test_tout_us = TEST_MASTER_SEND_TOUT_US
|
||||
};
|
||||
|
||||
task_handles[2] = test_master_tcp_create(&tcp_master_cfg_1, &descriptors[0], num_descriptors);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_GROUP_RUNNER(modbus_adapter_comm_basic)
|
||||
{
|
||||
#if (CONFIG_FMB_COMM_MODE_RTU_EN && CONFIG_FMB_COMM_MODE_ASCII_EN)
|
||||
RUN_TEST_CASE(modbus_adapter_comm_basic, test_modbus_adapter_rtu);
|
||||
RUN_TEST_CASE(modbus_adapter_comm_basic, test_modbus_adapter_ascii);
|
||||
#endif
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
|
||||
RUN_TEST_CASE(modbus_adapter_comm_basic, test_modbus_adapter_tcp);
|
||||
#endif
|
||||
}
|
17
test_apps/adapter_tests/pytest_mb_comm_adapter.py
Normal file
17
test_apps/adapter_tests/pytest_mb_comm_adapter.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# 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.param('tcp', 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_comm_adapter(dut: Dut) -> None:
|
||||
dut.expect_unity_test_output()
|
23
test_apps/adapter_tests/sdkconfig.ci.serial
Normal file
23
test_apps/adapter_tests/sdkconfig.ci.serial
Normal file
@@ -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
|
||||
|
23
test_apps/adapter_tests/sdkconfig.ci.tcp
Normal file
23
test_apps/adapter_tests/sdkconfig.ci.tcp
Normal file
@@ -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=n
|
||||
CONFIG_FMB_COMM_MODE_ASCII_EN=n
|
||||
CONFIG_FMB_COMM_MODE_TCP_EN=y
|
||||
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
|
||||
|
21
test_apps/adapter_tests/sdkconfig.defaults
Normal file
21
test_apps/adapter_tests/sdkconfig.defaults
Normal file
@@ -0,0 +1,21 @@
|
||||
# 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_APP_BUILD_USE_FLASH_SECTIONS=n
|
||||
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=y
|
||||
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_MASTER_TASK_PRIO=4
|
||||
CONFIG_MB_TEST_SLAVE_TASK_PRIO=4
|
||||
CONFIG_MB_TEST_COMM_CYCLE_COUNTER=10
|
115
test_apps/conftest.py
Normal file
115
test_apps/conftest.py
Normal file
@@ -0,0 +1,115 @@
|
||||
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# pylint: disable=W0621 # redefined-outer-name
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Callable, Dict, Match, Optional, TextIO, Tuple
|
||||
|
||||
import pexpect
|
||||
import pytest
|
||||
from pytest_embedded_idf import CaseTester
|
||||
from _pytest.config import Config
|
||||
from _pytest.fixtures import FixtureRequest
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from pytest_embedded.plugin import multi_dut_argument, multi_dut_fixture
|
||||
from pytest_embedded_idf.app import IdfApp
|
||||
from pytest_embedded_idf.dut import IdfDut
|
||||
from pytest_embedded_idf.serial import IdfSerial
|
||||
|
||||
DEFAULT_SDKCONFIG = 'default'
|
||||
|
||||
|
||||
############
|
||||
# Fixtures #
|
||||
############
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def case_tester(dut: IdfDut, **kwargs): # type: ignore
|
||||
yield CaseTester(dut, **kwargs)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session', autouse=True)
|
||||
def session_tempdir() -> str:
|
||||
|
||||
_tmpdir = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'pytest_embedded_log',
|
||||
datetime.now().strftime('%Y-%m-%d_%H-%M-%S'),
|
||||
)
|
||||
os.makedirs(_tmpdir, exist_ok=True)
|
||||
return _tmpdir
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@multi_dut_fixture
|
||||
def junit_properties(
|
||||
test_case_name: str, record_xml_attribute: Callable[[str, object], None]
|
||||
) -> None:
|
||||
"""
|
||||
This fixture is autoused and will modify the junit report test case name to <target>.<config>.<case_name>
|
||||
"""
|
||||
record_xml_attribute('name', test_case_name)
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch:
|
||||
mp = MonkeyPatch()
|
||||
request.addfinalizer(mp.undo)
|
||||
return mp
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@multi_dut_argument
|
||||
def config(request: FixtureRequest) -> str:
|
||||
return getattr(request, 'param', None) or DEFAULT_SDKCONFIG
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@multi_dut_fixture
|
||||
def build_dir(app_path: str, target: Optional[str], config: Optional[str]) -> str:
|
||||
"""
|
||||
Check local build dir with the following priority:
|
||||
|
||||
1. build_<target>_<config>
|
||||
2. build_<target>
|
||||
3. build_<config>
|
||||
4. build
|
||||
|
||||
Args:
|
||||
app_path: app path
|
||||
target: target
|
||||
config: config
|
||||
|
||||
Returns:
|
||||
valid build directory
|
||||
"""
|
||||
check_dirs = []
|
||||
if target is not None and config is not None:
|
||||
check_dirs.append(f'build_{target}_{config}')
|
||||
if target is not None:
|
||||
check_dirs.append(f'build_{target}')
|
||||
if config is not None:
|
||||
check_dirs.append(f'build_{config}')
|
||||
check_dirs.append('build')
|
||||
|
||||
for check_dir in check_dirs:
|
||||
binary_path = os.path.join(app_path, check_dir)
|
||||
if os.path.isdir(binary_path):
|
||||
logging.info(f'find valid binary path: {binary_path}')
|
||||
return check_dir
|
||||
|
||||
logging.warning(
|
||||
'checking binary path: %s... missing... try another place', binary_path
|
||||
)
|
||||
|
||||
recommend_place = check_dirs[0]
|
||||
logging.error(
|
||||
f'no build dir valid. Please build the binary via "idf.py -B {recommend_place} build" and run pytest again'
|
||||
)
|
||||
sys.exit(1)
|
2
test_apps/physical_tests/.gitignore
vendored
Normal file
2
test_apps/physical_tests/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/pytest_embedded_log/
|
||||
/__pycache__/
|
8
test_apps/physical_tests/CMakeLists.txt
Normal file
8
test_apps/physical_tests/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# 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")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(test_modbus_comm_multi_dev)
|
4
test_apps/physical_tests/README.md
Normal file
4
test_apps/physical_tests/README.md
Normal file
@@ -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.
|
12
test_apps/physical_tests/main/CMakeLists.txt
Normal file
12
test_apps/physical_tests/main/CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_modbus_rs485_comm_master_slave.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 test_utils test_common 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")
|
||||
|
45
test_apps/physical_tests/main/Kconfig.projbuild
Normal file
45
test_apps/physical_tests/main/Kconfig.projbuild
Normal file
@@ -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 test communication cycle counter"
|
||||
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
|
4
test_apps/physical_tests/main/component.mk
Normal file
4
test_apps/physical_tests/main/component.mk
Normal file
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
6
test_apps/physical_tests/main/idf_component.yml
Normal file
6
test_apps/physical_tests/main/idf_component.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
dependencies:
|
||||
idf: ">=4.3"
|
||||
espressif/esp-modbus:
|
||||
version: "^2.0"
|
||||
override_path: "../../../"
|
||||
|
24
test_apps/physical_tests/main/test_app_main.c
Normal file
24
test_apps/physical_tests/main/test_app_main.c
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "test_common.h"
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
test_common_start();
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
test_common_stop();
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
printf("Modbus RS485 multi-device test cases/n");
|
||||
unity_run_menu();
|
||||
}
|
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include "unity.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "test_common.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#if __has_include("unity_test_utils.h")
|
||||
// unity test utils are used
|
||||
#include "unity_test_utils.h"
|
||||
#else
|
||||
// Unit_test_app utils from test_utils ("test_utils.h"), v4.4
|
||||
#define unity_utils_task_delete test_utils_task_delete
|
||||
#endif
|
||||
|
||||
#define TEST_SER_PORT_NUM (1)
|
||||
#define TEST_TASK_TIMEOUT_MS (30000)
|
||||
#define TEST_SEND_TOUT_US (30000)
|
||||
#define TEST_RESP_TOUT_MS (1000)
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#define TEST_SER_PIN_RX (22)
|
||||
#define TEST_SER_PIN_TX (23)
|
||||
// RTS for RS485 Half-Duplex Mode manages DE/~RE
|
||||
#define TEST_SER_PIN_RTS (18)
|
||||
#define TEST_BAUD_RATE (115200)
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||
#define TEST_SER_PIN_RX (4)
|
||||
#define TEST_SER_PIN_TX (5)
|
||||
#define TEST_SER_PIN_RTS (10)
|
||||
#define TEST_BAUD_RATE (115200)
|
||||
#endif
|
||||
|
||||
#define TEST_MASTER_RESPOND_TOUT_MS CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND
|
||||
|
||||
// The workaround to statically link the whole test library
|
||||
__attribute__((unused)) bool mb_test_include_impl = 1;
|
||||
|
||||
#define TAG "MODBUS_SERIAL_COMM_TEST"
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_RTU_EN || CONFIG_FMB_COMM_MODE_ASCII_EN)
|
||||
|
||||
// Example Data (Object) Dictionary for Modbus parameters
|
||||
static const mb_parameter_descriptor_t descriptors[] = {
|
||||
{CID_DEV_REG0, STR("MB_hold_reg-0"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 1,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
{CID_DEV_REG1, STR("MB_hold_reg-1"), 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_REG2, STR("MB_hold_reg-2"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 1,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
{CID_DEV_REG3, STR("MB_hold_reg-3"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 3, 1,
|
||||
0, PARAM_TYPE_U16, 2, OPTS(0, 0, 0), PAR_PERMS_READ_WRITE_TRIGGER},
|
||||
{CID_DEV_REG_COUNT, 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}
|
||||
};
|
||||
|
||||
// The number of parameters in the table
|
||||
const uint16_t num_descriptors = (sizeof(descriptors) / sizeof(descriptors[0]));
|
||||
|
||||
static void test_task_start_wait_done(TaskHandle_t task_handle)
|
||||
{
|
||||
uint32_t test_task = 0;
|
||||
|
||||
// Start test sequence intentionally in the task
|
||||
test_common_task_notify_start(task_handle, 1);
|
||||
|
||||
for (int i = 0; (i < 2); i++) {
|
||||
test_task = test_common_wait_done(pdMS_TO_TICKS(TEST_TASK_TIMEOUT_MS));
|
||||
if (test_task == (uint32_t)task_handle) {
|
||||
unity_utils_task_delete((TaskHandle_t)test_task);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(5); // A small delay to let the test lower priority task delete itself
|
||||
if (test_task != (uint32_t)task_handle) {
|
||||
ESP_LOGI(TAG, "Could not complete task 0x%" PRIx32" after %d ms, force kill the task.",
|
||||
(uint32_t)task_handle, TEST_TASK_TIMEOUT_MS);
|
||||
unity_utils_task_delete((TaskHandle_t)task_handle);
|
||||
}
|
||||
|
||||
TEST_ASSERT_EQUAL(test_task, (uint32_t)task_handle);
|
||||
ESP_LOGI(TAG, "Test task 0x%" PRIx32 ", done successfully.", (uint32_t)task_handle);
|
||||
}
|
||||
|
||||
static void test_modbus_rs485_rtu_slave(void)
|
||||
{
|
||||
mb_communication_info_t slave_config1 = {
|
||||
.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 = TEST_BAUD_RATE,
|
||||
.ser_opts.parity = UART_PARITY_DISABLE,
|
||||
.ser_opts.response_tout_ms = TEST_MASTER_RESPOND_TOUT_MS,
|
||||
.ser_opts.test_tout_us = TEST_SEND_TOUT_US
|
||||
};
|
||||
|
||||
TEST_ESP_OK(uart_set_pin(slave_config1.ser_opts.port, TEST_SER_PIN_TX,
|
||||
TEST_SER_PIN_RX, TEST_SER_PIN_RTS, UART_PIN_NO_CHANGE));
|
||||
|
||||
TaskHandle_t slave_task_handle = test_slave_serial_create(&slave_config1);
|
||||
|
||||
// Set driver mode to Half Duplex
|
||||
TEST_ESP_OK(uart_set_mode(slave_config1.ser_opts.port, UART_MODE_RS485_HALF_DUPLEX));
|
||||
|
||||
ESP_LOGI(TAG, "Slave RTU is started. (%s).", __func__);
|
||||
|
||||
unity_send_signal("Slave_ready");
|
||||
unity_wait_for_signal("Master_started");
|
||||
|
||||
test_task_start_wait_done(slave_task_handle);
|
||||
|
||||
}
|
||||
|
||||
static void test_modbus_rs485_rtu_master(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Master RTU is started (%s).", __func__);
|
||||
unity_wait_for_signal("Slave_ready");
|
||||
unity_send_signal("Master_started");
|
||||
|
||||
// Initialize and start Modbus controller
|
||||
mb_communication_info_t master_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_1,
|
||||
.ser_opts.baudrate = TEST_BAUD_RATE,
|
||||
.ser_opts.parity = UART_PARITY_DISABLE,
|
||||
.ser_opts.response_tout_ms = TEST_MASTER_RESPOND_TOUT_MS,
|
||||
.ser_opts.test_tout_us = TEST_SEND_TOUT_US
|
||||
};
|
||||
|
||||
TaskHandle_t master_task_handle = test_master_serial_create(&master_config, &descriptors[0], num_descriptors);
|
||||
|
||||
// Set driver mode to Half Duplex
|
||||
TEST_ESP_OK(uart_set_mode(master_config.ser_opts.port, UART_MODE_RS485_HALF_DUPLEX));
|
||||
TEST_ESP_OK(uart_set_pin(master_config.ser_opts.port, TEST_SER_PIN_TX,
|
||||
TEST_SER_PIN_RX, TEST_SER_PIN_RTS, UART_PIN_NO_CHANGE));
|
||||
|
||||
test_task_start_wait_done(master_task_handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Modbus RS485 RTU multi device test case
|
||||
*/
|
||||
TEST_CASE_MULTIPLE_DEVICES("Modbus RS485 RTU multi device master - slave case.", "[modbus][test_env=multi_dut_modbus_serial]", test_modbus_rs485_rtu_slave, test_modbus_rs485_rtu_master);
|
||||
|
||||
static void test_modbus_rs485_ascii_slave(void)
|
||||
{
|
||||
mb_communication_info_t slave_config1 = {
|
||||
.ser_opts.port = TEST_SER_PORT_NUM,
|
||||
.ser_opts.mode = MB_ASCII,
|
||||
.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 = TEST_BAUD_RATE,
|
||||
.ser_opts.parity = UART_PARITY_DISABLE,
|
||||
.ser_opts.response_tout_ms = TEST_MASTER_RESPOND_TOUT_MS,
|
||||
.ser_opts.test_tout_us = TEST_SEND_TOUT_US
|
||||
};
|
||||
|
||||
TEST_ESP_OK(uart_set_pin(slave_config1.ser_opts.port, TEST_SER_PIN_TX,
|
||||
TEST_SER_PIN_RX, TEST_SER_PIN_RTS, UART_PIN_NO_CHANGE));
|
||||
|
||||
TaskHandle_t slave_task_handle = test_slave_serial_create(&slave_config1);
|
||||
|
||||
// Set driver mode to Half Duplex
|
||||
TEST_ESP_OK(uart_set_mode(slave_config1.ser_opts.port, UART_MODE_RS485_HALF_DUPLEX));
|
||||
|
||||
ESP_LOGI(TAG, "Slave ASCII is started. (%s).", __func__);
|
||||
|
||||
unity_send_signal("Slave_ready");
|
||||
unity_wait_for_signal("Master_started");
|
||||
|
||||
test_task_start_wait_done(slave_task_handle);
|
||||
|
||||
}
|
||||
|
||||
static void test_modbus_rs485_ascii_master(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Master ASCII is started (%s).", __func__);
|
||||
unity_wait_for_signal("Slave_ready");
|
||||
|
||||
// Initialize and start Modbus controller
|
||||
mb_communication_info_t master_config = {
|
||||
.ser_opts.port = TEST_SER_PORT_NUM,
|
||||
.ser_opts.mode = MB_ASCII,
|
||||
.ser_opts.data_bits = UART_DATA_8_BITS,
|
||||
.ser_opts.stop_bits = UART_STOP_BITS_1,
|
||||
.ser_opts.baudrate = TEST_BAUD_RATE,
|
||||
.ser_opts.parity = UART_PARITY_DISABLE,
|
||||
.ser_opts.response_tout_ms = TEST_MASTER_RESPOND_TOUT_MS,
|
||||
.ser_opts.test_tout_us = TEST_SEND_TOUT_US
|
||||
};
|
||||
|
||||
TaskHandle_t master_task_handle = test_master_serial_create(&master_config, &descriptors[0], num_descriptors);
|
||||
|
||||
// Set driver mode to Half Duplex
|
||||
TEST_ESP_OK(uart_set_mode(master_config.ser_opts.port, UART_MODE_RS485_HALF_DUPLEX));
|
||||
TEST_ESP_OK(uart_set_pin(master_config.ser_opts.port, TEST_SER_PIN_TX,
|
||||
TEST_SER_PIN_RX, TEST_SER_PIN_RTS, UART_PIN_NO_CHANGE));
|
||||
|
||||
unity_send_signal("Master_started");
|
||||
|
||||
test_task_start_wait_done(master_task_handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Modbus RS485 ASCII multi device test case
|
||||
*/
|
||||
TEST_CASE_MULTIPLE_DEVICES("Modbus RS485 ASCII multi device master - slave case.", "[modbus][test_env=multi_dut_modbus_serial]", test_modbus_rs485_ascii_slave, test_modbus_rs485_ascii_master);
|
||||
|
||||
|
||||
#endif
|
24
test_apps/physical_tests/pytest_mb_comm_multi_dev.py
Normal file
24
test_apps/physical_tests/pytest_mb_comm_multi_dev.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded_idf import CaseTester
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.multi_dut_modbus_serial
|
||||
@pytest.mark.parametrize('count, config', [(2, 'serial')], indirect=True)
|
||||
def test_modbus_comm_multi_dev_serial(case_tester) -> None: # type: ignore
|
||||
#case_tester.run_all_multi_dev_cases(reset=True)
|
||||
for case in case_tester.test_menu:
|
||||
if case.attributes.get('test_env', 'multi_dut_modbus_serial') == 'multi_dut_modbus_serial':
|
||||
print(case.name)
|
||||
case_tester.run_multi_dev_case(case=case, reset=True)
|
||||
|
||||
|
||||
# @pytest.mark.esp32
|
||||
# @pytest.mark.multi_dut_modbus_tcp
|
||||
# #@pytest.mark.parametrize('count, config', [(2, 'tcp')], indirect=True)
|
||||
# def test_modbus_comm_multi_dev_tcp(case_tester) -> None: # type: ignore
|
||||
# for case in case_tester.test_menu:
|
||||
# if case.attributes.get('test_env', 'multi_dut_modbus_tcp') == 'multi_dut_modbus_tcp':
|
||||
# case_tester.run_multi_dev_case(case=case, reset=True)
|
23
test_apps/physical_tests/sdkconfig.ci.serial
Normal file
23
test_apps/physical_tests/sdkconfig.ci.serial
Normal file
@@ -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=y
|
||||
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_TEST_SLAVE_TASK_PRIO=5
|
||||
CONFIG_MB_TEST_MASTER_TASK_PRIO=5
|
||||
CONFIG_MB_TEST_COMM_CYCLE_COUNTER=10
|
||||
CONFIG_MB_PORT_ADAPTER_EN=n
|
||||
CONFIG_MB_TEST_LEAK_CRITICAL_LEVEL=128
|
||||
CONFIG_MB_TEST_LEAK_WARN_LEVEL=128
|
||||
|
20
test_apps/physical_tests/sdkconfig.defaults
Normal file
20
test_apps/physical_tests/sdkconfig.defaults
Normal file
@@ -0,0 +1,20 @@
|
||||
# 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=n
|
||||
CONFIG_MB_TEST_MASTER_TASK_PRIO=5
|
||||
CONFIG_MB_TEST_MASTER_TASK_PRIO=5
|
||||
CONFIG_MB_TEST_COMM_CYCLE_COUNTER=10
|
9
test_apps/test_common/CMakeLists.txt
Normal file
9
test_apps/test_common/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
idf_component_register(SRCS "test_common.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES unity)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components")
|
||||
|
||||
add_subdirectory(mb_utest_lib)
|
||||
target_link_libraries(${COMPONENT_LIB} PUBLIC mb_ut_lib)
|
3
test_apps/test_common/README.md
Normal file
3
test_apps/test_common/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Test common
|
||||
|
||||
This directory contains component that is common for Modbus master and slave tests.
|
68
test_apps/test_common/include/test_common.h
Normal file
68
test_apps/test_common/include/test_common.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
|
||||
#include "mbcontroller.h" // for common Modbus defines
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define STR(fieldname) ((const char *)(fieldname))
|
||||
#define OPTS(min_val, max_val, step_val) \
|
||||
{ \
|
||||
.opt1 = min_val, .opt2 = max_val, .opt3 = step_val \
|
||||
}
|
||||
|
||||
// Enumeration of modbus slave addresses accessed by master device
|
||||
enum
|
||||
{
|
||||
MB_DEVICE_ADDR1 = 1,
|
||||
MB_DEVICE_ADDR2 = 200
|
||||
};
|
||||
|
||||
// Enumeration of all supported CIDs for device (used in parameter definition table)
|
||||
enum {
|
||||
CID_DEV_REG0 = 0,
|
||||
CID_DEV_REG1,
|
||||
CID_DEV_REG2,
|
||||
CID_DEV_REG3,
|
||||
CID_DEV_REG_COUNT
|
||||
};
|
||||
|
||||
// Enumeration of predefined test values
|
||||
enum {
|
||||
TEST_REG_VAL1 = 0x1111,
|
||||
TEST_REG_VAL2 = 0x2222,
|
||||
TEST_REG_VAL3 = 0x3333,
|
||||
TEST_REG_VAL4 = 0x4444
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper test functions for multi instance modbus master - slave test
|
||||
*
|
||||
*/
|
||||
TaskHandle_t test_slave_serial_create(mb_communication_info_t *pconfig);
|
||||
TaskHandle_t test_master_serial_create(mb_communication_info_t *pconfig, const mb_parameter_descriptor_t *pdescr, uint16_t descr_size);
|
||||
TaskHandle_t test_slave_tcp_create(mb_communication_info_t *pconfig);
|
||||
TaskHandle_t test_master_tcp_create(mb_communication_info_t *pconfig, const mb_parameter_descriptor_t *pdescr, uint16_t descr_size);
|
||||
TaskHandle_t test_slave_start_busy_task();
|
||||
// slave setup register area helper
|
||||
void test_slave_setup_start(void *mbs_handle);
|
||||
// Helper function to read characteristic from slave
|
||||
esp_err_t read_modbus_parameter(void *handle, uint16_t cid, uint16_t *par_data);
|
||||
// Helper function to write characteristic into slave
|
||||
esp_err_t write_modbus_parameter(void *handle, uint16_t cid, uint16_t *par_data);
|
||||
|
||||
// Common test check leak function
|
||||
void test_common_check_leak(size_t before_free, size_t after_free, const char *type, size_t warn_threshold, size_t critical_threshold);
|
||||
void test_slave_stop_busy_task(TaskHandle_t busy_task_handle);
|
||||
uint32_t test_common_wait_done(TickType_t timeout_ticks);
|
||||
void test_common_start();
|
||||
void test_common_stop();
|
||||
void test_common_task_notify_start(TaskHandle_t task_handle, uint32_t value);
|
72
test_apps/test_common/mb_utest_lib/CMakeLists.txt
Normal file
72
test_apps/test_common/mb_utest_lib/CMakeLists.txt
Normal file
@@ -0,0 +1,72 @@
|
||||
message(STATUS "mb_ut_lib: ${CMAKE_CURRENT_LIST_DIR}, ${CONFIG_MB_UTEST}")
|
||||
|
||||
add_library(mb_ut_lib "${CMAKE_CURRENT_LIST_DIR}/port_adapter.c"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/port_stubs.c")
|
||||
|
||||
idf_component_get_property(dir esp-modbus COMPONENT_DIR)
|
||||
target_include_directories(mb_ut_lib PUBLIC
|
||||
"${CMAKE_CURRENT_LIST_DIR}"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include"
|
||||
"${dir}/modbus/mb_controller/common"
|
||||
"${dir}/modbus/mb_controller/common/include"
|
||||
"${dir}/modbus/mb_controller/serial"
|
||||
"${dir}/modbus/mb_controller/tcp"
|
||||
"${dir}/modbus/mb_objects/include"
|
||||
"${dir}/modbus/mb_ports/common"
|
||||
"${dir}/modbus/mb_ports/serial"
|
||||
"${dir}/modbus/mb_ports/tcp"
|
||||
"${dir}/modbus/mb_transports"
|
||||
"${dir}/modbus/mb_transports/rtu"
|
||||
"${dir}/modbus/mb_transports/ascii"
|
||||
"${dir}/modbus/mb_transports/tcp"
|
||||
)
|
||||
|
||||
idf_component_get_property(driver_lib driver COMPONENT_LIB)
|
||||
target_link_libraries(mb_ut_lib PUBLIC ${driver_lib})
|
||||
idf_component_get_property(timer_lib esp_timer COMPONENT_LIB)
|
||||
target_link_libraries(mb_ut_lib PUBLIC ${timer_lib})
|
||||
idf_component_get_property(netif_lib esp_netif COMPONENT_LIB)
|
||||
target_link_libraries(mb_ut_lib PUBLIC ${netif_lib})
|
||||
idf_component_get_property(test_utils_lib test_utils COMPONENT_LIB)
|
||||
target_link_libraries(mb_ut_lib PUBLIC ${test_utils_lib})
|
||||
|
||||
|
||||
# Wrap port functions to substitute port with port_adapter object
|
||||
if(CONFIG_MB_PORT_ADAPTER_EN)
|
||||
|
||||
set(WRAP_FUNCTIONS
|
||||
mbm_port_tcp_create
|
||||
mbm_port_tcp_set_conn_cb
|
||||
mbm_port_tcp_get_slave_info
|
||||
mbm_port_tcp_send_data
|
||||
mbm_port_tcp_recv_data
|
||||
mbm_port_tcp_enable
|
||||
mbm_port_tcp_disable
|
||||
mbm_port_tcp_delete
|
||||
# mbm_port_timer_expired
|
||||
mbs_port_tcp_create
|
||||
mbs_port_tcp_enable
|
||||
mbs_port_tcp_disable
|
||||
mbs_port_tcp_send_data
|
||||
mbs_port_tcp_recv_data
|
||||
mbs_port_tcp_delete
|
||||
mb_port_evt_post
|
||||
mb_port_evt_get
|
||||
mb_port_ser_create
|
||||
mb_port_ser_recv_data
|
||||
mb_port_ser_send_data
|
||||
mb_port_ser_enable
|
||||
mb_port_ser_disable
|
||||
mb_port_ser_delete
|
||||
)
|
||||
|
||||
foreach(wrap ${WRAP_FUNCTIONS})
|
||||
target_link_libraries(mb_ut_lib PUBLIC "-Wl,--undefined=${wrap}")
|
||||
target_link_libraries(mb_ut_lib PUBLIC "-Wl,--wrap=${wrap}")
|
||||
#target_link_libraries(mb_ut_lib INTERFACE "-u __wrap_${wrap}")
|
||||
endforeach()
|
||||
|
||||
endif()
|
||||
|
||||
# allow multiple symbol definitions
|
||||
target_link_libraries(mb_ut_lib PUBLIC "-Wl,--allow-multiple-definition")
|
671
test_apps/test_common/mb_utest_lib/port_adapter.c
Normal file
671
test_apps/test_common/mb_utest_lib/port_adapter.c
Normal file
@@ -0,0 +1,671 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdatomic.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "esp_timer.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#include "mb_common.h"
|
||||
#include "esp_modbus_common.h"
|
||||
#include "mbc_slave.h"
|
||||
|
||||
#include "mb_common.h"
|
||||
#include "port_common.h"
|
||||
#include "mb_config.h"
|
||||
#include "port_serial_common.h"
|
||||
#include "port_adapter.h"
|
||||
#include "mb_port_types.h"
|
||||
#include "port_stubs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/* ----------------------- Defines ------------------------------------------*/
|
||||
|
||||
#define MB_ADAPTER_TASK_STACK_SIZE (CONFIG_FMB_PORT_TASK_STACK_SIZE)
|
||||
#define MB_ADAPTER_MAX_PORTS (8)
|
||||
#define MB_ADAPTER_RX_QUEUE_MAX_SIZE (CONFIG_FMB_QUEUE_LENGTH * MB_ADAPTER_MAX_PORTS)
|
||||
#define MB_ADAPTER_TX_QUEUE_MAX_SIZE (CONFIG_FMB_QUEUE_LENGTH * MB_ADAPTER_MAX_PORTS)
|
||||
#define MB_ADAPTER_QUEUE_TIMEOUT (200 / portTICK_PERIOD_MS)
|
||||
#define MB_ADAPTER_QUEUE_SET_MAX_LEN ((sizeof(frame_entry_t) + sizeof(mb_uid_info_t)) * MB_ADAPTER_MAX_PORTS) //
|
||||
#define MB_ADAPTER_CONN_TIMEOUT (200 / portTICK_PERIOD_MS)
|
||||
|
||||
typedef struct _mb_adapter_port_entry
|
||||
{
|
||||
mb_port_base_t base;
|
||||
uint8_t rx_buffer[CONFIG_FMB_BUFFER_SIZE];
|
||||
uint16_t recv_length;
|
||||
uint64_t send_time_stamp;
|
||||
uint64_t recv_time_stamp;
|
||||
uint64_t test_timeout_us;
|
||||
uint32_t flags;
|
||||
mb_uid_info_t addr_info;
|
||||
QueueHandle_t rx_queue;
|
||||
QueueHandle_t tx_queue;
|
||||
QueueHandle_t conn_queue; /*!< conection queue handle */
|
||||
SemaphoreHandle_t conn_sema_handle; /*!< connection blocking semaphore handle */
|
||||
esp_timer_handle_t timer_handle;
|
||||
EventGroupHandle_t event_group_handle;
|
||||
LIST_ENTRY(_mb_adapter_port_entry) entries;
|
||||
} mb_port_adapter_t;
|
||||
|
||||
/* ----------------------- Static variables & functions ----------------------*/
|
||||
static const char *TAG = "mb_port.test_adapter";
|
||||
|
||||
static LIST_HEAD(mb_port_inst, _mb_adapter_port_entry) s_port_list = LIST_HEAD_INITIALIZER(s_port_list);
|
||||
static uint32_t s_port_list_counter = 0; /*!< port registered instance counter */
|
||||
|
||||
// The queue set for the receive task
|
||||
static QueueSetHandle_t queue_set = NULL;
|
||||
static TaskHandle_t adapter_task_handle; /*!< receive task handle */
|
||||
|
||||
IRAM_ATTR
|
||||
static bool mb_port_adapter_tmr_expired(void *inst)
|
||||
{
|
||||
mb_port_adapter_t *port_obj = __containerof(inst, mb_port_adapter_t, base);
|
||||
|
||||
bool need_poll = false;
|
||||
mb_tmr_mode_enum_t timer_mode = mb_port_get_cur_tmr_mode(&port_obj->base);
|
||||
|
||||
mb_port_tmr_disable(&port_obj->base);
|
||||
|
||||
switch (timer_mode)
|
||||
{
|
||||
case MB_TMODE_T35:
|
||||
need_poll = mb_port_evt_post(&port_obj->base, EVENT(EV_READY));
|
||||
ESP_EARLY_LOGD(TAG, "%p:EV_READY", port_obj->base.descr.parent);
|
||||
break;
|
||||
|
||||
case MB_TMODE_RESPOND_TIMEOUT:
|
||||
mb_port_evt_set_err_type(&port_obj->base, EV_ERROR_RESPOND_TIMEOUT);
|
||||
need_poll = mb_port_evt_post(&port_obj->base, EVENT(EV_ERROR_PROCESS));
|
||||
|
||||
ESP_EARLY_LOGW(TAG, "%p:EV_ERROR_RESPOND_TIMEOUT", port_obj->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(&port_obj->base, EVENT(EV_EXECUTE));
|
||||
ESP_EARLY_LOGD(TAG, "%p:MB_TMODE_CONVERT_DELAY", port_obj->base.descr.parent);
|
||||
break;
|
||||
|
||||
default:
|
||||
need_poll = mb_port_evt_post(&port_obj->base, EVENT(EV_READY));
|
||||
break;
|
||||
}
|
||||
|
||||
return need_poll;
|
||||
}
|
||||
|
||||
void mb_port_adapter_set_response_time(mb_port_base_t *inst, uint64_t resp_time)
|
||||
{
|
||||
mb_port_adapter_t *port_obj = __containerof(inst, mb_port_adapter_t, base);
|
||||
atomic_store(&(port_obj->test_timeout_us), resp_time);
|
||||
}
|
||||
|
||||
int mb_port_adapter_get_rx_buffer(mb_port_base_t *inst, uint8_t **ppfame, int *plen)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((ppfame && plen), -1, TAG, "mb serial get buffer failure.");
|
||||
mb_port_adapter_t *port_obj = __containerof(inst, mb_port_adapter_t, base);
|
||||
int sz = port_obj->recv_length;
|
||||
if (*ppfame && *plen >= port_obj->recv_length)
|
||||
{
|
||||
CRITICAL_SECTION(inst->lock)
|
||||
{
|
||||
memcpy(*ppfame, port_obj->rx_buffer, sz);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
*ppfame = port_obj->rx_buffer;
|
||||
*plen = sz;
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
|
||||
int mb_port_adapter_get_tx_buffer(mb_port_base_t *inst, uint8_t **ppfame, int *plen)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((ppfame && plen), -1, TAG, "mb serial get buffer failure.");
|
||||
mb_port_adapter_t *port_obj = __containerof(inst, mb_port_adapter_t, base);
|
||||
int sz = port_obj->recv_length;
|
||||
if (*ppfame && *plen >= port_obj->recv_length)
|
||||
{
|
||||
CRITICAL_SECTION(inst->lock)
|
||||
{
|
||||
memcpy(*ppfame, port_obj->rx_buffer, sz);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
*ppfame = port_obj->rx_buffer;
|
||||
*plen = sz;
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
|
||||
void mb_port_adapter_set_flag(mb_port_base_t *inst, mb_queue_flags_t mask)
|
||||
{
|
||||
mb_port_adapter_t *port_obj = __containerof(inst, mb_port_adapter_t, base);
|
||||
EventBits_t bits = xEventGroupSetBits(port_obj->event_group_handle, (EventBits_t)mask);
|
||||
ESP_LOGV(TAG, "%s: set flag (0x%x).", inst->descr.parent_name, (int)bits);
|
||||
}
|
||||
|
||||
void mb_port_adapter_clear_flag(mb_port_base_t *inst, mb_queue_flags_t mask)
|
||||
{
|
||||
mb_port_adapter_t *port_obj = __containerof(inst, mb_port_adapter_t, base);
|
||||
EventBits_t bits = xEventGroupClearBits(port_obj->event_group_handle, (EventBits_t)mask);
|
||||
ESP_LOGV(TAG, "%s: clear flag (0x%x).", inst->descr.parent_name, (int)bits);
|
||||
}
|
||||
|
||||
uint16_t mb_port_adapter_wait_flag(mb_port_base_t *inst, uint16_t mask, uint32_t timeout)
|
||||
{
|
||||
mb_port_adapter_t *port_obj = __containerof(inst, mb_port_adapter_t, base);
|
||||
EventBits_t bits = xEventGroupWaitBits(port_obj->event_group_handle, // The event group being tested.
|
||||
(EventBits_t)mask, // The bits within the event group to wait for.
|
||||
pdTRUE, // Masked bits should be cleared before returning.
|
||||
pdFALSE, // Don't wait for both bits, either bit will do.
|
||||
(TickType_t)timeout); // Wait during timeout for either bit to be set.
|
||||
ESP_LOGV(TAG, "%s: get flag (0x%x).", inst->descr.parent_name, (int)bits);
|
||||
return (uint16_t)bits;
|
||||
}
|
||||
|
||||
// Timer task to send notification on timeout expiration
|
||||
IRAM_ATTR
|
||||
static void mb_port_adapter_timer_cb(void *param)
|
||||
{
|
||||
mb_port_adapter_t *port_obj = __containerof(param, mb_port_adapter_t, base);
|
||||
uint8_t temp_buffer[CONFIG_FMB_BUFFER_SIZE] = {0};
|
||||
mb_port_adapter_t *it;
|
||||
|
||||
if (!LIST_EMPTY(&s_port_list))
|
||||
{
|
||||
// send the queued frame to all registered ports with the same port number
|
||||
int sz = queue_pop(port_obj->tx_queue, (void *)&temp_buffer[0], CONFIG_FMB_BUFFER_SIZE, NULL);
|
||||
LIST_FOREACH(it, &s_port_list, entries)
|
||||
{
|
||||
if (it && (it != port_obj) &&
|
||||
(port_obj->addr_info.port == it->addr_info.port) && (sz != -1)
|
||||
&& (port_obj->addr_info.proto == it->addr_info.proto)
|
||||
&& (!port_obj->addr_info.uid || !it->addr_info.uid))
|
||||
{
|
||||
// Send the data to all ports with the same communication port setting except itself
|
||||
queue_push(it->rx_queue, (void *)&temp_buffer[0], sz, NULL);
|
||||
mb_port_adapter_set_flag(&port_obj->base, MB_QUEUE_FLAG_SENT);
|
||||
ESP_LOGW(TAG, "Send (%d bytes) from %s to %s. ", (int)sz, port_obj->base.descr.parent_name, it->base.descr.parent_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool mb_port_adapter_is_connected(void *inst)
|
||||
{
|
||||
mb_port_adapter_t *port_obj = __containerof(inst, mb_port_adapter_t, base);
|
||||
if (queue_is_empty(port_obj->conn_queue)
|
||||
&& port_obj->base.descr.is_master) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void mb_port_adapter_conn_logic(void *inst, mb_uid_info_t *paddr_info)
|
||||
{
|
||||
bool slave_found = false;
|
||||
mb_port_adapter_t *slave = NULL;
|
||||
mb_port_adapter_t *port_obj = __containerof(inst, mb_port_adapter_t, base);
|
||||
|
||||
if (port_obj->base.descr.is_master) { // master object
|
||||
LIST_FOREACH(slave, &s_port_list, entries) {
|
||||
if ((paddr_info->uid == slave->addr_info.uid)
|
||||
&& !slave->base.descr.is_master
|
||||
&& (paddr_info->port == slave->addr_info.port)) {
|
||||
// Register each slave object
|
||||
ESP_LOGW(TAG, "Check connection state of object #%d(%s), uid: %d, port: %d, %s",
|
||||
paddr_info->index, paddr_info->node_name_str,
|
||||
paddr_info->uid, paddr_info->port,
|
||||
(paddr_info->state == MB_SOCK_STATE_CONNECTED) ? "CONNECTED" : "DISCONNECTED");
|
||||
if ((paddr_info->state != MB_SOCK_STATE_CONNECTED) || (paddr_info->inst != inst)) {
|
||||
(void)xQueueSend(slave->conn_queue, &port_obj->addr_info, MB_ADAPTER_QUEUE_TIMEOUT);
|
||||
} else {
|
||||
mb_port_adapter_set_flag(inst, MB_QUEUE_FLAG_CONNECTED);
|
||||
}
|
||||
slave_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!slave_found) {
|
||||
// reactivate the connection set
|
||||
ESP_LOGE(TAG, "Slave #%d(%s), uid: %d, port: %d is not found, reconnect.",
|
||||
paddr_info->index, paddr_info->node_name_str, paddr_info->uid, paddr_info->port);
|
||||
(void)xQueueSend(port_obj->conn_queue, paddr_info, MB_ADAPTER_QUEUE_TIMEOUT);
|
||||
vTaskDelay(MB_ADAPTER_CONN_TIMEOUT);
|
||||
}
|
||||
} else { // slave connection logic
|
||||
ESP_LOGW(TAG, "Register connection in adapter object #%d(%s), uid: %d, port: %d, to master %s",
|
||||
port_obj->addr_info.index, port_obj->addr_info.node_name_str,
|
||||
port_obj->addr_info.uid, port_obj->addr_info.port, paddr_info->node_name_str);
|
||||
// Mimic connection logic for each slave here
|
||||
//mb_port_adapter_slave_connect(it);
|
||||
port_obj->addr_info.state = MB_SOCK_STATE_CONNECTED;
|
||||
mb_port_adapter_t *master = (mb_port_adapter_t *)(paddr_info->inst);
|
||||
port_obj->addr_info.inst = paddr_info->inst; // link slave with master
|
||||
(void)xQueueSend(master->conn_queue, &port_obj->addr_info, MB_ADAPTER_QUEUE_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
// UART receive event task
|
||||
static void mb_port_adapter_task(void *p_args)
|
||||
{
|
||||
QueueSetMemberHandle_t active_queue = NULL;
|
||||
mb_port_adapter_t *it = NULL;
|
||||
mb_uid_info_t addr_info;
|
||||
frame_entry_t frame_entry;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (!LIST_EMPTY(&s_port_list))
|
||||
{
|
||||
active_queue = xQueueSelectFromSet(queue_set, MB_ADAPTER_QUEUE_TIMEOUT);
|
||||
LIST_FOREACH(it, &s_port_list, entries)
|
||||
{
|
||||
if (it && active_queue && (it->rx_queue == active_queue)) {
|
||||
if (xQueuePeek(it->rx_queue, &frame_entry, 0) == pdTRUE) {
|
||||
it->recv_length = frame_entry.len;
|
||||
mb_port_evt_post(&it->base, EVENT(EV_FRAME_RECEIVED, frame_entry.len, NULL, 0));
|
||||
ESP_LOGW(TAG, "%s, frame %d bytes is ready.", (it->base.descr.parent_name), (int)frame_entry.len);
|
||||
}
|
||||
} else if (it && (it->conn_queue == active_queue)) {
|
||||
if (xQueueReceive(it->conn_queue, &addr_info, MB_ADAPTER_QUEUE_TIMEOUT) == pdTRUE) {
|
||||
mb_port_adapter_conn_logic(it, &addr_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static mb_err_enum_t mb_port_adapter_connect(mb_tcp_opts_t *tcp_opts, void *pobject)
|
||||
{
|
||||
char **paddr_table = tcp_opts->ip_addr_table;
|
||||
mb_uid_info_t uid_info;
|
||||
mb_port_adapter_t *port_obj = __containerof(pobject, mb_port_adapter_t, base);
|
||||
|
||||
MB_RETURN_ON_FALSE((paddr_table && *paddr_table && (tcp_opts->mode == MB_TCP)),
|
||||
MB_EINVAL, TAG,
|
||||
"%s, invalid address table.", port_obj->base.descr.parent_name);
|
||||
int count = 0;
|
||||
while (*paddr_table)
|
||||
{
|
||||
int res = port_scan_addr_string((char *)*paddr_table, &uid_info);
|
||||
if (res > 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "Config: %s, IP: %s, port: %d, slave_addr: %d, ip_ver: %s",
|
||||
(char *)*paddr_table, uid_info.ip_addr_str, uid_info.port,
|
||||
uid_info.uid, (uid_info.addr_type == MB_IPV4 ? "IPV4" : "IPV6"));
|
||||
uid_info.index = count++;
|
||||
free(uid_info.ip_addr_str);
|
||||
uid_info.ip_addr_str = (char *)*paddr_table;
|
||||
uid_info.node_name_str = uid_info.ip_addr_str;
|
||||
if (xQueueSend(port_obj->conn_queue, &uid_info, MB_EVENT_QUEUE_TIMEOUT_MAX) != pdTRUE)
|
||||
{
|
||||
ESP_LOGE(TAG, "can not send info to connection queue.");
|
||||
};
|
||||
// Mimic connection event
|
||||
if (!tcp_opts->start_disconnected) {
|
||||
uint16_t event = mb_port_adapter_wait_flag(pobject, MB_QUEUE_FLAG_CONNECTED, MB_ADAPTER_CONN_TIMEOUT);
|
||||
if (!event) {
|
||||
ESP_LOGE(TAG, "Could not connect to slave %s during timeout.", (char *)*paddr_table);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "unable to open slave: %s, check configuration.", (char *)*paddr_table);
|
||||
}
|
||||
paddr_table++;
|
||||
}
|
||||
ESP_LOGW(TAG, "parsed and added %d slave configurations.", count);
|
||||
return count ? MB_ENOERR : MB_EINVAL;
|
||||
}
|
||||
|
||||
mb_err_enum_t mb_port_adapter_create(mb_uid_info_t *paddr_info, mb_port_base_t **in_out_obj)
|
||||
{
|
||||
mb_port_adapter_t *padapter = NULL;
|
||||
mb_err_enum_t ret = MB_EILLSTATE;
|
||||
padapter = (mb_port_adapter_t *)calloc(1, sizeof(mb_port_adapter_t));
|
||||
|
||||
MB_GOTO_ON_FALSE((padapter && paddr_info && in_out_obj), MB_EILLSTATE, error, TAG, "mb serial port creation error.");
|
||||
|
||||
CRITICAL_SECTION_INIT(padapter->base.lock);
|
||||
padapter->base.descr = ((mb_port_base_t *)*in_out_obj)->descr;
|
||||
padapter->addr_info = *paddr_info;
|
||||
|
||||
esp_timer_create_args_t timer_conf = {
|
||||
.callback = mb_port_adapter_timer_cb,
|
||||
.arg = padapter,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = padapter->base.descr.parent_name};
|
||||
// Create Modbus timer handlers for streams
|
||||
MB_GOTO_ON_ERROR(esp_timer_create(&timer_conf, &padapter->timer_handle),
|
||||
error, TAG, "create input stream timer failed.");
|
||||
|
||||
padapter->rx_queue = queue_create(MB_ADAPTER_RX_QUEUE_MAX_SIZE);
|
||||
MB_GOTO_ON_FALSE(padapter->rx_queue, MB_EILLSTATE, error, TAG, "create rx queue failed");
|
||||
padapter->tx_queue = queue_create(MB_ADAPTER_TX_QUEUE_MAX_SIZE);
|
||||
MB_GOTO_ON_FALSE(padapter->tx_queue, MB_EILLSTATE, error, TAG, "create tx queue failed");
|
||||
padapter->event_group_handle = xEventGroupCreate();
|
||||
MB_GOTO_ON_FALSE((padapter->event_group_handle), MB_EILLSTATE, error, TAG,
|
||||
"%p, event group create error.", *in_out_obj);
|
||||
|
||||
if (!s_port_list_counter)
|
||||
{
|
||||
// Create a task to handle UART events
|
||||
BaseType_t status = xTaskCreatePinnedToCore(mb_port_adapter_task, "adapt_rx_task",
|
||||
MB_ADAPTER_TASK_STACK_SIZE,
|
||||
&padapter->base, CONFIG_FMB_PORT_TASK_PRIO,
|
||||
&adapter_task_handle, CONFIG_FMB_PORT_TASK_AFFINITY);
|
||||
// Force exit from function with failure
|
||||
MB_GOTO_ON_FALSE((status == pdPASS), MB_EILLSTATE, error, TAG,
|
||||
"serial task creation error, returned (0x%x).", (int)status);
|
||||
// Create the queue set to handle clients
|
||||
queue_set = xQueueCreateSet(MB_ADAPTER_QUEUE_SET_MAX_LEN);
|
||||
MB_GOTO_ON_FALSE((queue_set), MB_EILLSTATE, error, TAG, "can not create queue set.");
|
||||
}
|
||||
// Add connection set for master object only
|
||||
padapter->conn_queue = xQueueCreate(MB_ADAPTER_MAX_PORTS, sizeof(mb_uid_info_t));
|
||||
MB_GOTO_ON_FALSE(padapter->conn_queue, MB_EILLSTATE, error, TAG, "create conn queue failed");
|
||||
MB_GOTO_ON_FALSE((queue_set && xQueueAddToSet(padapter->conn_queue, queue_set)),
|
||||
MB_EILLSTATE, error, TAG, "can not add conn queue to queue set.");
|
||||
// Add rx queue to set
|
||||
MB_GOTO_ON_FALSE((queue_set && xQueueAddToSet(padapter->rx_queue, queue_set)),
|
||||
MB_EILLSTATE, error, TAG, "can not add rx queue to queue set.");
|
||||
|
||||
MB_GOTO_ON_FALSE((s_port_list_counter <= MB_ADAPTER_MAX_PORTS), MB_EILLSTATE, error,
|
||||
TAG, "adapter exceeded maximum number of ports = %d", MB_ADAPTER_MAX_PORTS);
|
||||
|
||||
// register new port instance in the list
|
||||
LIST_INSERT_HEAD(&s_port_list, padapter, entries);
|
||||
s_port_list_counter++;
|
||||
char *pstr;
|
||||
int res = asprintf(&pstr, "%d;%s;%u", (unsigned)paddr_info->uid,
|
||||
padapter->base.descr.parent_name, (unsigned)paddr_info->port);
|
||||
MB_GOTO_ON_FALSE((res), MB_EILLSTATE, error,
|
||||
TAG, "object adress info alloc fail, err: %d", (int)res);
|
||||
padapter->base.cb.tmr_expired = mb_port_adapter_tmr_expired;
|
||||
padapter->base.cb.tx_empty = NULL;
|
||||
padapter->base.cb.byte_rcvd = NULL;
|
||||
padapter->base.arg = (void *)padapter;
|
||||
|
||||
padapter->addr_info.state = MB_SOCK_STATE_CONNECTING;
|
||||
padapter->addr_info.inst = padapter;
|
||||
padapter->addr_info.node_name_str = pstr;
|
||||
padapter->addr_info.ip_addr_str = pstr;
|
||||
*in_out_obj = &(padapter->base);
|
||||
ESP_LOGW(TAG, "created object @%p, from parent %p", padapter, padapter->base.descr.parent);
|
||||
return MB_ENOERR;
|
||||
|
||||
error:
|
||||
if (padapter) {
|
||||
mb_port_adapter_delete(&padapter->base);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
mb_err_enum_t mb_port_adapter_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t **in_out_obj)
|
||||
{
|
||||
mb_uid_info_t addr_info = {
|
||||
.ip_addr_str = NULL,
|
||||
.index = s_port_list_counter,
|
||||
.addr_type = MB_IPV4,
|
||||
.uid = tcp_opts->uid,
|
||||
.port = tcp_opts->port,
|
||||
.proto = MB_TCP,
|
||||
.state = MB_SOCK_STATE_UNDEF
|
||||
};
|
||||
|
||||
mb_port_base_t *pobj = *in_out_obj;
|
||||
mb_err_enum_t ret = mb_port_adapter_create(&addr_info, &pobj);
|
||||
|
||||
if ((ret == MB_ENOERR) && pobj) {
|
||||
// Parse master config and register dependent objects
|
||||
if (pobj->descr.is_master) {
|
||||
ESP_LOGI(TAG, "Parsing of config for %s", pobj->descr.parent_name);
|
||||
ret |= mb_port_adapter_connect(tcp_opts, pobj);
|
||||
MB_GOTO_ON_FALSE((ret == MB_ENOERR), MB_EILLSTATE, error, TAG,
|
||||
"%s, could not parse config, err=%x.", pobj->descr.parent_name, (int)ret);
|
||||
}
|
||||
ESP_LOGW(TAG, "%s, set test time to %" PRIu64, pobj->descr.parent_name, tcp_opts->test_tout_us);
|
||||
mb_port_adapter_set_response_time(pobj, (tcp_opts->test_tout_us));
|
||||
}
|
||||
*in_out_obj = pobj;
|
||||
return ret;
|
||||
|
||||
error:
|
||||
if (pobj) {
|
||||
mb_port_adapter_delete(pobj);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
|
||||
|
||||
mb_err_enum_t mb_port_adapter_ser_create(mb_serial_opts_t *ser_opts, mb_port_base_t **in_out_obj)
|
||||
{
|
||||
mb_uid_info_t addr_info = {
|
||||
.ip_addr_str = NULL, // unknown
|
||||
.index = s_port_list_counter,
|
||||
.addr_type = MB_NOIP,
|
||||
.uid = ser_opts->uid,
|
||||
.port = ser_opts->port,
|
||||
.proto = ser_opts->mode,
|
||||
.state = MB_SOCK_STATE_UNDEF
|
||||
};
|
||||
|
||||
mb_port_base_t *pobj = *in_out_obj;
|
||||
mb_err_enum_t ret = mb_port_adapter_create(&addr_info, &pobj);
|
||||
if ((ret == MB_ENOERR) && pobj) {
|
||||
ESP_LOGW(TAG, "%s, set test time to %d", pobj->descr.parent_name, (int)(ser_opts->test_tout_us));
|
||||
mb_port_adapter_set_response_time(pobj, (ser_opts->test_tout_us));
|
||||
*in_out_obj = pobj;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
void mb_port_adapter_delete(mb_port_base_t *inst)
|
||||
{
|
||||
mb_port_adapter_t *port_obj = __containerof(inst, mb_port_adapter_t, base);
|
||||
|
||||
if (port_obj->rx_queue && !queue_is_empty(port_obj->rx_queue))
|
||||
{
|
||||
queue_flush(port_obj->rx_queue);
|
||||
}
|
||||
if (port_obj->tx_queue && !queue_is_empty(port_obj->tx_queue))
|
||||
{
|
||||
queue_flush(port_obj->tx_queue);
|
||||
}
|
||||
if (port_obj && port_obj->event_group_handle)
|
||||
{
|
||||
vEventGroupDelete(port_obj->event_group_handle);
|
||||
port_obj->event_group_handle = NULL;
|
||||
}
|
||||
if (port_obj && port_obj->timer_handle)
|
||||
{
|
||||
esp_timer_stop(port_obj->timer_handle);
|
||||
esp_timer_delete(port_obj->timer_handle);
|
||||
port_obj->timer_handle = NULL;
|
||||
}
|
||||
CRITICAL_SECTION_CLOSE(inst->lock);
|
||||
LIST_REMOVE(port_obj, entries);
|
||||
if (s_port_list_counter)
|
||||
{
|
||||
atomic_store(&(s_port_list_counter), (s_port_list_counter - 1));
|
||||
if (queue_set && port_obj && port_obj->rx_queue)
|
||||
{
|
||||
xQueueRemoveFromSet(port_obj->rx_queue, queue_set);
|
||||
}
|
||||
if (port_obj && port_obj->conn_queue && queue_set)
|
||||
{
|
||||
xQueueRemoveFromSet(port_obj->conn_queue, queue_set);
|
||||
}
|
||||
}
|
||||
if (!s_port_list_counter)
|
||||
{
|
||||
if (port_obj && adapter_task_handle) {
|
||||
vTaskDelete(adapter_task_handle);
|
||||
adapter_task_handle = NULL;
|
||||
}
|
||||
vQueueDelete(queue_set);
|
||||
queue_set = NULL;
|
||||
}
|
||||
if (port_obj && port_obj->rx_queue && port_obj->tx_queue)
|
||||
{
|
||||
queue_delete(port_obj->rx_queue);
|
||||
queue_delete(port_obj->tx_queue);
|
||||
port_obj->rx_queue = NULL;
|
||||
port_obj->tx_queue = NULL;
|
||||
}
|
||||
if (port_obj && port_obj->conn_queue)
|
||||
{
|
||||
vQueueDelete(port_obj->conn_queue);
|
||||
port_obj->conn_queue = NULL;
|
||||
}
|
||||
if (port_obj && port_obj->addr_info.node_name_str)
|
||||
{
|
||||
free(port_obj->addr_info.node_name_str);
|
||||
port_obj->addr_info.node_name_str = NULL;
|
||||
port_obj->addr_info.ip_addr_str = NULL;
|
||||
}
|
||||
free(port_obj);
|
||||
}
|
||||
|
||||
static esp_err_t mb_port_adapter_set_timer(mb_port_base_t *inst, uint64_t time_diff_us)
|
||||
{
|
||||
mb_port_adapter_t *port_obj = __containerof(inst, mb_port_adapter_t, base);
|
||||
esp_timer_stop(port_obj->timer_handle);
|
||||
esp_err_t ret = esp_timer_start_once(port_obj->timer_handle, time_diff_us);
|
||||
MB_RETURN_ON_FALSE((ret == ESP_OK),
|
||||
ESP_ERR_INVALID_STATE, TAG,
|
||||
"%s, could not start timer, err=%x.", inst->descr.parent_name, (int)ret);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool mb_port_adapter_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength)
|
||||
{
|
||||
MB_RETURN_ON_FALSE((ppframe && plength), false, TAG, "mb serial get buffer failure.");
|
||||
mb_port_adapter_t *port_obj = __containerof(inst, mb_port_adapter_t, base);
|
||||
int length = *plength ? *plength : port_obj->recv_length;
|
||||
|
||||
if (length)
|
||||
{
|
||||
CRITICAL_SECTION_LOCK(inst->lock);
|
||||
int length = queue_pop(port_obj->rx_queue, &port_obj->rx_buffer[0], CONFIG_FMB_BUFFER_SIZE, NULL);
|
||||
if (length)
|
||||
{
|
||||
mb_port_tmr_disable(inst);
|
||||
ESP_LOGW(TAG, "%s, received data: %d bytes.", inst->descr.parent_name, length);
|
||||
// Stop timer because the new data is received
|
||||
// Store the timestamp of received frame
|
||||
port_obj->recv_time_stamp = esp_timer_get_time();
|
||||
*ppframe = &port_obj->rx_buffer[0];
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(MB_STR_CAT(inst->descr.parent_name, ":PORT_RECV"),
|
||||
(void *)&port_obj->rx_buffer[0], (uint16_t)length, ESP_LOG_WARN);
|
||||
}
|
||||
CRITICAL_SECTION_UNLOCK(inst->lock);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "%s: junk data (%d bytes) received. ", inst->descr.parent_name, length);
|
||||
}
|
||||
*plength = length;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mb_port_adapter_send_data(mb_port_base_t *inst, uint8_t address, uint8_t *pframe, uint16_t length)
|
||||
{
|
||||
bool res = false;
|
||||
mb_port_adapter_t *port_obj = __containerof(inst, mb_port_adapter_t, base);
|
||||
uint64_t time_diff = atomic_load(&port_obj->test_timeout_us);
|
||||
|
||||
if (pframe && length)
|
||||
{
|
||||
CRITICAL_SECTION_LOCK(inst->lock);
|
||||
esp_err_t err = queue_push(port_obj->tx_queue, (void *)pframe, length, NULL);
|
||||
CRITICAL_SECTION_UNLOCK(inst->lock);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK),
|
||||
false, TAG, "%s, could not send the data into queue.", inst->descr.parent_name);
|
||||
MB_RETURN_ON_FALSE((mb_port_adapter_set_timer(inst, time_diff) == ESP_OK),
|
||||
false, TAG, "%s, could not set output timer.", inst->descr.parent_name);
|
||||
// Wait for send buffer complition
|
||||
uint16_t flags = mb_port_adapter_wait_flag(inst, MB_QUEUE_FLAG_SENT, MB_EVENT_QUEUE_TIMEOUT_MAX);
|
||||
port_obj->send_time_stamp = esp_timer_get_time();
|
||||
// Print sent packet, the tag used is more clear to see
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(MB_STR_CAT(inst->descr.parent_name, ":PORT_SEND"),
|
||||
(void *)pframe, length, ESP_LOG_WARN);
|
||||
(void)mb_port_evt_post(inst, EVENT(EV_FRAME_SENT));
|
||||
ESP_LOGW(TAG, "%s, tx completed, flags = 0x%04x.", inst->descr.parent_name, (int)flags);
|
||||
res = true;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "send callback %p, %u. ", pframe, (unsigned)length);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void mb_port_adapter_enable(mb_port_base_t *inst)
|
||||
{
|
||||
ESP_LOGD(TAG, "adapter tcp enable port.");
|
||||
}
|
||||
|
||||
void mb_port_adapter_disable(mb_port_base_t *inst)
|
||||
{
|
||||
ESP_LOGD(TAG, "adapter tcp disable port.");
|
||||
}
|
||||
|
||||
mb_uid_info_t *mb_port_adapter_get_slave_info(mb_port_base_t *inst, uint8_t slave_addr, mb_sock_state_t exp_state)
|
||||
{
|
||||
mb_port_adapter_t *it = NULL;
|
||||
|
||||
if (!LIST_EMPTY(&s_port_list))
|
||||
{
|
||||
LIST_FOREACH(it, &s_port_list, entries)
|
||||
{
|
||||
if ((it->addr_info.uid == slave_addr) && (it->base.descr.is_master == false))
|
||||
{
|
||||
return (&it->addr_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mb_port_adapter_tcp_set_conn_cb(mb_port_base_t *inst, void *conn_fp, void *arg)
|
||||
{
|
||||
void (*on_conn_done_cb)(void *) = conn_fp;
|
||||
|
||||
if (mb_port_adapter_is_connected(inst)) {
|
||||
if (on_conn_done_cb && arg) {
|
||||
on_conn_done_cb(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
47
test_apps/test_common/mb_utest_lib/port_adapter.h
Normal file
47
test_apps/test_common/mb_utest_lib/port_adapter.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 <sys/queue.h>
|
||||
|
||||
#include "port_tcp_utils.h"
|
||||
#include "port_common.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
MB_QUEUE_FLAG_EMPTY = 0x0000,
|
||||
MB_QUEUE_FLAG_SENT = 0x0001,
|
||||
MB_QUEUE_FLAG_RECV = 0x0002,
|
||||
MB_QUEUE_FLAG_CONNECTED = 0x0004
|
||||
} mb_queue_flags_t;
|
||||
|
||||
#define MB_QUEUE_FLAGS (MB_QUEUE_FLAG_SENT | MB_QUEUE_FLAG_RECV | MB_QUEUE_FLAG_CONNECTED)
|
||||
|
||||
typedef struct _uid_info mb_uid_info_t;
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
|
||||
mb_err_enum_t mb_port_adapter_ser_create(mb_serial_opts_t *ser_opts, mb_port_base_t **in_out_obj);
|
||||
#endif
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
|
||||
mb_err_enum_t mb_port_adapter_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t **in_out_obj);
|
||||
#endif
|
||||
|
||||
void mb_port_adapter_delete(mb_port_base_t *inst);
|
||||
void mb_port_adapter_set_response_time(mb_port_base_t *inst, uint64_t resp_time);
|
||||
int mb_port_adapter_get_rx_buffer(mb_port_base_t *inst, uint8_t **ppfame, int *plength);
|
||||
|
||||
bool mb_port_adapter_send_data(mb_port_base_t *inst, uint8_t address, uint8_t *pframe, uint16_t length);
|
||||
bool mb_port_adapter_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength);
|
||||
void mb_port_adapter_enable(mb_port_base_t *inst);
|
||||
void mb_port_adapter_disable(mb_port_base_t *inst);
|
||||
void mb_port_adapter_tcp_set_conn_cb(mb_port_base_t *inst, void *conn_fp, void *arg);
|
||||
void mb_port_adapter_tcp_set_conn_time(mb_port_base_t *inst, void *conn_fp, void *arg);
|
||||
mb_uid_info_t *mb_port_adapter_get_slave_info(mb_port_base_t *inst, uint8_t slave_addr, mb_sock_state_t exp_state);
|
173
test_apps/test_common/mb_utest_lib/port_stubs.c
Normal file
173
test_apps/test_common/mb_utest_lib/port_stubs.c
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdatomic.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "esp_timer.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#include "mb_common.h"
|
||||
#include "esp_modbus_common.h"
|
||||
#include "mbc_slave.h"
|
||||
|
||||
#include "mb_common.h"
|
||||
#include "port_common.h"
|
||||
#include "mb_config.h"
|
||||
#include "port_serial_common.h"
|
||||
#include "port_tcp_common.h"
|
||||
#include "port_adapter.h"
|
||||
#include "port_stubs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static const char *TAG = "port_stub";
|
||||
|
||||
// Below are function wrappers to substitute actual port object with the adapter object for test purpose
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
|
||||
|
||||
mb_err_enum_t __wrap_mb_port_ser_create(mb_serial_opts_t *ser_opts, mb_port_base_t **in_out_obj)
|
||||
{
|
||||
return mb_port_adapter_ser_create(ser_opts, in_out_obj);
|
||||
}
|
||||
|
||||
void __wrap_mb_port_ser_delete(mb_port_base_t *inst)
|
||||
{
|
||||
mb_port_adapter_delete(inst);
|
||||
}
|
||||
|
||||
bool __wrap_mb_port_ser_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength)
|
||||
{
|
||||
return mb_port_adapter_recv_data(inst, ppframe, plength);
|
||||
}
|
||||
|
||||
bool __wrap_mb_port_ser_send_data(mb_port_base_t *inst, uint8_t *pframe, uint16_t length)
|
||||
{
|
||||
return mb_port_adapter_send_data(inst, 0, pframe, length);
|
||||
}
|
||||
|
||||
void __wrap_mb_port_ser_enable(mb_port_base_t *inst)
|
||||
{
|
||||
mb_port_adapter_enable(inst);
|
||||
}
|
||||
|
||||
void __wrap_mb_port_ser_disable(mb_port_base_t *inst)
|
||||
{
|
||||
mb_port_adapter_disable(inst);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
IRAM_ATTR
|
||||
bool __wrap_mb_port_evt_get(mb_port_base_t *inst, mb_event_t *pevent)
|
||||
{
|
||||
bool result = __real_mb_port_evt_get(inst, pevent);
|
||||
ESP_LOGW(TAG, "%s, get event:%x.", inst->descr.parent_name, pevent->event);
|
||||
return result;
|
||||
}
|
||||
|
||||
IRAM_ATTR
|
||||
bool __wrap_mb_port_evt_post(mb_port_base_t *inst, mb_event_t event)
|
||||
{
|
||||
bool result = __real_mb_port_evt_post(inst, event);
|
||||
return result;
|
||||
}
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
|
||||
|
||||
// Below are the TCP port function wrappers to exchange the port layer to TCP adapter
|
||||
mb_err_enum_t __wrap_mbm_port_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t **in_out_obj)
|
||||
{
|
||||
ESP_LOGW(TAG, "master tcp adapter installed.");
|
||||
return mb_port_adapter_tcp_create(tcp_opts, in_out_obj);
|
||||
}
|
||||
|
||||
bool __wrap_mbm_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength)
|
||||
{
|
||||
return mb_port_adapter_recv_data(inst, ppframe, plength);
|
||||
}
|
||||
|
||||
bool __wrap_mbm_port_tcp_send_data(mb_port_base_t *inst, uint8_t address, uint8_t *pframe, uint16_t length)
|
||||
{
|
||||
return mb_port_adapter_send_data(inst, address, pframe, length);
|
||||
}
|
||||
|
||||
void __wrap_mbm_port_tcp_delete(mb_port_base_t *inst)
|
||||
{
|
||||
mb_port_adapter_delete(inst);
|
||||
}
|
||||
|
||||
void __wrap_mbm_port_tcp_enable(mb_port_base_t *inst)
|
||||
{
|
||||
ESP_LOGW(TAG, "adapter master tcp enable port.");
|
||||
}
|
||||
|
||||
void __wrap_mbm_port_tcp_disable(mb_port_base_t *inst)
|
||||
{
|
||||
ESP_LOGW(TAG, "adapter master tcp disable port.");
|
||||
}
|
||||
|
||||
void __wrap_mbm_port_tcp_set_conn_cb(mb_port_base_t *inst, void *conn_fp, void *arg)
|
||||
{
|
||||
ESP_LOGW(TAG, "adapter set connection callback.");
|
||||
mb_port_adapter_tcp_set_conn_cb(inst, conn_fp, arg);
|
||||
}
|
||||
|
||||
mb_uid_info_t *__wrap_mbm_port_tcp_get_slave_info(mb_port_base_t *inst, uint8_t slave_addr, mb_sock_state_t exp_state)
|
||||
{
|
||||
ESP_LOGW(TAG, "adapter get slave #%d info.", slave_addr);
|
||||
return mb_port_adapter_get_slave_info(inst, slave_addr, exp_state);
|
||||
}
|
||||
|
||||
// IRAM_ATTR
|
||||
// bool __wrap_mbm_port_timer_expired(void *inst)
|
||||
// {
|
||||
// return mb_port_adapter_tmr_expired(inst);
|
||||
// }
|
||||
|
||||
// Wrappers for modbus slave tcp
|
||||
|
||||
mb_err_enum_t __wrap_mbs_port_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t **in_out_obj)
|
||||
{
|
||||
ESP_LOGW(TAG, "install slave tcp adapter.");
|
||||
return mb_port_adapter_tcp_create(tcp_opts, in_out_obj);
|
||||
}
|
||||
|
||||
bool __wrap_mbs_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength)
|
||||
{
|
||||
return mb_port_adapter_recv_data(inst, ppframe, plength);
|
||||
}
|
||||
|
||||
bool __wrap_mbs_port_tcp_send_data(mb_port_base_t *inst, uint8_t *pframe, uint16_t length)
|
||||
{
|
||||
return mb_port_adapter_send_data(inst, 0, pframe, length);
|
||||
}
|
||||
|
||||
void __wrap_mbs_port_tcp_delete(mb_port_base_t *inst)
|
||||
{
|
||||
mb_port_adapter_delete(inst);
|
||||
}
|
||||
|
||||
void __wrap_mbs_port_tcp_enable(mb_port_base_t *inst)
|
||||
{
|
||||
ESP_LOGW(TAG, "adapter slave tcp enable port.");
|
||||
}
|
||||
|
||||
void __wrap_mbs_port_tcp_disable(mb_port_base_t *inst)
|
||||
{
|
||||
ESP_LOGW(TAG, "adapter slave tcp disable port.");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
70
test_apps/test_common/mb_utest_lib/port_stubs.h
Normal file
70
test_apps/test_common/mb_utest_lib/port_stubs.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
// Serial port function wrappers
|
||||
|
||||
bool __wrap_mb_port_evt_get(mb_port_base_t *inst, mb_event_t *pevent);
|
||||
bool __wrap_mb_port_evt_post(mb_port_base_t *inst, mb_event_t event);
|
||||
extern bool __real_mb_port_evt_get(mb_port_base_t *inst, mb_event_t *pevent);
|
||||
extern bool __real_mb_port_evt_post(mb_port_base_t *inst, mb_event_t event);
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
|
||||
|
||||
extern void __real_mb_port_ser_enable(mb_port_base_t *inst);
|
||||
extern void __real_mb_port_ser_disable(mb_port_base_t *inst);
|
||||
extern bool __real_mb_port_ser_send_data(mb_port_base_t *inst, uint8_t *p_ser_frame, uint16_t ser_length);
|
||||
extern bool __real_mb_port_ser_recv_data(mb_port_base_t *inst, uint8_t **pp_ser_frame, uint16_t *p_ser_length);
|
||||
extern void __real_mb_port_ser_delete(mb_port_base_t *inst);
|
||||
|
||||
mb_err_enum_t __wrap_mb_port_ser_create(mb_serial_opts_t *ser_opts, mb_port_base_t **in_out_obj);
|
||||
void __wrap_mb_port_ser_enable(mb_port_base_t *inst);
|
||||
void __wrap_mb_port_ser_disable(mb_port_base_t *inst);
|
||||
bool __wrap_mb_port_ser_send_data(mb_port_base_t *inst, uint8_t *p_ser_frame, uint16_t ser_length);
|
||||
bool __wrap_mb_port_ser_recv_data(mb_port_base_t *inst, uint8_t **pp_ser_frame, uint16_t *p_ser_length);
|
||||
void __wrap_mb_port_ser_delete(mb_port_base_t *inst);
|
||||
|
||||
#endif
|
||||
|
||||
// TCP port function wrappers
|
||||
|
||||
mb_err_enum_t __wrap_mbm_port_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t **in_out_obj);
|
||||
void __wrap_mbm_port_tcp_delete(mb_port_base_t *inst);
|
||||
|
||||
bool __wrap_mbm_port_tcp_send_data(mb_port_base_t *inst, uint8_t address, uint8_t *pframe, uint16_t length);
|
||||
bool __wrap_mbm_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength);
|
||||
void __wrap_mbm_port_tcp_enable(mb_port_base_t *inst);
|
||||
void __wrap_mbm_port_tcp_disable(mb_port_base_t *inst);
|
||||
void __wrap_mbm_port_tcp_set_conn_cb(mb_port_base_t *inst, void *conn_fp, void *arg);
|
||||
mb_uid_info_t *__wrap_mbm_port_tcp_get_slave_info(mb_port_base_t *inst, uint8_t uid, mb_sock_state_t exp_state);
|
||||
//bool __wrap_mbm_port_timer_expired(void *inst);
|
||||
|
||||
extern void __real_mbm_port_tcp_enable(mb_port_base_t *inst);
|
||||
extern void __real_mbm_port_tcp_disable(mb_port_base_t *inst);
|
||||
extern void __real_mbm_port_tcp_set_conn_cb(mb_port_base_t *inst, void *conn_fp, void *arg);
|
||||
extern mb_uid_info_t *__real_mbm_port_tcp_get_slave_info(mb_port_base_t *inst, uint8_t uid, mb_sock_state_t exp_state);
|
||||
extern bool __real_mbm_port_tcp_send_data(mb_port_base_t *inst, uint8_t address, uint8_t *pframe, uint16_t length);
|
||||
extern bool __real_mbm_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength);
|
||||
|
||||
mb_err_enum_t __wrap_mbs_port_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t **in_out_obj);
|
||||
void __wrap_mbs_port_tcp_delete(mb_port_base_t *inst);
|
||||
bool __wrap_mbs_port_tcp_send_data(mb_port_base_t *inst, uint8_t *pframe, uint16_t length);
|
||||
bool __wrap_mbs_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength);
|
||||
void __wrap_mbs_port_tcp_enable(mb_port_base_t *inst);
|
||||
void __wrap_mbs_port_tcp_disable(mb_port_base_t *inst);
|
||||
|
||||
mb_err_enum_t __real_mbs_port_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t **in_out_obj);
|
||||
extern void __real_mbs_port_tcp_delete(mb_port_base_t *inst);
|
||||
extern bool __real_mbs_port_tcp_send_data(mb_port_base_t *inst, uint8_t *pframe, uint16_t length);
|
||||
extern bool __real_mbs_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength);
|
||||
extern void __real_mbs_port_tcp_enable(mb_port_base_t *inst);
|
||||
extern void __real_mbs_port_tcp_disable(mb_port_base_t *inst);
|
21
test_apps/test_common/sdkconfig.defaults
Normal file
21
test_apps/test_common/sdkconfig.defaults
Normal file
@@ -0,0 +1,21 @@
|
||||
# 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=y
|
||||
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=n
|
||||
CONFIG_MB_TEST_MASTER_TASK_PRIO=4
|
||||
CONFIG_MB_TEST_SLAVE_TASK_PRIO=4
|
||||
CONFIG_MB_TEST_COMM_CYCLE_COUNTER=10
|
||||
|
527
test_apps/test_common/test_common.c
Normal file
527
test_apps/test_common/test_common.c
Normal file
@@ -0,0 +1,527 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "port_adapter.h"
|
||||
#include "mb_common.h"
|
||||
#include "mbc_slave.h"
|
||||
#include "mbc_master.h"
|
||||
|
||||
#include "test_common.h"
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef CONFIG_HEAP_TRACING
|
||||
#include "esp_heap_trace.h"
|
||||
#endif
|
||||
|
||||
#define TEST_TASK_PRIO_MASTER CONFIG_MB_TEST_MASTER_TASK_PRIO
|
||||
#define TEST_TASK_PRIO_SLAVE CONFIG_MB_TEST_SLAVE_TASK_PRIO
|
||||
#define TEST_TASK_STACK_SIZE 5120
|
||||
#define TEST_TASK_CYCLE_COUNTER CONFIG_MB_TEST_COMM_CYCLE_COUNTER
|
||||
#define TEST_BUSY_TASK_PRIO 20
|
||||
|
||||
#define TEST_REG_START_AREA0 (0x0000)
|
||||
#define TEST_READ_MASK (MB_EVENT_HOLDING_REG_RD | MB_EVENT_INPUT_REG_RD | MB_EVENT_DISCRETE_RD | MB_EVENT_COILS_RD)
|
||||
#define TEST_WRITE_MASK (MB_EVENT_HOLDING_REG_WR | MB_EVENT_COILS_WR)
|
||||
#define TEST_READ_WRITE_MASK (TEST_WRITE_MASK | TEST_READ_MASK)
|
||||
|
||||
#define TEST_BUSY_COUNT 150000
|
||||
|
||||
#define TEST_PAR_INFO_GET_TOUT (10)
|
||||
#define TEST_SEND_TIMEOUT (200 / portTICK_PERIOD_MS)
|
||||
#define TEST_TASK_START_TIMEOUT (5000 / portTICK_PERIOD_MS)
|
||||
|
||||
#define TEST_NOTIF_SEND_TOUT 100
|
||||
#define TEST_NOTIF_SIZE 20
|
||||
#define TEST_ALLOW_PROC_FAIL 2 // percentage of allowed failures
|
||||
|
||||
#define TEST_TASK_TICK_TIME (20 / portTICK_PERIOD_MS)
|
||||
|
||||
#define TAG "TEST_COMMON"
|
||||
|
||||
typedef enum {
|
||||
RT_HOLDING_RD,
|
||||
RT_HOLDING_WR
|
||||
} mb_access_t;
|
||||
|
||||
static uint16_t holding_registers[16] = {0};
|
||||
static uint16_t input_registers[8] = {0};
|
||||
static uint16_t coil_registers[10] = {0};
|
||||
|
||||
const uint16_t holding_registers_counter = (sizeof(holding_registers) / sizeof(holding_registers[0]));
|
||||
const uint16_t input_registers_counter = (sizeof(input_registers) / sizeof(input_registers[0]));
|
||||
const uint16_t coil_registers_counter = (sizeof(coil_registers) / sizeof(coil_registers[0]));
|
||||
|
||||
static QueueHandle_t tasks_done_queue = NULL;
|
||||
static int test_error_counter = 0;
|
||||
static int test_good_counter = 0;
|
||||
|
||||
static size_t before_free_8bit = 0;
|
||||
static size_t before_free_32bit = 0;
|
||||
|
||||
#ifdef CONFIG_HEAP_TRACING
|
||||
#define NUM_RECORDS 200
|
||||
static heap_trace_record_t trace_record[NUM_RECORDS];
|
||||
#endif
|
||||
|
||||
#define CHECK_PAR_VALUE(par, err, value, expected) \
|
||||
do \
|
||||
{ \
|
||||
if ((err != ESP_OK) || (((uint16_t)value) != ((uint16_t)expected))) \
|
||||
{ \
|
||||
ESP_LOGE(TAG, "CHAR #%u, value: 0x%" PRIx16 ", expected: 0x%" PRIx16 ", error = %d.", \
|
||||
(unsigned)par, ((uint16_t)value), ((uint16_t)expected), (int)err); \
|
||||
TEST_ASSERT((++test_error_counter * 100 / (TEST_TASK_CYCLE_COUNTER * CID_DEV_REG_COUNT)) <= TEST_ALLOW_PROC_FAIL); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
ESP_LOGI(TAG, "CHAR #%u, value is ok.", (unsigned)par); \
|
||||
test_good_counter++; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static void test_busy_task(void *phandle)
|
||||
{
|
||||
spinlock_t spin_lock;
|
||||
SPIN_LOCK_INIT(spin_lock);
|
||||
ESP_EARLY_LOGW(TAG, "test task");
|
||||
while(1) {
|
||||
SPIN_LOCK_ENTER(spin_lock);
|
||||
for (int i = 0; i < TEST_BUSY_COUNT; i++){
|
||||
;
|
||||
}
|
||||
SPIN_LOCK_EXIT(spin_lock);
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Start the high priority task to mimic the case when the modbus
|
||||
// tasks do not get time quota from RTOS.
|
||||
TaskHandle_t test_slave_start_busy_task()
|
||||
{
|
||||
TaskHandle_t busy_task_handle = NULL;
|
||||
TEST_ASSERT_TRUE(xTaskCreatePinnedToCore(test_busy_task, "busy_task",
|
||||
TEST_TASK_STACK_SIZE,
|
||||
NULL, TEST_BUSY_TASK_PRIO,
|
||||
&busy_task_handle, MB_PORT_TASK_AFFINITY));
|
||||
return busy_task_handle;
|
||||
}
|
||||
|
||||
void test_slave_stop_busy_task(TaskHandle_t busy_task_handle)
|
||||
{
|
||||
vTaskDelete(busy_task_handle);
|
||||
}
|
||||
|
||||
void test_common_start()
|
||||
{
|
||||
#ifdef CONFIG_HEAP_TRACING
|
||||
ESP_ERROR_CHECK( heap_trace_init_standalone(trace_record, NUM_RECORDS) );
|
||||
#endif
|
||||
|
||||
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
|
||||
#ifdef CONFIG_HEAP_TRACING
|
||||
ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
|
||||
#endif
|
||||
|
||||
tasks_done_queue = xQueueCreate(TEST_NOTIF_SIZE, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
uint32_t test_common_wait_done(TickType_t timeout_ticks)
|
||||
{
|
||||
uint32_t tmp_val = 0;
|
||||
|
||||
if (xQueueReceive(tasks_done_queue, &tmp_val, timeout_ticks) == pdTRUE) {
|
||||
return tmp_val;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t test_comon_notify_done(uint32_t value)
|
||||
{
|
||||
uint32_t tmp_val = value;
|
||||
return (uint32_t)xQueueSend(tasks_done_queue, (const void*)&tmp_val, TEST_NOTIF_SEND_TOUT);
|
||||
}
|
||||
|
||||
static uint32_t test_common_task_wait_start(TickType_t timeout_ticks)
|
||||
{
|
||||
static uint32_t notify_value = 0;
|
||||
|
||||
if (xTaskNotifyWait(0, 0, ¬ify_value, timeout_ticks) == pdTRUE) {
|
||||
ESP_LOGW(TAG, "Task: 0x%" PRIx32 ", get notify value = %d",
|
||||
(uint32_t)xTaskGetCurrentTaskHandle(), (int)notify_value);
|
||||
return pdTRUE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_common_task_notify_start(TaskHandle_t task_handle, uint32_t value)
|
||||
{
|
||||
ESP_LOGW(TAG, "Notify task 0x%" PRIx32, (uint32_t)task_handle);
|
||||
TEST_ASSERT_EQUAL_INT(xTaskNotify(task_handle, value, eSetValueWithOverwrite), pdTRUE);
|
||||
}
|
||||
|
||||
void test_common_stop()
|
||||
{
|
||||
vQueueDelete(tasks_done_queue);
|
||||
holding_registers[CID_DEV_REG_COUNT] = 0;
|
||||
test_error_counter = 0;
|
||||
test_good_counter = 0;
|
||||
|
||||
/* check if unit test has caused heap corruption in any heap */
|
||||
TEST_ASSERT_MESSAGE(heap_caps_check_integrity(MALLOC_CAP_INVALID, true), "The test has corrupted the heap");
|
||||
|
||||
#ifdef CONFIG_HEAP_TRACING
|
||||
ESP_ERROR_CHECK( heap_trace_stop() );
|
||||
heap_trace_dump();
|
||||
#endif
|
||||
|
||||
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
test_common_check_leak(before_free_8bit, after_free_8bit, "8BIT",
|
||||
CONFIG_MB_TEST_LEAK_WARN_LEVEL, CONFIG_MB_TEST_LEAK_CRITICAL_LEVEL);
|
||||
test_common_check_leak(before_free_32bit, after_free_32bit, "32BIT",
|
||||
CONFIG_MB_TEST_LEAK_WARN_LEVEL, CONFIG_MB_TEST_LEAK_CRITICAL_LEVEL);
|
||||
}
|
||||
|
||||
void test_common_check_leak(size_t before_free,
|
||||
size_t after_free,
|
||||
const char *type,
|
||||
size_t warn_threshold,
|
||||
size_t critical_threshold)
|
||||
{
|
||||
int free_delta = (int)after_free - (int)before_free;
|
||||
printf("MALLOC_CAP_%s usage: Free memory delta: %d Leak threshold: -%u \n",
|
||||
type,
|
||||
free_delta,
|
||||
critical_threshold);
|
||||
|
||||
if (free_delta > 0) {
|
||||
return; // free memory went up somehow
|
||||
}
|
||||
|
||||
size_t leaked = (size_t)(free_delta * -1);
|
||||
if (leaked <= warn_threshold) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf("MALLOC_CAP_%s %s leak: Before %u bytes free, After %u bytes free (delta %u)\n",
|
||||
type,
|
||||
leaked <= critical_threshold ? "potential" : "critical",
|
||||
before_free, after_free, leaked);
|
||||
fflush(stdout);
|
||||
TEST_ASSERT_MESSAGE(leaked <= critical_threshold, "The test leaked too much memory");
|
||||
}
|
||||
|
||||
// Helper function to read one characteristic from slave
|
||||
esp_err_t read_modbus_parameter(void *handle, uint16_t cid, uint16_t *par_data)
|
||||
{
|
||||
const mb_parameter_descriptor_t *param_descriptor = NULL;
|
||||
|
||||
esp_err_t err = mbc_master_get_cid_info(handle, cid, ¶m_descriptor);
|
||||
if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL))
|
||||
{
|
||||
uint8_t type = 0;
|
||||
err = mbc_master_get_parameter(handle, cid, (uint8_t *)par_data, &type);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
ESP_LOGI(TAG, "%p, CHAR #%u %s (%s) value = (0x%04x) parameter read successful.",
|
||||
handle,
|
||||
param_descriptor->cid,
|
||||
param_descriptor->param_key,
|
||||
param_descriptor->param_units,
|
||||
*(uint16_t *)par_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "%p, CHAR #%u (%s) read fail, err = 0x%x (%s).",
|
||||
handle,
|
||||
param_descriptor->cid,
|
||||
param_descriptor->param_key,
|
||||
(int)err,
|
||||
(char *)esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
// Helper function to write one characteristic to slave
|
||||
esp_err_t write_modbus_parameter(void *handle, uint16_t cid, uint16_t *par_data)
|
||||
{
|
||||
const mb_parameter_descriptor_t *param_descriptor = NULL;
|
||||
|
||||
esp_err_t err = mbc_master_get_cid_info(handle, cid, ¶m_descriptor);
|
||||
if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL))
|
||||
{
|
||||
uint8_t type = 0; // type of parameter from dictionary
|
||||
err = mbc_master_set_parameter(handle, cid, (uint8_t *)par_data, &type);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
ESP_LOGI(TAG, "%p, CHAR #%u %s (%s) value = (0x%04x), write successful.",
|
||||
handle,
|
||||
param_descriptor->cid,
|
||||
param_descriptor->param_key,
|
||||
param_descriptor->param_units,
|
||||
*(uint16_t *)par_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "%p, CHAR #%u (%s) write fail, err = 0x%x (%s).",
|
||||
handle,
|
||||
param_descriptor->cid,
|
||||
param_descriptor->param_key,
|
||||
(int)err,
|
||||
(char *)esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
// This is user function to read and write modbus holding registers
|
||||
static void test_master_task(void *arg)
|
||||
{
|
||||
void *mbm_handle = arg;
|
||||
//mbm_controller_iface_t *pctrl_obj = ((mbm_controller_iface_t *)mbm_handle);
|
||||
|
||||
static mb_access_t req_type = RT_HOLDING_RD;
|
||||
esp_err_t err = ESP_FAIL;
|
||||
uint16_t cycle_counter = 0;
|
||||
|
||||
// Wait task start notification during timeout
|
||||
test_common_task_wait_start(TEST_TASK_START_TIMEOUT);
|
||||
|
||||
holding_registers[CID_DEV_REG0] = TEST_REG_VAL1;
|
||||
holding_registers[CID_DEV_REG1] = TEST_REG_VAL2;
|
||||
holding_registers[CID_DEV_REG2] = TEST_REG_VAL3;
|
||||
holding_registers[CID_DEV_REG3] = TEST_REG_VAL4;
|
||||
holding_registers[CID_DEV_REG_COUNT] = cycle_counter;
|
||||
write_modbus_parameter(mbm_handle, CID_DEV_REG0, &holding_registers[CID_DEV_REG0]);
|
||||
write_modbus_parameter(mbm_handle, CID_DEV_REG1, &holding_registers[CID_DEV_REG1]);
|
||||
write_modbus_parameter(mbm_handle, CID_DEV_REG2, &holding_registers[CID_DEV_REG2]);
|
||||
write_modbus_parameter(mbm_handle, CID_DEV_REG3, &holding_registers[CID_DEV_REG3]);
|
||||
for (cycle_counter = 0; cycle_counter <= TEST_TASK_CYCLE_COUNTER; cycle_counter++)
|
||||
{
|
||||
switch (req_type)
|
||||
{
|
||||
case RT_HOLDING_RD:
|
||||
err = read_modbus_parameter(mbm_handle, CID_DEV_REG0, &holding_registers[CID_DEV_REG0]);
|
||||
CHECK_PAR_VALUE(CID_DEV_REG0, err, holding_registers[CID_DEV_REG0], TEST_REG_VAL1);
|
||||
|
||||
err = read_modbus_parameter(mbm_handle, CID_DEV_REG1, &holding_registers[CID_DEV_REG1]);
|
||||
CHECK_PAR_VALUE(CID_DEV_REG1, err, holding_registers[CID_DEV_REG1], TEST_REG_VAL2);
|
||||
|
||||
err = read_modbus_parameter(mbm_handle, CID_DEV_REG2, &holding_registers[CID_DEV_REG2]);
|
||||
CHECK_PAR_VALUE(CID_DEV_REG2, err, holding_registers[CID_DEV_REG2], TEST_REG_VAL3);
|
||||
|
||||
err = read_modbus_parameter(mbm_handle, CID_DEV_REG3, &holding_registers[CID_DEV_REG3]);
|
||||
CHECK_PAR_VALUE(CID_DEV_REG3, err, holding_registers[CID_DEV_REG3], TEST_REG_VAL4);
|
||||
req_type = RT_HOLDING_WR;
|
||||
break;
|
||||
|
||||
case RT_HOLDING_WR:
|
||||
err = write_modbus_parameter(mbm_handle, CID_DEV_REG0, &holding_registers[CID_DEV_REG0]);
|
||||
CHECK_PAR_VALUE(CID_DEV_REG0, err, holding_registers[CID_DEV_REG0], TEST_REG_VAL1);
|
||||
|
||||
err = write_modbus_parameter(mbm_handle, CID_DEV_REG1, &holding_registers[CID_DEV_REG1]);
|
||||
CHECK_PAR_VALUE(CID_DEV_REG1, err, holding_registers[CID_DEV_REG1], TEST_REG_VAL2);
|
||||
|
||||
err = write_modbus_parameter(mbm_handle, CID_DEV_REG2, &holding_registers[CID_DEV_REG2]);
|
||||
CHECK_PAR_VALUE(CID_DEV_REG2, err, holding_registers[CID_DEV_REG2], TEST_REG_VAL3);
|
||||
|
||||
err = write_modbus_parameter(mbm_handle, CID_DEV_REG3, &holding_registers[CID_DEV_REG3]);
|
||||
CHECK_PAR_VALUE(CID_DEV_REG3, err, holding_registers[CID_DEV_REG3], TEST_REG_VAL4);
|
||||
req_type = RT_HOLDING_RD;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
write_modbus_parameter(mbm_handle, CID_DEV_REG_COUNT, &cycle_counter);
|
||||
vTaskDelay(TEST_TASK_TICK_TIME); // Let the IDLE task to trigger
|
||||
if (holding_registers[CID_DEV_REG_COUNT] >= TEST_TASK_CYCLE_COUNTER) {
|
||||
ESP_LOGI(TAG, "Stop master: %p.", mbm_handle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "Destroy master, inst: %p.", mbm_handle);
|
||||
TEST_ESP_OK(mbc_master_delete(mbm_handle));
|
||||
test_comon_notify_done((uint32_t)xTaskGetCurrentTaskHandle());
|
||||
vTaskSuspend(NULL);
|
||||
}
|
||||
|
||||
static void test_slave_task(void *arg)
|
||||
{
|
||||
void *mbs_handle = arg;
|
||||
mbs_controller_iface_t *pctrl_obj = ((mbs_controller_iface_t *)mbs_handle);
|
||||
mb_param_info_t reg_info; // keeps the Modbus registers access information
|
||||
|
||||
test_common_task_wait_start(TEST_TASK_START_TIMEOUT);
|
||||
|
||||
while(1) {
|
||||
// Get parameter information from parameter queue
|
||||
esp_err_t err = mbc_slave_get_param_info(mbs_handle, ®_info, TEST_PAR_INFO_GET_TOUT);
|
||||
const char *rw_str = (reg_info.type & TEST_READ_MASK) ? "READ" : "WRITE";
|
||||
|
||||
// Filter events and process them accordingly
|
||||
if ((err != ESP_ERR_TIMEOUT) && (reg_info.type & TEST_READ_WRITE_MASK))
|
||||
{
|
||||
// Get parameter information from parameter queue
|
||||
ESP_LOGI("SLAVE", "OBJ %p, %s (%" PRIu32 " us), SL: %u, REG:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 "(0x%" PRIx16 "), SIZE:%u",
|
||||
(void *)pctrl_obj->mb_base->descr.parent,
|
||||
rw_str,
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(unsigned)pctrl_obj->opts.comm_opts.common_opts.uid,
|
||||
(unsigned)reg_info.mb_offset,
|
||||
(unsigned)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
*(uint16_t *)reg_info.address,
|
||||
(unsigned)reg_info.size);
|
||||
}
|
||||
vTaskDelay(TEST_TASK_TICK_TIME); // Let IDLE task to trigger
|
||||
if (holding_registers[CID_DEV_REG_COUNT] >= TEST_TASK_CYCLE_COUNTER) {
|
||||
ESP_LOGI(TAG, "Stop slave: %p.", mbs_handle);
|
||||
vTaskDelay(TEST_TASK_TICK_TIME); // Let master to get response from slave prior to close
|
||||
break;
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "Destroy slave, inst: %p.", mbs_handle);
|
||||
TEST_ESP_OK(mbc_slave_delete(mbs_handle));
|
||||
test_comon_notify_done((uint32_t)xTaskGetCurrentTaskHandle());
|
||||
vTaskSuspend(NULL);
|
||||
}
|
||||
|
||||
void test_slave_setup_start(void *mbs_handle)
|
||||
{
|
||||
TEST_ASSERT_TRUE(mbs_handle);
|
||||
mb_register_area_descriptor_t reg_area;
|
||||
|
||||
reg_area.type = MB_PARAM_HOLDING;
|
||||
reg_area.start_offset = TEST_REG_START_AREA0;
|
||||
reg_area.address = (void *)&holding_registers[CID_DEV_REG0];
|
||||
reg_area.size = holding_registers_counter << 1;
|
||||
TEST_ESP_OK(mbc_slave_set_descriptor(mbs_handle, reg_area));
|
||||
|
||||
reg_area.type = MB_PARAM_INPUT;
|
||||
reg_area.start_offset = TEST_REG_START_AREA0;
|
||||
reg_area.address = (void *)&input_registers[CID_DEV_REG0];
|
||||
reg_area.size = input_registers_counter << 1;
|
||||
TEST_ESP_OK(mbc_slave_set_descriptor(mbs_handle, reg_area));
|
||||
|
||||
reg_area.type = MB_PARAM_COIL;
|
||||
reg_area.start_offset = TEST_REG_START_AREA0;
|
||||
reg_area.address = (void *)&coil_registers[CID_DEV_REG0];
|
||||
reg_area.size = coil_registers_counter;
|
||||
TEST_ESP_OK(mbc_slave_set_descriptor(mbs_handle, reg_area));
|
||||
TEST_ESP_OK(mbc_slave_start(mbs_handle));
|
||||
}
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_RTU_EN || CONFIG_FMB_COMM_MODE_ASCII_EN)
|
||||
|
||||
TaskHandle_t test_master_serial_create(mb_communication_info_t *pconfig, const mb_parameter_descriptor_t *pdescr, uint16_t descr_size)
|
||||
{
|
||||
if (!pconfig || !pdescr) {
|
||||
ESP_LOGI(TAG, "invalid master configuration.");
|
||||
}
|
||||
|
||||
void *mbm_handle = NULL;
|
||||
TaskHandle_t master_task_handle = NULL;
|
||||
|
||||
TEST_ESP_OK(mbc_master_create_serial(pconfig, &mbm_handle));
|
||||
mbm_controller_iface_t *pbase = mbm_handle;
|
||||
|
||||
TEST_ESP_OK(mbc_master_set_descriptor(mbm_handle, pdescr, descr_size));
|
||||
ESP_LOGI(TAG, "%p, modbus master stack is initialized", mbm_handle);
|
||||
|
||||
TEST_ESP_OK(mbc_master_start(mbm_handle));
|
||||
ESP_LOGI(TAG, "%p, modbus master start...", mbm_handle) ;
|
||||
|
||||
char* port_name = pbase->mb_base->descr.parent_name;
|
||||
TEST_ASSERT_TRUE(xTaskCreatePinnedToCore(test_master_task, port_name,
|
||||
TEST_TASK_STACK_SIZE,
|
||||
mbm_handle, (TEST_TASK_PRIO_MASTER),
|
||||
&master_task_handle, MB_PORT_TASK_AFFINITY));
|
||||
return master_task_handle;
|
||||
}
|
||||
|
||||
TaskHandle_t test_slave_serial_create(mb_communication_info_t *pconfig)
|
||||
{
|
||||
if (!pconfig) {
|
||||
ESP_LOGI(TAG, "invalid slave configuration.");
|
||||
}
|
||||
|
||||
void *mbs_handle = NULL;
|
||||
TaskHandle_t slave_task_handle = NULL;
|
||||
|
||||
TEST_ESP_OK(mbc_slave_create_serial(pconfig, &mbs_handle));
|
||||
|
||||
mbs_controller_iface_t *pbase = mbs_handle;
|
||||
|
||||
test_slave_setup_start(mbs_handle);
|
||||
|
||||
TEST_ASSERT_TRUE(xTaskCreatePinnedToCore(test_slave_task, pbase->mb_base->descr.parent_name,
|
||||
TEST_TASK_STACK_SIZE,
|
||||
mbs_handle, (TEST_TASK_PRIO_SLAVE),
|
||||
&slave_task_handle, MB_PORT_TASK_AFFINITY));
|
||||
return slave_task_handle;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
|
||||
|
||||
TaskHandle_t test_master_tcp_create(mb_communication_info_t *pconfig, const mb_parameter_descriptor_t *pdescr, uint16_t descr_size)
|
||||
{
|
||||
if (!pconfig || !pdescr) {
|
||||
ESP_LOGI(TAG, "invalid master configuration.");
|
||||
}
|
||||
|
||||
void *mbm_handle = NULL;
|
||||
TaskHandle_t master_task_handle = NULL;
|
||||
|
||||
TEST_ESP_OK(mbc_master_create_tcp(pconfig, &mbm_handle));
|
||||
mbm_controller_iface_t *pbase = mbm_handle;
|
||||
|
||||
TEST_ESP_OK(mbc_master_set_descriptor(mbm_handle, pdescr, descr_size));
|
||||
ESP_LOGI(TAG, "%p, modbus master stack is initialized", mbm_handle);
|
||||
|
||||
TEST_ESP_OK(mbc_master_start(mbm_handle));
|
||||
ESP_LOGI(TAG, "%p, modbus master start...", mbm_handle) ;
|
||||
|
||||
char *port_name = pbase->mb_base->descr.parent_name;
|
||||
TEST_ASSERT_TRUE(xTaskCreatePinnedToCore(test_master_task, port_name,
|
||||
TEST_TASK_STACK_SIZE,
|
||||
mbm_handle, (TEST_TASK_PRIO_MASTER),
|
||||
&master_task_handle, MB_PORT_TASK_AFFINITY));
|
||||
return master_task_handle;
|
||||
}
|
||||
|
||||
TaskHandle_t test_slave_tcp_create(mb_communication_info_t *pconfig)
|
||||
{
|
||||
if (!pconfig) {
|
||||
ESP_LOGI(TAG, "invalid slave configuration.");
|
||||
}
|
||||
|
||||
void *mbs_handle = NULL;
|
||||
TaskHandle_t slave_task_handle = NULL;
|
||||
|
||||
TEST_ESP_OK(mbc_slave_create_tcp(pconfig, &mbs_handle));
|
||||
|
||||
mbs_controller_iface_t *pbase = mbs_handle;
|
||||
test_slave_setup_start(mbs_handle);
|
||||
|
||||
TEST_ASSERT_TRUE(xTaskCreatePinnedToCore(test_slave_task, pbase->mb_base->descr.parent_name,
|
||||
TEST_TASK_STACK_SIZE,
|
||||
mbs_handle, (TEST_TASK_PRIO_SLAVE),
|
||||
&slave_task_handle, MB_PORT_TASK_AFFINITY));
|
||||
return slave_task_handle;
|
||||
}
|
||||
|
||||
#endif
|
9
test_apps/unit_tests/mb_controller_common/CMakeLists.txt
Normal file
9
test_apps/unit_tests/mb_controller_common/CMakeLists.txt
Normal file
@@ -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)
|
4
test_apps/unit_tests/mb_controller_common/README.md
Normal file
4
test_apps/unit_tests/mb_controller_common/README.md
Normal file
@@ -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)
|
||||
|
||||
|
||||
|
@@ -0,0 +1,9 @@
|
||||
:cmock:
|
||||
:plugins:
|
||||
- expect
|
||||
- expect_any_args
|
||||
- return_thru_ptr
|
||||
- array
|
||||
- ignore
|
||||
- ignore_arg
|
||||
- callback
|
@@ -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
|
||||
|
17
test_apps/unit_tests/mb_controller_mapping/CMakeLists.txt
Normal file
17
test_apps/unit_tests/mb_controller_mapping/CMakeLists.txt
Normal file
@@ -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)
|
4
test_apps/unit_tests/mb_controller_mapping/README.md
Normal file
4
test_apps/unit_tests/mb_controller_mapping/README.md
Normal file
@@ -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)
|
||||
|
||||
|
||||
|
@@ -0,0 +1,9 @@
|
||||
:cmock:
|
||||
:plugins:
|
||||
- expect
|
||||
- expect_any_args
|
||||
- return_thru_ptr
|
||||
- array
|
||||
- ignore
|
||||
- ignore_arg
|
||||
- callback
|
@@ -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
|
||||
|
17
test_apps/unit_tests/mb_controller_mapping/sdkconfig.default
Normal file
17
test_apps/unit_tests/mb_controller_mapping/sdkconfig.default
Normal file
@@ -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
|
||||
|
6
test_apps/unit_tests/test_stubs/CMakeLists.txt
Normal file
6
test_apps/unit_tests/test_stubs/CMakeLists.txt
Normal file
@@ -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)
|
14
test_apps/unit_tests/test_stubs/include/mb_object_stub.h
Normal file
14
test_apps/unit_tests/test_stubs/include/mb_object_stub.h
Normal file
@@ -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);
|
46
test_apps/unit_tests/test_stubs/include/transport_common.h
Normal file
46
test_apps/unit_tests/test_stubs/include/transport_common.h
Normal file
@@ -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
|
159
test_apps/unit_tests/test_stubs/src/mb_object_stub.c
Normal file
159
test_apps/unit_tests/test_stubs/src/mb_object_stub.c
Normal file
@@ -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
|
300
test_apps/unit_tests/test_stubs/src/mb_transport_stub.c
Normal file
300
test_apps/unit_tests/test_stubs/src/mb_transport_stub.c
Normal file
@@ -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