mirror of
https://github.com/espressif/esp-modbus.git
synced 2026-08-03 20:24:09 +02:00
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:
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ®_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, ®_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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user