add test applications and unit tests

This commit is contained in:
aleks
2023-12-10 15:22:41 +01:00
parent 581573f6d4
commit c343e08600
70 changed files with 4436 additions and 2 deletions

View File

@@ -191,12 +191,12 @@ void app_main(void)
(unsigned)reg_info.size); (unsigned)reg_info.size);
if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0) 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; holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET;
if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - 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; 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) { } 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", ESP_LOGI(TAG, "INPUT READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",

View 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
View File

@@ -0,0 +1 @@
/__pycache__/

View 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)

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

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

View 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

View File

@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,6 @@
dependencies:
idf: ">=4.3"
espressif/esp-modbus:
version: "^2.0"
override_path: "../../../"

View 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);
}

View 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
}

View 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()

View 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

View 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

View 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
View 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
View File

@@ -0,0 +1,2 @@
/pytest_embedded_log/
/__pycache__/

View 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)

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

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

View 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

View File

@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,6 @@
dependencies:
idf: ">=4.3"
espressif/esp-modbus:
version: "^2.0"
override_path: "../../../"

View 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();
}

View File

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

View 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)

View 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

View 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

View 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)

View File

@@ -0,0 +1,3 @@
# Test common
This directory contains component that is common for Modbus master and slave tests.

View 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);

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

View 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

View 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);

View 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

View 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);

View 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

View 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, &notify_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, &param_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, &param_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, &reg_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

View 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)

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

View File

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

View File

@@ -0,0 +1,9 @@
:cmock:
:plugins:
- expect
- expect_any_args
- return_thru_ptr
- array
- ignore
- ignore_arg
- callback

View File

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

View File

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

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

View File

@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,6 @@
dependencies:
idf: ">=4.3"
espressif/esp-modbus:
version: "^2.0"
override_path: "../../../../"

View 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(unit_test_controller);
}
void app_main(void)
{
UNITY_MAIN_FUNC(run_all_tests);
}

View File

@@ -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, &param_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);
}

View File

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

View 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

View 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)

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

View File

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

View File

@@ -0,0 +1,9 @@
:cmock:
:plugins:
- expect
- expect_any_args
- return_thru_ptr
- array
- ignore
- ignore_arg
- callback

View File

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

View File

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

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

View File

@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,6 @@
dependencies:
idf: ">=4.3"
espressif/esp-modbus:
version: "^2.0"
override_path: "../../../../"

View File

@@ -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);
}

View File

@@ -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, &param_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, &param_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, &param_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 = &reg_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);
}

View File

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

View File

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

View 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

View 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)

View 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);

View 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

View 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

View 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