feature modbus tcp master slave v2

This commit is contained in:
Alex Lisitsyn
2024-09-02 03:12:36 +08:00
parent d4345f8cd7
commit 2e2be467f6
109 changed files with 4454 additions and 2319 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.16)
idf_component_register(SRCS "test_common.c"
INCLUDE_DIRS "include"
REQUIRES unity)
INCLUDE_DIRS "include"
REQUIRES unity)
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components")
+49 -16
View File
@@ -5,6 +5,8 @@
*/
#include <stdio.h>
#include <string.h>
#include <sys/queue.h>
#include "unity.h"
#include "unity_test_runner.h"
@@ -43,26 +45,57 @@ enum {
TEST_REG_VAL4 = 0x4444
};
typedef struct task_entry_s {
TaskHandle_t task_handle;
SemaphoreHandle_t task_sema_handle;
void *inst_handle;
LIST_ENTRY(task_entry_s) entries;
} task_entry_t;
/**
* @brief Start, stop helpers for common test module
*
*/
void test_common_start();
void test_common_stop();
/**
* @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);
// TaskHandle_t test_common_slave_serial_create(mb_communication_info_t *pconfig);
// Common test check leak function
TaskHandle_t test_common_slave_serial_create(mb_communication_info_t *pconfig, uint32_t priority);
TaskHandle_t test_common_master_serial_create(mb_communication_info_t *pconfig,uint32_t priority, const mb_parameter_descriptor_t *pdescr, uint16_t descr_size);
TaskHandle_t test_common_slave_tcp_create(mb_communication_info_t *pconfig, uint32_t priority);
TaskHandle_t test_common_master_tcp_create(mb_communication_info_t *pconfig, uint32_t priority, const mb_parameter_descriptor_t *pdescr, uint16_t descr_size);
TaskHandle_t test_common_start_busy_task(uint32_t priority);
/**
* @brief The test helper function to check memory leak
*
*/
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();
// Slave setup register area helper
void test_common_slave_setup_start(void *mbs_handle);
// Helper function to read characteristic from slave
esp_err_t test_common_read_modbus_parameter(void *handle, uint16_t cid, uint16_t *par_data);
// Helper function to write characteristic into slave
esp_err_t test_common_write_modbus_parameter(void *handle, uint16_t cid, uint16_t *par_data);
/**
* @brief The test helper functions to work with test tasks
*
*/
void test_common_task_notify_start(TaskHandle_t task_handle, uint32_t value);
void test_common_task_start(TaskHandle_t task_handle, uint32_t value);
int test_common_task_start_all(uint32_t value);
bool test_common_task_wait_done(TaskHandle_t task_handle, TickType_t timeout_ticks);
bool test_common_task_wait_done_delete(TaskHandle_t task_handle, TickType_t task_timeout_ticks);
int test_common_task_wait_done_delete_all(TickType_t task_timeout_tick);
void test_common_task_delete(TaskHandle_t task_handle);
void test_common_task_delete_all();
void *test_common_task_get_instance(TaskHandle_t task_handle);
@@ -43,15 +43,14 @@ if(CONFIG_MB_PORT_ADAPTER_EN)
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_event_post
mb_port_event_get
mb_port_ser_create
mb_port_ser_recv_data
mb_port_ser_send_data
@@ -69,4 +68,8 @@ endforeach()
endif()
# allow multiple symbol definitions
target_link_libraries(mb_ut_lib PUBLIC "-Wl,--allow-multiple-definition")
target_link_libraries(mb_ut_lib PUBLIC "-Wl,--allow-multiple-definition")
if(CONFIG_MB_PORT_ADAPTER_EN)
set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u mb_test_include_stub_impl")
endif()
@@ -46,7 +46,7 @@ typedef struct _mb_adapter_port_entry
uint16_t recv_length;
uint64_t send_time_stamp;
uint64_t recv_time_stamp;
uint64_t test_timeout_us;
_Atomic uint64_t test_timeout_us;
uint32_t flags;
mb_uid_info_t addr_info;
QueueHandle_t rx_queue;
@@ -69,37 +69,37 @@ 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)
static bool mb_port_adapter_timer_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_timer_mode_enum_t timer_mode = mb_port_get_cur_timer_mode(&port_obj->base);
mb_port_tmr_disable(&port_obj->base);
mb_port_timer_disable(&port_obj->base);
switch (timer_mode)
{
case MB_TMODE_T35:
need_poll = mb_port_evt_post(&port_obj->base, EVENT(EV_READY));
need_poll = mb_port_event_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));
mb_port_event_set_err_type(&port_obj->base, EV_ERROR_RESPOND_TIMEOUT);
need_poll = mb_port_event_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));
need_poll = mb_port_event_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));
need_poll = mb_port_event_post(&port_obj->base, EVENT(EV_READY));
break;
}
@@ -200,7 +200,7 @@ static void mb_port_adapter_timer_cb(void *param)
// 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);
ESP_LOGD(TAG, "Send (%d bytes) from %s to %s. ", (int)sz, port_obj->base.descr.parent_name, it->base.descr.parent_name);
}
}
}
@@ -228,7 +228,7 @@ static void mb_port_adapter_conn_logic(void *inst, mb_uid_info_t *paddr_info)
&& !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",
ESP_LOGD(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");
@@ -249,7 +249,7 @@ static void mb_port_adapter_conn_logic(void *inst, mb_uid_info_t *paddr_info)
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",
ESP_LOGD(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
@@ -279,8 +279,8 @@ static void mb_port_adapter_task(void *p_args)
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);
mb_port_event_post(&it->base, EVENT(EV_FRAME_RECEIVED, frame_entry.len, NULL, 0));
ESP_LOGD(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) {
@@ -312,7 +312,7 @@ static mb_err_enum_t mb_port_adapter_connect(mb_tcp_opts_t *tcp_opts, void *pobj
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",
ESP_LOGD(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++;
@@ -337,7 +337,7 @@ static mb_err_enum_t mb_port_adapter_connect(mb_tcp_opts_t *tcp_opts, void *pobj
}
paddr_table++;
}
ESP_LOGW(TAG, "parsed and added %d slave configurations.", count);
ESP_LOGD(TAG, "parsed and added %d slave configurations.", count);
return count ? MB_ENOERR : MB_EINVAL;
}
@@ -357,7 +357,8 @@ mb_err_enum_t mb_port_adapter_create(mb_uid_info_t *paddr_info, mb_port_base_t *
.callback = mb_port_adapter_timer_cb,
.arg = padapter,
.dispatch_method = ESP_TIMER_TASK,
.name = padapter->base.descr.parent_name};
.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.");
@@ -404,7 +405,7 @@ mb_err_enum_t mb_port_adapter_create(mb_uid_info_t *paddr_info, mb_port_base_t *
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.tmr_expired = mb_port_adapter_timer_expired;
padapter->base.cb.tx_empty = NULL;
padapter->base.cb.byte_rcvd = NULL;
padapter->base.arg = (void *)padapter;
@@ -414,7 +415,7 @@ mb_err_enum_t mb_port_adapter_create(mb_uid_info_t *paddr_info, mb_port_base_t *
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);
ESP_LOGD(TAG, "created object @%p, from parent %p", padapter, padapter->base.descr.parent);
return MB_ENOERR;
error:
@@ -447,7 +448,7 @@ mb_err_enum_t mb_port_adapter_tcp_create(mb_tcp_opts_t *tcp_opts, mb_port_base_t
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);
ESP_LOGD(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;
@@ -478,7 +479,7 @@ mb_err_enum_t mb_port_adapter_ser_create(mb_serial_opts_t *ser_opts, mb_port_bas
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));
ESP_LOGD(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;
}
@@ -576,14 +577,14 @@ bool mb_port_adapter_recv_data(mb_port_base_t *inst, uint8_t **ppframe, uint16_t
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);
mb_port_timer_disable(inst);
ESP_LOGD(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);
(void *)&port_obj->rx_buffer[0], (uint16_t)length, ESP_LOG_DEBUG);
}
CRITICAL_SECTION_UNLOCK(inst->lock);
}
@@ -615,9 +616,9 @@ bool mb_port_adapter_send_data(mb_port_base_t *inst, uint8_t address, uint8_t *p
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);
(void *)pframe, length, ESP_LOG_DEBUG);
(void)mb_port_event_post(inst, EVENT(EV_FRAME_SENT));
ESP_LOGD(TAG, "%s, tx completed, flags = 0x%04x.", inst->descr.parent_name, (int)flags);
res = true;
}
+25 -24
View File
@@ -3,12 +3,10 @@
*
* 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"
@@ -24,11 +22,18 @@
#include "port_adapter.h"
#include "port_stubs.h"
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
static const char *TAG = "port_stub";
static __attribute__((unused)) const char *TAG = "port_stub";
#if (CONFIG_MB_PORT_ADAPTER_EN)
// The workaround to statically link whole test library
__attribute__((unused)) bool mb_test_include_stub_impl = true;
// Below are function wrappers to substitute actual port object with the adapter object for test purpose
@@ -66,18 +71,18 @@ void __wrap_mb_port_ser_disable(mb_port_base_t *inst)
#endif
IRAM_ATTR
bool __wrap_mb_port_evt_get(mb_port_base_t *inst, mb_event_t *pevent)
IRAM_ATTR
bool __wrap_mb_port_event_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);
bool result = __real_mb_port_event_get(inst, pevent);
ESP_LOGD(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)
IRAM_ATTR
bool __wrap_mb_port_event_post(mb_port_base_t *inst, mb_event_t event)
{
bool result = __real_mb_port_evt_post(inst, event);
bool result = __real_mb_port_event_post(inst, event);
return result;
}
@@ -86,7 +91,7 @@ bool __wrap_mb_port_evt_post(mb_port_base_t *inst, mb_event_t event)
// 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.");
ESP_LOGD(TAG, "master tcp adapter installed.");
return mb_port_adapter_tcp_create(tcp_opts, in_out_obj);
}
@@ -107,37 +112,31 @@ void __wrap_mbm_port_tcp_delete(mb_port_base_t *inst)
void __wrap_mbm_port_tcp_enable(mb_port_base_t *inst)
{
ESP_LOGW(TAG, "adapter master tcp enable port.");
ESP_LOGD(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.");
ESP_LOGD(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.");
ESP_LOGD(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);
ESP_LOGD(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.");
ESP_LOGD(TAG, "install slave tcp adapter.");
return mb_port_adapter_tcp_create(tcp_opts, in_out_obj);
}
@@ -158,16 +157,18 @@ void __wrap_mbs_port_tcp_delete(mb_port_base_t *inst)
void __wrap_mbs_port_tcp_enable(mb_port_base_t *inst)
{
ESP_LOGW(TAG, "adapter slave tcp enable port.");
ESP_LOGD(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.");
ESP_LOGD(TAG, "adapter slave tcp disable port.");
}
#endif
#endif // CONFIG_MB_PORT_ADAPTER_EN
#ifdef __cplusplus
}
#endif
@@ -6,17 +6,17 @@
#pragma once
#include <sdkconfig.h>
#include "esp_log.h"
#include "mb_common.h"
#include "mb_port_types.h"
#include "sdkconfig.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);
bool __wrap_mb_port_event_get(mb_port_base_t *inst, mb_event_t *pevent);
bool __wrap_mb_port_event_post(mb_port_base_t *inst, mb_event_t event);
extern bool __real_mb_port_event_get(mb_port_base_t *inst, mb_event_t *pevent);
extern bool __real_mb_port_event_post(mb_port_base_t *inst, mb_event_t event);
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
@@ -46,7 +46,7 @@ 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);
//bool __wrap_mbm_port__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);
+255 -97
View File
@@ -21,28 +21,27 @@
#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_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 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 (10000 / portTICK_PERIOD_MS)
#define TEST_NOTIF_SEND_TOUT (400 / portTICK_PERIOD_MS)
#define TEST_NOTIF_SIZE (20)
#define TEST_ALLOW_PROC_FAIL (5) // percentage of allowed failures due to desynchronization
#define TEST_TASK_TICK_TIME (50 / portTICK_PERIOD_MS)
#define TAG "TEST_COMMON"
@@ -59,15 +58,15 @@ const uint16_t holding_registers_counter = (sizeof(holding_registers) / sizeof(h
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;
// Heap memory leak traicing
#ifdef CONFIG_HEAP_TRACING
#define NUM_RECORDS 200
#define NUM_RECORDS 500
static heap_trace_record_t trace_record[NUM_RECORDS];
#endif
@@ -87,6 +86,82 @@ static heap_trace_record_t trace_record[NUM_RECORDS];
} \
} while (0)
// The linked list of test tasks instances
LIST_HEAD(task_entry, task_entry_s) s_task_list;
static portMUX_TYPE s_list_spinlock = portMUX_INITIALIZER_UNLOCKED;
static void task_entry_remove(task_entry_t *task_entry)
{
portENTER_CRITICAL(&s_list_spinlock);
LIST_REMOVE(task_entry, entries);
portEXIT_CRITICAL(&s_list_spinlock);
ESP_LOGD(TAG, "Delete task 0x%" PRIx32, (uint32_t)task_entry->task_handle);
vTaskDelete(task_entry->task_handle);
vSemaphoreDelete(task_entry->task_sema_handle);
free(task_entry);
}
static bool task_wait_done_and_remove(task_entry_t *task_entry, TickType_t tout_ticks)
{
bool is_done = false;
if (task_entry && task_entry->task_handle && task_entry->task_sema_handle) {
if ((xSemaphoreTake(task_entry->task_sema_handle, tout_ticks) == pdTRUE)) {
ESP_LOGI(TAG, "Test task 0x%" PRIx32 ", done successfully.", (uint32_t)task_entry->task_handle);
is_done = true;
} else {
ESP_LOGE(TAG, "Could not complete task 0x%" PRIx32 " after timeout, force kill the task.",
(uint32_t)task_entry->task_handle);
is_done = false;
}
vTaskDelay(1); // Let the lower priority task to suspend or delete itself
task_entry_remove(task_entry);
}
return (is_done);
}
static void test_task_add_entry(TaskHandle_t task_handle, void *pinst)
{
TEST_ASSERT_TRUE(task_handle);
task_entry_t *new_entry = (task_entry_t*) calloc(1, sizeof(task_entry_t));
TEST_ASSERT_TRUE(new_entry);
portENTER_CRITICAL(&s_list_spinlock);
new_entry->task_handle = task_handle;
new_entry->task_sema_handle = xSemaphoreCreateBinary();
new_entry->inst_handle = pinst;
LIST_INSERT_HEAD(&s_task_list, new_entry, entries);
portEXIT_CRITICAL(&s_list_spinlock);
xSemaphoreTake(new_entry->task_sema_handle, 1);
}
static task_entry_t *test_task_find_entry(TaskHandle_t task_handle)
{
TEST_ASSERT_NOT_NULL(task_handle);
task_entry_t *it, *pfound = NULL;
if (LIST_EMPTY(&s_task_list)) {
return NULL;
}
portENTER_CRITICAL(&s_list_spinlock);
LIST_FOREACH(it, &s_task_list, entries) {
if (it->task_handle == task_handle) {
pfound = it;
break;
}
}
portEXIT_CRITICAL(&s_list_spinlock);
return pfound;
}
static void test_common_task_notify_done(TaskHandle_t task_handle)
{
task_entry_t *it = test_task_find_entry(task_handle);
if (it) {
xSemaphoreGive(it->task_sema_handle);
}
}
static void test_busy_task(void *phandle)
{
spinlock_t spin_lock;
@@ -102,21 +177,95 @@ static void test_busy_task(void *phandle)
}
}
// 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()
void test_common_task_start(TaskHandle_t task_handle, uint32_t value)
{
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;
// Directly notify the task waiting to start loop
test_common_task_notify_start(task_handle, value);
}
void test_slave_stop_busy_task(TaskHandle_t busy_task_handle)
int test_common_task_start_all(uint32_t value)
{
vTaskDelete(busy_task_handle);
task_entry_t *it = NULL;
if (LIST_EMPTY(&s_task_list)) {
return 0;
}
int task_count = 0;
LIST_FOREACH(it, &s_task_list, entries) {
test_common_task_notify_start(it->task_handle, value);
task_count++;
}
return task_count;
}
bool test_common_task_wait_done(TaskHandle_t task_handle, TickType_t timeout_ticks)
{
task_entry_t *it = test_task_find_entry(task_handle);
if (it && (xSemaphoreTake(it->task_sema_handle, timeout_ticks) == pdTRUE)) {
return true;
}
return false;
}
bool test_common_task_wait_done_delete(TaskHandle_t task_handle, TickType_t task_timeout_ticks)
{
task_entry_t *it = test_task_find_entry(task_handle);
return task_wait_done_and_remove(it, task_timeout_ticks);
}
int test_common_task_wait_done_delete_all(TickType_t task_timeout_tick)
{
task_entry_t *it, *ptmp = NULL;
int task_count = 0;
if (LIST_EMPTY(&s_task_list)) {
return 0;
}
LIST_FOREACH_SAFE(it, &s_task_list, entries, ptmp) {
task_wait_done_and_remove(it, task_timeout_tick);
task_count++;
}
return task_count;
}
void test_common_task_delete(TaskHandle_t task_handle)
{
task_entry_t *it = test_task_find_entry(task_handle);
if (it) {
task_entry_remove(it);
}
}
void test_common_task_delete_all()
{
task_entry_t *it = NULL;
while ((it = LIST_FIRST(&s_task_list))) {
task_entry_remove(it);
}
}
void *test_common_task_get_instance(TaskHandle_t task_handle)
{
task_entry_t *it = test_task_find_entry(task_handle);
if (it) {
return it->inst_handle;
}
return NULL;
}
// Start the high priority task to mimic the case when the modbus
// tasks do not get time quota from RTOS.
TaskHandle_t test_common_start_busy_task(uint32_t priority)
{
TaskHandle_t busy_task_handle = NULL;
if (!priority) {
priority = TEST_BUSY_TASK_PRIO;
}
TEST_ASSERT_TRUE(xTaskCreatePinnedToCore(test_busy_task, "busy_task",
TEST_TASK_STACK_SIZE,
NULL, priority,
&busy_task_handle, MB_PORT_TASK_AFFINITY));
test_task_add_entry(busy_task_handle, NULL);
return busy_task_handle;
}
void test_common_start()
@@ -131,47 +280,12 @@ void test_common_start()
#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);
LIST_INIT(&s_task_list);
}
void test_common_stop()
{
vQueueDelete(tasks_done_queue);
//vQueueDelete(tasks_done_queue);
holding_registers[CID_DEV_REG_COUNT] = 0;
test_error_counter = 0;
test_good_counter = 0;
@@ -183,7 +297,6 @@ void test_common_stop()
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",
@@ -192,11 +305,29 @@ void test_common_stop()
CONFIG_MB_TEST_LEAK_WARN_LEVEL, CONFIG_MB_TEST_LEAK_CRITICAL_LEVEL);
}
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_LOGD(TAG, "Task: 0x%" PRIx32 ", get notify value = %u",
(uint32_t)xTaskGetCurrentTaskHandle(), (unsigned)notify_value);
return pdTRUE;
}
return 0;
}
void test_common_task_notify_start(TaskHandle_t task_handle, uint32_t value)
{
ESP_LOGD(TAG, "Notify task start 0x%" PRIx32, (uint32_t)task_handle);
TEST_ASSERT_EQUAL_INT(xTaskNotify(task_handle, value, eSetValueWithOverwrite), pdTRUE);
}
void test_common_check_leak(size_t before_free,
size_t after_free,
const char *type,
size_t warn_threshold,
size_t critical_threshold)
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",
@@ -222,7 +353,7 @@ void test_common_check_leak(size_t before_free,
}
// Helper function to read one characteristic from slave
esp_err_t read_modbus_parameter(void *handle, uint16_t cid, uint16_t *par_data)
esp_err_t test_common_read_modbus_parameter(void *handle, uint16_t cid, uint16_t *par_data)
{
const mb_parameter_descriptor_t *param_descriptor = NULL;
@@ -312,16 +443,16 @@ static void test_master_task(void *arg)
switch (req_type)
{
case RT_HOLDING_RD:
err = read_modbus_parameter(mbm_handle, CID_DEV_REG0, &holding_registers[CID_DEV_REG0]);
err = test_common_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]);
err = test_common_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]);
err = test_common_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]);
err = test_common_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;
@@ -344,16 +475,17 @@ static void test_master_task(void *arg)
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;
} else {
write_modbus_parameter(mbm_handle, CID_DEV_REG_COUNT, &cycle_counter);
vTaskDelay(TEST_TASK_TICK_TIME); // Let the IDLE task to trigger
}
}
ESP_LOGI(TAG, "Destroy master, inst: %p.", mbm_handle);
TEST_ESP_OK(mbc_master_delete(mbm_handle));
test_comon_notify_done((uint32_t)xTaskGetCurrentTaskHandle());
test_common_task_notify_done(xTaskGetCurrentTaskHandle());
vTaskSuspend(NULL);
}
@@ -387,18 +519,20 @@ static void test_slave_task(void *arg)
}
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);
ESP_LOGD(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());
ESP_LOGD(TAG, "Notify task done, inst: %p.", xTaskGetCurrentTaskHandle());
test_common_task_notify_done(xTaskGetCurrentTaskHandle());
vTaskDelay(10);
vTaskSuspend(NULL);
}
void test_slave_setup_start(void *mbs_handle)
void test_common_slave_setup_start(void *mbs_handle)
{
TEST_ASSERT_TRUE(mbs_handle);
mb_register_area_descriptor_t reg_area;
@@ -425,7 +559,10 @@ void test_slave_setup_start(void *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)
TaskHandle_t test_common_master_serial_create(mb_communication_info_t *pconfig,
uint32_t priority,
const mb_parameter_descriptor_t *pdescr,
uint16_t descr_size)
{
if (!pconfig || !pdescr) {
ESP_LOGI(TAG, "invalid master configuration.");
@@ -442,16 +579,21 @@ TaskHandle_t test_master_serial_create(mb_communication_info_t *pconfig, const m
TEST_ESP_OK(mbc_master_start(mbm_handle));
ESP_LOGI(TAG, "%p, modbus master start...", mbm_handle) ;
if (priority) {
priority = TEST_TASK_PRIO_MASTER;
}
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),
mbm_handle, priority,
&master_task_handle, MB_PORT_TASK_AFFINITY));
test_task_add_entry(master_task_handle, mbm_handle);
return master_task_handle;
}
TaskHandle_t test_slave_serial_create(mb_communication_info_t *pconfig)
TaskHandle_t test_common_slave_serial_create(mb_communication_info_t *pconfig, uint32_t priority)
{
if (!pconfig) {
ESP_LOGI(TAG, "invalid slave configuration.");
@@ -464,12 +606,17 @@ TaskHandle_t test_slave_serial_create(mb_communication_info_t *pconfig)
mbs_controller_iface_t *pbase = mbs_handle;
test_slave_setup_start(mbs_handle);
test_common_slave_setup_start(mbs_handle);
if (priority) {
priority = TEST_TASK_PRIO_SLAVE;
}
TEST_ASSERT_TRUE(xTaskCreatePinnedToCore(test_slave_task, pbase->mb_base->descr.parent_name,
TEST_TASK_STACK_SIZE,
mbs_handle, (TEST_TASK_PRIO_SLAVE),
mbs_handle, priority,
&slave_task_handle, MB_PORT_TASK_AFFINITY));
test_task_add_entry(slave_task_handle, mbs_handle);
return slave_task_handle;
}
@@ -477,7 +624,7 @@ TaskHandle_t test_slave_serial_create(mb_communication_info_t *pconfig)
#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)
TaskHandle_t test_common_master_tcp_create(mb_communication_info_t *pconfig, uint32_t priority, const mb_parameter_descriptor_t *pdescr, uint16_t descr_size)
{
if (!pconfig || !pdescr) {
ESP_LOGI(TAG, "invalid master configuration.");
@@ -495,15 +642,21 @@ TaskHandle_t test_master_tcp_create(mb_communication_info_t *pconfig, const mb_p
TEST_ESP_OK(mbc_master_start(mbm_handle));
ESP_LOGI(TAG, "%p, modbus master start...", mbm_handle) ;
if (priority) {
priority = TEST_TASK_PRIO_MASTER;
}
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),
mbm_handle, priority,
&master_task_handle, MB_PORT_TASK_AFFINITY));
test_task_add_entry(master_task_handle, mbm_handle);
return master_task_handle;
}
TaskHandle_t test_slave_tcp_create(mb_communication_info_t *pconfig)
TaskHandle_t test_common_slave_tcp_create(mb_communication_info_t *pconfig, uint32_t priority)
{
if (!pconfig) {
ESP_LOGI(TAG, "invalid slave configuration.");
@@ -515,12 +668,17 @@ TaskHandle_t test_slave_tcp_create(mb_communication_info_t *pconfig)
TEST_ESP_OK(mbc_slave_create_tcp(pconfig, &mbs_handle));
mbs_controller_iface_t *pbase = mbs_handle;
test_slave_setup_start(mbs_handle);
test_common_slave_setup_start(mbs_handle);
if (priority) {
priority = TEST_TASK_PRIO_SLAVE;
}
TEST_ASSERT_TRUE(xTaskCreatePinnedToCore(test_slave_task, pbase->mb_base->descr.parent_name,
TEST_TASK_STACK_SIZE,
mbs_handle, (TEST_TASK_PRIO_SLAVE),
mbs_handle, priority,
&slave_task_handle, MB_PORT_TASK_AFFINITY));
test_task_add_entry(slave_task_handle, mbs_handle);
return slave_task_handle;
}