add test applications and unit tests

This commit is contained in:
aleks committed 2023-12-10 15:50:28 +01:00
1 parent 581573f6d4
commit c343e08600
70 files changed
+4436 -2

No files matched your search

+9
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)
+3
View File
@@ -0,0 +1,3 @@
# Test common
This directory contains component that is common for Modbus master and slave tests.
@@ -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);
@@ -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")
@@ -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
@@ -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);
@@ -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
@@ -0,0 +1,70 @@
/*
* SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <sdkconfig.h>
#include "esp_log.h"
#include "mb_common.h"
#include "mb_port_types.h"
// Serial port function wrappers
bool __wrap_mb_port_evt_get(mb_port_base_t *inst, mb_event_t *pevent);
bool __wrap_mb_port_evt_post(mb_port_base_t *inst, mb_event_t event);
extern bool __real_mb_port_evt_get(mb_port_base_t *inst, mb_event_t *pevent);
extern bool __real_mb_port_evt_post(mb_port_base_t *inst, mb_event_t event);
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
extern void __real_mb_port_ser_enable(mb_port_base_t *inst);
extern void __real_mb_port_ser_disable(mb_port_base_t *inst);
extern bool __real_mb_port_ser_send_data(mb_port_base_t *inst, uint8_t *p_ser_frame, uint16_t ser_length);
extern bool __real_mb_port_ser_recv_data(mb_port_base_t *inst, uint8_t **pp_ser_frame, uint16_t *p_ser_length);
extern void __real_mb_port_ser_delete(mb_port_base_t *inst);
mb_err_enum_t __wrap_mb_port_ser_create(mb_serial_opts_t *ser_opts, mb_port_base_t **in_out_obj);
void __wrap_mb_port_ser_enable(mb_port_base_t *inst);
void __wrap_mb_port_ser_disable(mb_port_base_t *inst);
bool __wrap_mb_port_ser_send_data(mb_port_base_t *inst, uint8_t *p_ser_frame, uint16_t ser_length);
bool __wrap_mb_port_ser_recv_data(mb_port_base_t *inst, uint8_t **pp_ser_frame, uint16_t *p_ser_length);
void __wrap_mb_port_ser_delete(mb_port_base_t *inst);
#endif
// TCP port function wrappers
mb_err_enum_t __wrap_mbm_port_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t **in_out_obj);
void __wrap_mbm_port_tcp_delete(mb_port_base_t *inst);
bool __wrap_mbm_port_tcp_send_data(mb_port_base_t *inst, uint8_t address, uint8_t *pframe, uint16_t length);
bool __wrap_mbm_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength);
void __wrap_mbm_port_tcp_enable(mb_port_base_t *inst);
void __wrap_mbm_port_tcp_disable(mb_port_base_t *inst);
void __wrap_mbm_port_tcp_set_conn_cb(mb_port_base_t *inst, void *conn_fp, void *arg);
mb_uid_info_t *__wrap_mbm_port_tcp_get_slave_info(mb_port_base_t *inst, uint8_t uid, mb_sock_state_t exp_state);
//bool __wrap_mbm_port_timer_expired(void *inst);
extern void __real_mbm_port_tcp_enable(mb_port_base_t *inst);
extern void __real_mbm_port_tcp_disable(mb_port_base_t *inst);
extern void __real_mbm_port_tcp_set_conn_cb(mb_port_base_t *inst, void *conn_fp, void *arg);
extern mb_uid_info_t *__real_mbm_port_tcp_get_slave_info(mb_port_base_t *inst, uint8_t uid, mb_sock_state_t exp_state);
extern bool __real_mbm_port_tcp_send_data(mb_port_base_t *inst, uint8_t address, uint8_t *pframe, uint16_t length);
extern bool __real_mbm_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength);
mb_err_enum_t __wrap_mbs_port_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t **in_out_obj);
void __wrap_mbs_port_tcp_delete(mb_port_base_t *inst);
bool __wrap_mbs_port_tcp_send_data(mb_port_base_t *inst, uint8_t *pframe, uint16_t length);
bool __wrap_mbs_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength);
void __wrap_mbs_port_tcp_enable(mb_port_base_t *inst);
void __wrap_mbs_port_tcp_disable(mb_port_base_t *inst);
mb_err_enum_t __real_mbs_port_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t **in_out_obj);
extern void __real_mbs_port_tcp_delete(mb_port_base_t *inst);
extern bool __real_mbs_port_tcp_send_data(mb_port_base_t *inst, uint8_t *pframe, uint16_t length);
extern bool __real_mbs_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t *plength);
extern void __real_mbs_port_tcp_enable(mb_port_base_t *inst);
extern void __real_mbs_port_tcp_disable(mb_port_base_t *inst);
+21
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
+527
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