Merge branch 'feature/add_mb_console_helper' into 'main'

feat: add initial common mb console helper for examples and tests

See merge request idf/esp-modbus!163
This commit is contained in:
Alex Lisitsyn
2026-03-30 08:54:13 +01:00
28 changed files with 869 additions and 164 deletions
@@ -0,0 +1,8 @@
idf_component_register(SRCS "mb_console.c"
INCLUDE_DIRS "include"
REQUIRES console
LDFRAGMENTS linker.lf)
if(CONFIG_MB_CONSOLE_CMD_AUTO_REGISTRATION)
target_link_libraries(${COMPONENT_LIB} PRIVATE "-u mb_console_cmd_mb_register")
endif()
@@ -0,0 +1,16 @@
menu "Modbus Console Helper"
config MB_CONSOLE_HELPER_ENABLED
bool "Enable console helper in examples"
default y
help
Enable the helper component to track and send commands
via the console (UART/USB).
config MB_CONSOLE_CMD_AUTO_REGISTRATION
bool "Enable Console command Modbus Auto-registration"
default y
help
Enabling this allows for the autoregistration of the Modbus command.
endmenu
@@ -0,0 +1,3 @@
dependencies:
idf:
version: ">=5.0"
@@ -0,0 +1,82 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "sdkconfig.h"
#define MB_CMD_NO_EVENTS 0
#define MB_CMD_START BIT0
#define MB_CMD_STOP BIT1
#define MB_CMD_CONFIG_END BIT2
#define MB_CMD_MAX_CFG_COUNT 50
#if __cplusplus
extern "C" {
#endif
/* This structure describes the plugin to the rest of the application */
typedef struct {
/* A pointer to the name of the command */
const char *name;
/* A function which performs auto-registration of console commands */
esp_err_t (*plugin_regd_fn)(void);
} console_cmd_plugin_desc_t;
/**
* @brief Initialize the console helper component
*
* @param callback Function to call when a command is processed
* @return
* - ESP_OK - initialization is completed, otherwise reports the error code.
*/
esp_err_t mb_console_init();
/**
* @brief Check for console input message during timeout
*
* @param event console event corresponded to command
* @param tout_ms timeout in milliseconds to wait for the event
*
* @return event bits that were set, or MB_CMD_NO_EVENTS if timeout occurred without receiving the event
*/
int mb_console_event_check(int event, uint32_t tout_ms);
#if CONFIG_MB_CONSOLE_HELPER_ENABLED
/**
* @brief Registers the mb command.
*
* @return
* - ESP_OK - command registration is completed, otherwise reports the error code.
*/
esp_err_t mb_console_cmd_mb_register(void);
/**
* @brief Add legacy configuration registration function
* @param config_table - pointer to the configuration table, which is an array of strings with NULL terminator.
* The console helper will update the entries in this table with the values received from the console.
* @return
* - ESP_OK - command registration is completed, otherwise reports the error code.
*/
esp_err_t mb_console_register_configs(char **config_table);
/**
* @brief Destroy modbus console
*
* @return
* - ESP_OK - destroy is completed successfully, , otherwise reports the error code.
*/
esp_err_t mb_console_destroy(void);
#else
#error "Console helper is disabled."
#endif
#if __cplusplus
}
#endif
+13
View File
@@ -0,0 +1,13 @@
[sections:console_cmd_desc]
entries:
.console_cmd_desc
[scheme:console_cmd_desc_default]
entries:
console_cmd_desc -> flash_rodata
[mapping:console_cmd_desc]
archive: *
entries:
* (console_cmd_desc_default);
console_cmd_desc -> flash_rodata KEEP() SORT(name) SURROUND(console_cmd_array)
+451
View File
@@ -0,0 +1,451 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/semphr.h"
#include "esp_console.h"
#include "esp_log.h"
#include "argtable3/argtable3.h"
#include "mb_console.h"
#if CONFIG_MB_CONSOLE_HELPER_ENABLED
static const char *TAG = "mb_console";
/* FreeRTOS event group to command received */
static EventGroupHandle_t s_mb_event_group;
// Mutex to protect configuration table updates
static SemaphoreHandle_t s_config_table_lock;
#if CONFIG_MB_CONSOLE_CMD_AUTO_REGISTRATION
static char **s_config_table = NULL;
static esp_console_repl_t *repl = NULL;
static struct {
struct arg_str *config_str;
struct arg_end *end;
} add_config_args;
// Supports simple Modbus command arguments for now
static struct {
struct arg_str *command;
struct arg_str *instance;
struct arg_end *end;
} mb_args;
esp_err_t mb_console_cmd_mb_register(void);
/**
* Static registration of this plugin is achieved by defining the plugin description
* structure and placing it into .console_cmd_desc section.
* The name of the section and its placement is determined by linker.lf file in 'plugins' component.
*/
static const console_cmd_plugin_desc_t __attribute__((section(".console_cmd_desc"), used)) PLUGIN = {
.name = "console_cmd_mb",
.plugin_regd_fn = &mb_console_cmd_mb_register
};
#endif
static int do_mb_cmd(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&mb_args);
if (nerrors != 0) {
arg_print_errors(stderr, mb_args.end, argv[0]);
return 1;
}
const char *inst = mb_args.instance->sval[0];
if (strcmp(inst, "instances") == 0 ||
strcmp(inst, "masters") == 0 ||
strcmp(inst, "slaves") == 0) {
if (strcmp(mb_args.command->sval[0], "start") == 0) {
ESP_LOGI(TAG, "Start modbus %s.", mb_args.instance->sval[0]);
xEventGroupSetBits(s_mb_event_group, MB_CMD_START);
return 0;
} else if (strcmp(mb_args.command->sval[0], "stop") == 0) {
ESP_LOGI(TAG, "Stop modbus %s.", mb_args.instance->sval[0]);
xEventGroupSetBits(s_mb_event_group, MB_CMD_STOP);
return 0;
}
}
return 1;
}
static char *console_cmd_scan_config(int *index, uint16_t *port_ptr, const char *buffer)
{
if (!buffer || !index) {
return NULL;
}
char *ip_str = NULL;
int a[8] = {0};
int buf_cnt = 0;
uint16_t port_val = 0;
#if !CONFIG_EXAMPLE_CONNECT_IPV6
buf_cnt = sscanf(buffer, "%d=%d.%d.%d.%d;%" PRIu16, index, &a[0], &a[1], &a[2], &a[3], &port_val);
if (buf_cnt == 6) {
if (-1 == asprintf(&ip_str, "%02x;%d.%d.%d.%d;%" PRIu16, (int)(*index + 1), a[0], a[1], a[2], a[3], port_val)) {
abort();
}
} else if (buf_cnt == 5) {
if (-1 == asprintf(&ip_str, "%02x;%d.%d.%d.%d", (int)(*index + 1), a[0], a[1], a[2], a[3])) {
abort();
}
} else {
return NULL;
}
#else
buf_cnt = sscanf(buffer, "%d=%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x;%" PRIu16, index, &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6], &a[7], &port_val);
if (buf_cnt == 9) {
if (-1 == asprintf(&ip_str, "%02x;%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x;%" PRIu16, (int)(*index + 1), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], port_val)) {
abort();
}
} else if (buf_cnt == 8) {
if (-1 == asprintf(&ip_str, "%02x;%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x", (int)(*index + 1), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7])) {
abort();
}
} else {
return NULL;
}
#endif
if (port_ptr) {
*port_ptr = port_val;
}
printf("IP string: %s\r\n", ip_str);
return ip_str;
}
static int console_cmd_check_table(char **config_table, int *free_slot_cnt_ptr, int *first_free_slot_ptr)
{
if (!config_table || !config_table[0]) {
ESP_LOGE(TAG, "Configuration table is not correctly initialized.");
return -1;
}
int cnt = 0;
int free_slot_cnt = 0;
int first_free_slot = -1;
for (cnt = 0; cnt < MB_CMD_MAX_CFG_COUNT && config_table[cnt]; ++cnt) {
if (strcmp("FROM_STDIN", config_table[cnt]) == 0) {
free_slot_cnt++;
first_free_slot = first_free_slot < 0 ? cnt : first_free_slot;
}
}
if (cnt == MB_CMD_MAX_CFG_COUNT && config_table[cnt]) {
ESP_LOGE(TAG, "Configuration table is not terminated correctly: %d", cnt);
return -1;
}
if (free_slot_cnt_ptr) {
*free_slot_cnt_ptr = free_slot_cnt;
}
if (first_free_slot_ptr) {
*first_free_slot_ptr = first_free_slot;
}
ESP_LOGI(TAG, "Configuration table length: %d, free slots: %d", cnt, free_slot_cnt);
return cnt;
}
static int do_add_config(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&add_config_args);
if (nerrors != 0) {
arg_print_errors(stderr, add_config_args.end, argv[0]);
return 1;
}
if (!s_config_table) {
ESP_LOGE(TAG, "Configuration table is not set.");
return 1;
}
if (!s_config_table_lock) {
ESP_LOGE(TAG, "Configuration table mutex is not initialized.");
return 1;
}
int cnt = 0;
int free_slot_cnt = 0;
int first_free_slot = -1;
int ret = 1; // default to error
if (xSemaphoreTake(s_config_table_lock, portMAX_DELAY) != pdTRUE) {
ESP_LOGE(TAG, "Failed to lock configuration table mutex.");
return 1;
}
do {
cnt = console_cmd_check_table(s_config_table, &free_slot_cnt, &first_free_slot);
if (cnt <= 0) {
ESP_LOGE(TAG, "Configuration table is not valid.");
break;
}
int index = 0;
char *config_str = console_cmd_scan_config(&index, NULL, add_config_args.config_str->sval[0]);
if (!config_str) {
ESP_LOGE(TAG, "Incorrect config string: %s", add_config_args.config_str->sval[0]);
break;
}
ESP_LOGI(TAG, "Config table index %d(%s), free slot: %d, cnt: %d, free_slots_cnt: %d", index, config_str, first_free_slot, cnt, free_slot_cnt);
if (index >= cnt) {
ESP_LOGE(TAG, "Incorrect config IP index: %d > %d", index, cnt);
free (config_str);
break;
}
// Allocate and store the IP string
if (s_config_table[index] &&
(strcmp("FROM_STDIN", s_config_table[index]) == 0) &&
(index < MB_CMD_MAX_CFG_COUNT) &&
(index < cnt)
) {
s_config_table[index] = config_str;
} else {
ESP_LOGI(TAG, "Leave IP(%d) = [%s] set manually.", index, s_config_table[index]);
free (config_str);
break;
}
ESP_LOGI(TAG, "Config[%d] set to %s", index, s_config_table[index]);
if (first_free_slot + 1 == cnt) {
ESP_LOGI(TAG, "All %d configs are set.", cnt);
xEventGroupSetBits(s_mb_event_group, MB_CMD_CONFIG_END);
} else {
ESP_LOGI(TAG, "Waiting IP(%d) from stdin:", first_free_slot + 1);
}
ret = 0;
} while (0);
xSemaphoreGive(s_config_table_lock);
return ret;
}
// Registers the basic modbus command for start and stop events
esp_err_t mb_console_cmd_mb_register(void)
{
esp_err_t ret;
// Support for just simple commands for now
mb_args.command = arg_str1(NULL, NULL, "<Command>", "Command (start, stop).");
mb_args.instance = arg_str1(NULL, NULL, "<Instances>", "Instance types (instances, slaves, masters, id)");
mb_args.end = arg_end(2);
const esp_console_cmd_t mb_cmd = {
.command = "mb",
.help = "Send simple modbus action command",
.hint = NULL,
.func = &do_mb_cmd,
.argtable = &mb_args
};
ret = esp_console_cmd_register(&mb_cmd);
if (ret) {
ESP_LOGE(TAG, "Unable to register modbus command");
}
return ret;
}
esp_err_t mb_console_register_configs(char **config_table)
{
if (!config_table) {
ESP_LOGE(TAG, "Incorrect configuration table.");
return ESP_ERR_INVALID_ARG;
}
if (!s_config_table_lock) {
ESP_LOGE(TAG, "Configuration table mutex is not initialized.");
return ESP_ERR_INVALID_STATE;
}
int free_slot_cnt = 0;
int first_free_slot = -1;
if (xSemaphoreTake(s_config_table_lock, portMAX_DELAY) != pdTRUE) {
ESP_LOGE(TAG, "Failed to lock configuration table mutex.");
return ESP_ERR_INVALID_STATE;
}
int cnt = console_cmd_check_table(config_table, &free_slot_cnt, &first_free_slot);
if (cnt <= 0 || first_free_slot < 0) {
ESP_LOGE(TAG, "Configuration table is not valid.");
xSemaphoreGive(s_config_table_lock);
return ESP_ERR_INVALID_ARG;
}
s_config_table = config_table;
xSemaphoreGive(s_config_table_lock);
add_config_args.config_str = arg_str1(NULL, NULL, "<config>", "IP config (e.g. \"01=192.168.1.5;1502)\"");
add_config_args.end = arg_end(1);
// Use the command similar to legacy string
const esp_console_cmd_t cmd = {
.command = "IP",
.help = "Register configuration",
.hint = NULL,
.func = &do_add_config,
.argtable = &add_config_args
};
esp_err_t ret = esp_console_cmd_register(&cmd);
if (ret) {
ESP_LOGE(TAG, "Unable to register %s", cmd.command);
return ret;
}
ESP_LOGI(TAG, "Waiting IP(%d) from stdin:", first_free_slot);
return ESP_OK;
}
esp_err_t mb_console_init()
{
#if CONFIG_MB_CONSOLE_HELPER_ENABLED
ESP_LOGI(TAG, "Initialize console helper.");
if (s_config_table_lock) {
ESP_LOGE(TAG, "Failed to init command console. Already installed?");
return ESP_ERR_INVALID_STATE;
}
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
esp_err_t ret = ESP_FAIL;
// install console REPL environment
#if defined(CONFIG_ESP_CONSOLE_UART_DEFAULT) || defined(CONFIG_ESP_CONSOLE_UART_CUSTOM)
esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
ret = esp_console_new_repl_uart(&hw_config, &repl_config, &repl);
#elif defined(CONFIG_ESP_CONSOLE_USB_CDC)
esp_console_dev_usb_cdc_config_t hw_config = ESP_CONSOLE_DEV_CDC_CONFIG_DEFAULT();
ret = esp_console_new_repl_usb_cdc(&hw_config, &repl_config, &repl);
#elif defined(CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG)
esp_console_dev_usb_serial_jtag_config_t hw_config = ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT();
ret = esp_console_new_repl_usb_serial_jtag(&hw_config, &repl_config, &repl);
#else
#error Unsupported console type
#endif
if (ret) {
ESP_LOGE(TAG, "Failed to init repl: %s", esp_err_to_name(ret));
return ret;
}
extern const console_cmd_plugin_desc_t _console_cmd_array_start;
extern const console_cmd_plugin_desc_t _console_cmd_array_end;
ESP_LOGI(TAG, "List of Console commands:\n");
for (const console_cmd_plugin_desc_t *it = &_console_cmd_array_start; it != &_console_cmd_array_end; ++it) {
ESP_LOGI(TAG, "- Command '%s', function plugin_regd_fn=%p\n", it->name, it->plugin_regd_fn);
if (it->plugin_regd_fn != NULL) {
ret = (it->plugin_regd_fn)();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to register console commands: %s", esp_err_to_name(ret));
}
}
}
s_mb_event_group = xEventGroupCreate();
if (!s_mb_event_group) {
ESP_LOGE(TAG, "Failed to create event group.");
return ESP_ERR_NO_MEM;
}
s_config_table_lock = xSemaphoreCreateMutex();
if (!s_config_table_lock) {
ESP_LOGE(TAG, "Failed to create configuration table mutex.");
vEventGroupDelete(s_mb_event_group);
return ESP_ERR_NO_MEM;
}
ret = esp_console_start_repl(repl);
if (ret) {
ESP_LOGE(TAG, "Failed to start console commands: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "Console helper initialized with config table protection.");
#endif
return ESP_OK;
}
int mb_console_event_check(int event, uint32_t tout_ms)
{
if (!event || !tout_ms) {
return MB_CMD_NO_EVENTS;
}
if (!s_mb_event_group) {
ESP_LOGW(TAG, "Console helper is not initialized, cannot check for commands.");
return MB_CMD_NO_EVENTS;
}
EventBits_t event_mask = event;
EventBits_t event_bits = MB_CMD_NO_EVENTS;
#if CONFIG_MB_CONSOLE_HELPER_ENABLED
event_bits = xEventGroupWaitBits(s_mb_event_group,
event_mask ? event_mask : MB_CMD_STOP | MB_CMD_START,
pdTRUE, // Clear bits before returning
pdFALSE,
pdMS_TO_TICKS(tout_ms));
#endif
return (int) event_bits;
}
esp_err_t mb_console_destroy(void)
{
esp_err_t err = ESP_ERR_NOT_SUPPORTED;
#if CONFIG_MB_CONSOLE_HELPER_ENABLED
ESP_LOGI(TAG, "Destroying console helper...");
if (xSemaphoreTake(s_config_table_lock, pdMS_TO_TICKS(1000)) == pdTRUE) {
xSemaphoreGive(s_config_table_lock);
}
if (s_config_table) {
for (int i = 0; s_config_table[i]; ++i) {
if (strcmp("FROM_STDIN", s_config_table[i]) != 0) {
free(s_config_table[i]);
}
s_config_table[i] = NULL;
}
}
if (s_config_table_lock) {
vSemaphoreDelete(s_config_table_lock);
s_config_table_lock = NULL;
}
if (s_mb_event_group) {
vEventGroupDelete(s_mb_event_group);
s_mb_event_group = NULL;
}
// It is enough to call repl destructor, esp_console_deinit() call is performed from there
if (repl && repl->del) {
err = repl->del(repl);
if (err) {
ESP_LOGE(TAG, "Failed to stop repl: %s", esp_err_to_name(err));
}
}
repl = NULL;
ESP_LOGI(TAG, "Console helper destroyed.");
#endif
return err;
}
#endif
+3 -3
View File
@@ -410,7 +410,7 @@ class ModbusTestDut(IdfDut):
if self.dut_list is not None:
for dut_instance in self.dut_list:
self.logger.info("Sending destroy message to others DUT Instances")
dut_instance.write("Destroy instances\n")
dut_instance.write("mb stop instances\n")
return None
def get_avg_response_time_master(self) -> int:
@@ -433,7 +433,7 @@ class ModbusTestDut(IdfDut):
def send_message_destroy_dut(self) -> None:
"""The function sends message to end caller DUT"""
self.logger.info("Sending destroy message to this DUT")
self.write("Destroy instances\n")
self.write("mb stop instances\n")
return None
def add_request_response(
@@ -504,7 +504,7 @@ class ModbusTestDut(IdfDut):
)
if isinstance(slave_ip, str):
for addr_num in range(0, self.TEST_MAX_CIDS):
message: str = r"IP{}={}".format(addr_num, slave_ip)
message: str = r"IP {}={}".format(addr_num, slave_ip)
if isinstance(port, str) or isinstance(port, int):
message += r";{}".format(str(port))
message += r"\r\n"
@@ -10,6 +10,7 @@ menu "Modbus TCP Example Configuration"
bool "Resolve Modbus slave addresses using mDNS service."
config MB_SLAVE_IP_FROM_STDIN
select MB_CONSOLE_HELPER_ENABLED
bool "Configure Modbus slave addresses from stdin"
endchoice
@@ -8,5 +8,7 @@ dependencies:
version: "^1"
mb_example_common:
path: "../../../mb_example_common"
mb_console_helper:
path: "../../../../components/mb_console_helper"
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
+35 -94
View File
@@ -24,8 +24,9 @@
#include "mbcontroller.h"
#include "sdkconfig.h"
#define MB_TCP_PORT (CONFIG_FMB_TCP_PORT_DEFAULT) // TCP port used by example
#include "mb_console.h"
#define MB_TCP_PORT (CONFIG_FMB_TCP_PORT_DEFAULT) // TCP port used by example
// The number of parameters that intended to be used in the particular control process
#define MASTER_MAX_CIDS num_device_parameters
@@ -85,6 +86,8 @@
#define MB_MDNS_INSTANCE(pref) pref"mb_master_tcp"
#define MB_CUST_DATA_LEN 100 // The length of custom command buffer
#define MB_CMD_CONFIGURATION_TOUT_MS 120000
#define MB_CMD_CHECK_TOUT_MS 50
static const char *TAG = "MASTER_TEST";
@@ -92,7 +95,7 @@ static const char *TAG = "MASTER_TEST";
// Each address in the table is a index of TCP slave ip address in mb_communication_info_t::tcp_ip_addr table
enum {
MB_DEVICE_ADDR1 = 1, // Slave UID = 1
//MB_DEVICE_ADDR1,
MB_DEVICE_ADDR2,
//MB_DEVICE_ADDR3,
MB_DEVICE_COUNT = 2
};
@@ -149,7 +152,7 @@ const mb_parameter_descriptor_t device_parameters[] = {
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
},
{
CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR2, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_data0), TEST_HOLD_REG_SIZE(holding_data0),
HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_HUMI_MIN, TEST_HUMI_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER
@@ -317,14 +320,12 @@ const size_t ip_table_sz;
char *slave_ip_address_table[MB_DEVICE_COUNT + 1] = {
#if CONFIG_MB_SLAVE_IP_FROM_STDIN
"FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR1 and set to predefined value by user
//"FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR2 and set to predefined value by user
//"FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR3 and set to predefined value by user
"FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR2 and set to predefined value by user
NULL // End of table condition (must be included)
#elif CONFIG_MB_MDNS_IP_RESOLVER
// This is workaround for the test to use the same slave for all CIDs and ignore UID setting in the slave
"01;mb_slave_tcp_01;1502",
// "02;mb_slave_tcp_01;502",
//"03;mb_slave_tcp_01;1502",
"02;mb_slave_tcp_01;502",
NULL // End of table condition (must be included)
#endif
};
@@ -332,83 +333,6 @@ char *slave_ip_address_table[MB_DEVICE_COUNT + 1] = {
const size_t ip_table_sz = (size_t)(sizeof(slave_ip_address_table) / sizeof(slave_ip_address_table[0]));
static char my_custom_data[MB_CUST_DATA_LEN] = {0}; // custom data buffer to handle slave response
#if CONFIG_MB_SLAVE_IP_FROM_STDIN
// Scan IP address according to IPV settings
char *master_scan_addr(int *index, char *buffer)
{
char *ip_str = NULL;
int a[8] = {0};
int buf_cnt = 0;
#if !CONFIG_EXAMPLE_CONNECT_IPV6
buf_cnt = sscanf(buffer, "IP%d=" IPSTR, index, &a[0], &a[1], &a[2], &a[3]);
if (buf_cnt == 5) {
if (-1 == asprintf(&ip_str, "%02x;" IPSTR, (int)(*index + 1), a[0], a[1], a[2], a[3])) {
abort();
}
}
#else
buf_cnt = sscanf(buffer, "IP%d="IPV6STR, index, &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6], &a[7]);
if (buf_cnt == 9) {
if (-1 == asprintf(&ip_str, "%02x;" IPV6STR, (int)(*index + 1), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7])) {
abort();
}
}
#endif
printf("IP string: %s", ip_str);
return ip_str;
}
static int master_get_slave_ip_stdin(char **addr_table)
{
char buf[128];
int index;
char *ip_str = NULL;
int buf_cnt = 0;
int ip_cnt = 0;
if (!addr_table) {
return 0;
}
ESP_ERROR_CHECK(example_configure_stdin_stdout());
while (1) {
if (addr_table[ip_cnt] && strcmp(addr_table[ip_cnt], "FROM_STDIN") == 0) {
printf("Waiting IP%d from stdin:\r\n", (int)ip_cnt);
while (fgets(buf, sizeof(buf), stdin) == NULL) {
fputs(buf, stdout);
}
buf_cnt = strlen(buf);
buf[buf_cnt - 1] = '\0';
fputc('\n', stdout);
ip_str = master_scan_addr(&index, buf);
if (ip_str != NULL) {
ESP_LOGI(TAG, "IP(%d) = [%s] set from stdin.", (int)ip_cnt, ip_str);
if ((ip_cnt >= ip_table_sz) || (index != ip_cnt)) {
addr_table[ip_cnt] = NULL;
break;
}
addr_table[ip_cnt++] = ip_str;
} else {
// End of configuration
addr_table[ip_cnt++] = NULL;
break;
}
} else {
if (addr_table[ip_cnt]) {
ESP_LOGI(TAG, "Leave IP(%d) = [%s] set manually.", (int)ip_cnt, addr_table[ip_cnt]);
ip_cnt++;
} else {
ESP_LOGI(TAG, "IP(%d) is not set in the table.", (int)ip_cnt);
break;
}
}
}
return ip_cnt;
}
#endif
static void master_destroy_slave_list(char **table, size_t ip_table_size)
{
for (int i = 0; ((i < ip_table_size) && table[i] != NULL); i++) {
@@ -463,11 +387,11 @@ static void *master_get_param_data(const mb_parameter_descriptor_t *param_descri
if (err == ESP_OK) { \
bool is_correct = true; \
if (pdescr->param_opts.opt3) { \
for EACH_ITEM(pinst, pdescr->param_size / sizeof(*item_ptr)) { \
if (*item_ptr != (typeof(*(pinst)))pdescr->param_opts.opt3) { \
for EACH_ITEM(pinst, pdescr->param_size / sizeof(*item_ptr)) { \
if (*item_ptr != (typeof(*(pinst)))pdescr->param_opts.opt3) { \
*item_ptr = (typeof(*(pinst)))pdescr->param_opts.opt3; \
ESP_LOGD(TAG, "%p Characteristic #%d (%s), initialize to 0x%" PRIx16 ".", \
master_handle, \
ESP_LOGD(TAG, "%p Characteristic #%d (%s), initialize to 0x%" PRIx16 ".", \
master_handle, \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(uint16_t)pdescr->param_opts.opt3); \
@@ -515,6 +439,11 @@ static void master_operation_func(void *arg)
bool alarm_state = false;
const mb_parameter_descriptor_t *param_descriptor = NULL;
// Wait for start instances command
if (!mb_console_event_check(MB_CMD_START, MB_CMD_CHECK_TOUT_MS)) {
ESP_LOGE(TAG, "Start TCP master after timeout.");
}
ESP_LOGI(TAG, "Master TCP is started.");
char *pcustom_string = "Master";
@@ -657,6 +586,10 @@ static void master_operation_func(void *arg)
}
}
vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls
if (mb_console_event_check(MB_CMD_STOP, MB_CMD_CHECK_TOUT_MS)) {
ESP_LOGI(TAG, "Intentionally stop modbus test...");
break;
}
}
}
vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS);
@@ -710,13 +643,22 @@ static esp_err_t init_services(mb_tcp_addr_type_t ip_addr_type)
#endif
#if CONFIG_MB_SLAVE_IP_FROM_STDIN
int ip_cnt = master_get_slave_ip_stdin(slave_ip_address_table);
if (ip_cnt) {
ESP_LOGI(TAG, "Configured %d IP address.", ip_cnt);
} else {
ESP_LOGE(TAG, "Fail to get IP address from stdin. Continue.");
#if CONFIG_MB_CONSOLE_HELPER_ENABLED
mb_console_init();
result = mb_console_register_configs(slave_ip_address_table);
MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"Could not init CONFIG mode, returns(0x%x).",
(int)result);
ESP_LOGI(TAG, "System initialized in CONFIG mode.");
ESP_LOGI(TAG, "Usage example: IP 0=192.168.1.5;1502 -> then: mb start instances");
if (!mb_console_event_check(MB_CMD_CONFIG_END, MB_CMD_CONFIGURATION_TOUT_MS)) {
ESP_LOGE(TAG, "Configuration timeout reached.");
return ESP_ERR_NOT_FOUND;
}
#else
#error "The MB_CONSOLE_HELPER_ENABLED is required for setting configs from STDIN."
#endif
#endif
return ESP_OK;
}
@@ -845,7 +787,6 @@ void app_main(void)
.tcp_opts.response_tout_ms = CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND,
.tcp_opts.ip_netif_ptr = (void *)get_example_netif()
};
ESP_ERROR_CHECK(master_init(&tcp_master_config));
master_operation_func(NULL);
@@ -21,3 +21,4 @@ CONFIG_EXAMPLE_WIFI_SSID="${CI_WIFI_SSID}"
CONFIG_EXAMPLE_WIFI_PASSWORD="${CI_WIFI_PASSW}"
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=n
CONFIG_LOG_MAXIMUM_LEVEL_DEBUG=y
CONFIG_MB_CONSOLE_HELPER_ENABLED=y
@@ -7,5 +7,7 @@ dependencies:
version: "^1.0.0"
mb_example_common:
path: "../../../mb_example_common"
mb_console_helper:
path: "../../../../components/mb_console_helper"
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
+70 -55
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2016-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -28,7 +28,10 @@
#include "mbcontroller.h" // for mbcontroller defines and api
#include "modbus_params.h" // for modbus parameters structures
#include "mb_console.h"
#define MB_TCP_PORT_NUMBER (CONFIG_FMB_TCP_PORT_DEFAULT)
#define MB_CMD_CHECK_TOUT_MS (50)
// Defines below are used to define register start address for each type of Modbus registers
#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) >> 1))
@@ -59,7 +62,6 @@
#define MB_SLAVE_ADDR (CONFIG_MB_SLAVE_ADDR)
#define MB_CUST_DATA_MAX_LEN (100)
static const char *TAG = "SLAVE_TEST";
static void *slave_handle = NULL;
@@ -141,66 +143,79 @@ static void setup_reg_data(void)
static void slave_operation_func(void *arg)
{
mb_param_info_t reg_info; // keeps the Modbus registers access information
mb_param_info_t reg_info = {0}; // keeps the Modbus registers access information
esp_err_t err = ESP_ERR_TIMEOUT;
// Check start event
mb_console_event_check(MB_CMD_START, MB_CMD_CHECK_TOUT_MS);
ESP_LOGI(TAG, "Slave TCP is started");
ESP_LOGI(TAG, "Start modbus test...");
// The cycle below will be terminated when parameter holding_data0
// incremented each access cycle reaches the CHAN_DATA_MAX_VAL value.
for (; holding_reg_params.holding_data0 < MB_CHAN_DATA_MAX_VAL;) {
// Check for read/write events of Modbus master for certain events
(void)mbc_slave_check_event(slave_handle, MB_READ_WRITE_MASK); // checks every type of event from specific slave , parameter queue
ESP_ERROR_CHECK_WITHOUT_ABORT(mbc_slave_get_param_info(slave_handle, &reg_info, MB_PAR_INFO_GET_TOUT)); // get latest info from parameter queue
const char *rw_str = (reg_info.type & MB_READ_MASK) ? "READ" : "WRITE"; //only checks read mask, assumes write if not
// Filter events and process them accordingly
if (reg_info.type & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
// Get parameter information from parameter queue
ESP_LOGI(TAG, "OBJ %p, HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
slave_handle,
rw_str,
(unsigned)reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(int)reg_info.address,
(unsigned)reg_info.size);
if (reg_info.address == (uint8_t *)&holding_reg_params.holding_data0) {
(void)mbc_slave_lock(slave_handle);
holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET;
if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - MB_CHAN_DATA_OFFSET)) {
coil_reg_params.coils_port1 = 0xFF;
ESP_LOGI(TAG, "Riched maximum value");
}
(void)mbc_slave_unlock(slave_handle);
}
} else if (reg_info.type & MB_EVENT_INPUT_REG_RD) {
ESP_LOGI(TAG, "OBJ %p, INPUT READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
slave_handle,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
} else if (reg_info.type & MB_EVENT_DISCRETE_RD) {
ESP_LOGI(TAG, "OBJ %p, DISCRETE READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
slave_handle,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
} else if (reg_info.type & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) {
ESP_LOGI(TAG, "OBJ %p, COILS %s (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
slave_handle,
rw_str,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
if (coil_reg_params.coils_port1 == 0xFF) {
ESP_LOGI(TAG, "Stop polling.");
// Check for read/write events of Modbus master for certain event
err = mbc_slave_get_param_info(slave_handle, &reg_info, MB_PAR_INFO_GET_TOUT); // get latest info from parameter queue
if (err == ESP_ERR_TIMEOUT) {
if (mb_console_event_check(MB_CMD_STOP, MB_CMD_CHECK_TOUT_MS)) {
ESP_LOGI(TAG, "Intentionally stop modbus test...");
break;
}
reg_info.type = MB_EVENT_NO_EVENTS;
}
if ((err != ESP_ERR_TIMEOUT) && (reg_info.type & MB_READ_WRITE_MASK)) {
const char *rw_str = (reg_info.type & MB_READ_MASK) ? "READ" : "WRITE"; // only checks read mask, assumes write if not
// Filter events and process them accordingly
if (reg_info.type & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
// Get parameter information from parameter queue
ESP_LOGI(TAG, "OBJ %p, HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
slave_handle,
rw_str,
(unsigned)reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(int)reg_info.address,
(unsigned)reg_info.size);
if (reg_info.address == (uint8_t *)&holding_reg_params.holding_data0) {
(void)mbc_slave_lock(slave_handle);
holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET;
if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - MB_CHAN_DATA_OFFSET)) {
coil_reg_params.coils_port1 = 0xFF;
ESP_LOGI(TAG, "Riched maximum value");
}
(void)mbc_slave_unlock(slave_handle);
}
} else if (reg_info.type & MB_EVENT_INPUT_REG_RD) {
ESP_LOGI(TAG, "OBJ %p, INPUT READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
slave_handle,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
} else if (reg_info.type & MB_EVENT_DISCRETE_RD) {
ESP_LOGI(TAG, "OBJ %p, DISCRETE READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
slave_handle,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
} else if (reg_info.type & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) {
ESP_LOGI(TAG, "OBJ %p, COILS %s (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
slave_handle,
rw_str,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
if (coil_reg_params.coils_port1 == 0xFF) {
ESP_LOGI(TAG, "Stop polling.");
break;
}
}
}
}
// Destroy of Modbus controller on alarm
@@ -215,6 +230,7 @@ static esp_err_t init_services(void)
ESP_ERROR_CHECK(nvs_flash_erase());
result = nvs_flash_init();
}
mb_console_init();
MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"nvs_flash_init fail, returns(0x%x).",
@@ -460,7 +476,6 @@ void app_main(void)
.tcp_opts.uid = MB_SLAVE_ADDR
};
ESP_ERROR_CHECK(slave_init(&tcp_slave_config_1));
ESP_LOGI(TAG, "Slave TCP #1 is started (%s)", __func__);
@@ -11,7 +11,7 @@ CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_FMB_TCP_UID_ENABLED=y
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_MB_SLAVE_ADDR=1
CONFIG_EXAMPLE_CONNECT_IPV6=n
+1 -1
View File
@@ -8,7 +8,7 @@ CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_TCP_UID_ENABLED=y
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_MB_SLAVE_ADDR=1
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
+1 -1
View File
@@ -10,7 +10,7 @@ CONFIG_FMB_COMM_MODE_RTU_EN=n
CONFIG_FMB_COMM_MODE_ASCII_EN=n
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=2000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_TCP_UID_ENABLED=y
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_MB_SLAVE_ADDR=1
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
+2 -2
View File
@@ -202,9 +202,9 @@ def test_modbus_tcp_communication(
@pytest.mark.parametrize("target", ["esp32"], indirect=True)
@pytest.mark.multi_dut_modbus_generic
@pytest.mark.parametrize("config", ["dummy_config"])
def test_modbus_tcp_generic(config) -> None:
def test_modbus_tcp_generic(config: str) -> None:
logger.info("The generic tcp example tests are not provided yet.")
if __name__ == "__main__":
pytest.main(["pytest_mb_tcp_instances.py"])
pytest.main(["pytest_mb_tcp_master_slave.py"])
@@ -0,0 +1,9 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.22)
set(EXCLUDE_COMPONENTS freemodbus)
list(APPEND EXTRA_COMPONENT_DIRS "../../components/mb_console_helper")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(modbus_console_helper_test)
@@ -0,0 +1,10 @@
set(PROJECT_NAME "modbus_console_helper_test")
idf_component_register(SRCS "mb_console_app.c"
REQUIRES mb_console_helper console)
# Workaround to avoid static analysis false positives for some components.
if(CONFIG_FMB_COMPILER_STATIC_ANALYZER_ENABLE AND CMAKE_C_COMPILER_ID STREQUAL "GNU")
target_compile_options(${COMPONENT_LIB} PRIVATE "-fanalyzer")
message(STATUS "Static analyzer build for ${PROJECT_NAME}.")
endif()
@@ -0,0 +1,80 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_console.h"
#include "mb_console.h"
const char *TAG = "console_helper_test";
#if !CONFIG_MB_CONSOLE_HELPER_ENABLED
#error "The MB_CONSOLE_HELPER_ENABLED option must be enabled for this test app."
#endif
// Simple config table: one entry waiting from stdin, NULL-terminated
#define APP_CFG_COUNT 3
#define APP_CFG_TIMEOUT_MS 500
char *app_config_table[APP_CFG_COUNT + 1] = { NULL };
void app_main(void)
{
ESP_LOGI(TAG, "Console helper test app starting.");
// Prepare the config table entries; "FROM_STDIN" will be replaced by the helper on configuration.
for (int i = 0; i < APP_CFG_COUNT; ++i) {
app_config_table[i] = "FROM_STDIN";
}
app_config_table[APP_CFG_COUNT] = NULL; // terminator of the table
// Init console helper and register config table.
esp_err_t err = mb_console_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "mb_console_init failed: %s", esp_err_to_name(err));
} else {
ESP_LOGI(TAG, "mb_console_init OK");
}
err = mb_console_register_configs(app_config_table);
if (err != ESP_OK) {
ESP_LOGE(TAG, "mb_console_register_configs failed: %s", esp_err_to_name(err));
}
for (;;) {
int ev = mb_console_event_check(MB_CMD_CONFIG_END | MB_CMD_START | MB_CMD_STOP, APP_CFG_TIMEOUT_MS);
if (ev & MB_CMD_CONFIG_END) {
/* print out configured addresses */
for (int i = 0; i < APP_CFG_COUNT; ++i) {
if (app_config_table[i]) {
ESP_LOGI(TAG, "Config[%d] set to %s\n", i, app_config_table[i]);
} else {
ESP_LOGI(TAG, "ConfigTable[%d]=NULL", i);
}
}
}
if (ev & MB_CMD_START) {
ESP_LOGI(TAG, "Start modbus instances.");
}
if (ev & MB_CMD_STOP) {
ESP_LOGI(TAG, "Stop modbus instances.");
break;
}
vTaskDelay(pdMS_TO_TICKS(APP_CFG_TIMEOUT_MS));
}
err = mb_console_destroy();
if (err == ESP_OK) {
ESP_LOGI(TAG, "Console helper destroyed.");
} else {
ESP_LOGE(TAG, "Console helper destroy failed.");
}
}
@@ -0,0 +1,4 @@
CONFIG_MB_CONSOLE_HELPER_ENABLED=y
CONFIG_MB_CONSOLE_CMD_AUTO_REGISTRATION=y
CONFIG_ESP_CONSOLE_UART_NUM=0
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
@@ -0,0 +1,39 @@
# SPDX-FileCopyrightText: 2016-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded import Dut
MB_APP_WAIT_TOUT_SEC = 120
@pytest.mark.parametrize("target", ["esp32"], indirect=True)
@pytest.mark.multi_dut_modbus_generic
def test_mb_console_helper_flow(
dut: Dut,
) -> None:
# Wait for the helper to request IP from stdin (the register function prints this)
dut.expect(r"Waiting IP\([0-9]{1,2}\) from stdin:", timeout=10)
# Send multiple config strings matching app_config_table entries (indices 0,1,2)
dut.write("IP 00=192.168.1.5;1502\n")
dut.write("IP 01=10.0.0.3;1502\n")
dut.write("IP 02=172.16.0.10;502\n")
# After sending, helper/app should confirm the configured table entries.
# The test app prints lines like: "Config[0] set to <string>"
dut.expect(r"Config\[0\] set to .*192\.168\.1\.5.*1502", timeout=5)
dut.expect(r"Config\[1\] set to .*10\.0\.0\.3.*1502", timeout=5)
dut.expect(r"Config\[2\] set to .*172\.16\.0\.10.*502", timeout=5)
# Trigger start command and ensure app logs the Start event
dut.write("mb start instances\n")
dut.expect("Start modbus instances.", timeout=5)
# Trigger stop command and ensure app logs the Stop event
dut.write("mb stop instances\n")
dut.expect("Stop modbus instances.", timeout=5)
dut.expect("Destroying console helper...", timeout=5)
dut.write("\r\n") # release repl correctly
dut.expect("Console helper destroyed.", timeout=10)
+3
View File
@@ -5,6 +5,9 @@ include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(EXTRA_COMPONENT_DIRS "../test_common")
# Include console command helper component
list(APPEND EXTRA_COMPONENT_DIRS "../../components/mb_console_helper")
# The workaround for the test_utils under ESP-IDF v6.0
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.5")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/test_apps/components")
@@ -8,5 +8,7 @@ dependencies:
version: "^1.0.0"
mb_example_common:
path: "../../../../tools/mb_example_common"
mb_console_helper:
path: "../../../../components/mb_console_helper"
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
@@ -7,5 +7,7 @@ dependencies:
version: "^1.0.0"
mb_example_common:
path: "../../../../tools/mb_example_common"
mb_console_helper:
path: "../../../../components/mb_console_helper"
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
+4 -1
View File
@@ -1,10 +1,13 @@
cmake_minimum_required(VERSION 3.22)
idf_component_register(SRCS "test_common.c"
INCLUDE_DIRS "include"
REQUIRES unity esp_timer)
REQUIRES unity esp_timer mb_console_helper)
set(EXTRA_COMPONENT_DIRS)
# Include console command helper component
list(APPEND EXTRA_COMPONENT_DIRS "../../components/mb_console_helper")
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.5")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/test_apps/components")
else()
@@ -5,6 +5,7 @@
*/
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <sys/queue.h>
#include "unity.h"
+23 -6
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -22,6 +22,10 @@
#include "esp_heap_trace.h"
#endif
#if CONFIG_MB_CONSOLE_HELPER_ENABLED
#include "mb_console.h"
#endif
#define TEST_TASK_CYCLE_COUNTER (CONFIG_MB_TEST_COMM_CYCLE_COUNTER)
#define TEST_BUSY_TASK_PRIO (20)
@@ -44,7 +48,7 @@
#define TEST_NOTIFY_DONE_TOUT (200 / portTICK_PERIOD_MS)
#define TAG "TEST_COMMON"
#define MSG_DESTROY "Destroy instances\n\0"
#define MSG_DESTROY "mb stop instances\n\0"
typedef enum {
RT_HOLDING_RD,
@@ -171,8 +175,15 @@ void test_common_task_notify_stop_all()
bool test_common_wait_check_destroy_message(char *message, uint32_t timeout_ms)
{
bool result = false;
/* Read line from console, non-blocking function, timeout in ms */
#if CONFIG_MB_CONSOLE_HELPER_ENABLED
if (mb_console_event_check(MB_CMD_STOP, MB_PAR_INFO_TOUT) == MB_CMD_STOP) {
ESP_LOGD(TAG, "Destroy message matched, notifying to destroy instances.");
result = true;
}
#else
// If console helper is not enabled, use standard input to read the message.
char buffer[64] = {0}; //fixed size buffer to store the input from stdin
ESP_LOGD(TAG, "Waiting for destroy message: \"%s\" (timeout: %lu ms)", message, timeout_ms);
@@ -184,10 +195,12 @@ bool test_common_wait_check_destroy_message(char *message, uint32_t timeout_ms)
if (strcmp(buffer, message) == 0) {
ESP_LOGD(TAG, "Destroy message matched, notifying to destroy instances.");
return true;
result = true;
} else {
ESP_LOGD(TAG, "Timeout waiting for destroy message.");
}
ESP_LOGD(TAG, "Timeout waiting for destroy message.");
return false;
#endif
return result;
}
void test_common_task_notify_done(TaskHandle_t task_handle)
@@ -332,6 +345,10 @@ void test_common_start()
ESP_ERROR_CHECK( heap_trace_init_standalone(trace_record, NUM_RECORDS) );
#endif
#if CONFIG_MB_CONSOLE_HELPER_ENABLED
mb_console_init();
#endif
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);