Modbus RTU - Multi Devices
This commit is contained in:
931
main/main.c
Normal file
931
main/main.c
Normal file
@@ -0,0 +1,931 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "driver/uart.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_spiffs.h"
|
||||
|
||||
#include "config_store.h"
|
||||
#include "modbus_memory.h"
|
||||
#include "modbus_points.h"
|
||||
#include "modbus_rtu.h"
|
||||
|
||||
#define DEVICE_CONFIG_PATH "/spiffs/device_config.ini"
|
||||
#define TAG "MODBUS_MAIN"
|
||||
#define MODBUS_RX_BUF_SIZE MODBUS_RTU_MAX_ADU_LEN
|
||||
#define MODBUS_TX_BUF_SIZE MODBUS_RTU_MAX_ADU_LEN
|
||||
|
||||
/* Manual DE polarity:
|
||||
* First try active-high:
|
||||
* idle receive = 0
|
||||
* transmit = 1
|
||||
*
|
||||
* If this still does not work, flip these two values:
|
||||
* MANUAL_DE_TX_LEVEL 0
|
||||
* MANUAL_DE_RX_LEVEL 1
|
||||
*/
|
||||
#define MANUAL_DE_TX_LEVEL 1
|
||||
#define MANUAL_DE_RX_LEVEL 0
|
||||
|
||||
static device_config_t g_device_cfg;
|
||||
static modbus_rtu_ctx_t g_rtu_ctx;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t unit_id;
|
||||
char name[DEVICE_NAME_MAX_LEN];
|
||||
char csv_path[128];
|
||||
|
||||
modbus_db_t db;
|
||||
modbus_memory_t mem;
|
||||
} modbus_device_t;
|
||||
|
||||
static modbus_device_t *g_devices = NULL;
|
||||
static size_t g_device_count = 0U;
|
||||
static int g_manual_de_pin = -1;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* Helpers */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
static bool file_exists(const char *path)
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
if (path == NULL)
|
||||
return false;
|
||||
|
||||
fp = fopen(path, "r");
|
||||
if (fp == NULL)
|
||||
return false;
|
||||
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void spiffs_list_files(void)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent *entry;
|
||||
|
||||
dir = opendir("/spiffs");
|
||||
if (dir == NULL)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to open /spiffs for listing");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "----- SPIFFS file list -----");
|
||||
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
ESP_LOGI(TAG, "SPIFFS file: %s", entry->d_name);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "----------------------------");
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* SPIFFS */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
static esp_err_t spiffs_init(void)
|
||||
{
|
||||
esp_vfs_spiffs_conf_t conf = {
|
||||
.base_path = "/spiffs",
|
||||
.partition_label = NULL,
|
||||
.max_files = 8,
|
||||
.format_if_mount_failed = true
|
||||
};
|
||||
|
||||
esp_err_t ret = esp_vfs_spiffs_register(&conf);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to mount SPIFFS (%s)", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
{
|
||||
size_t total = 0;
|
||||
size_t used = 0;
|
||||
ret = esp_spiffs_info(NULL, &total, &used);
|
||||
if (ret == ESP_OK)
|
||||
{
|
||||
ESP_LOGI(TAG, "SPIFFS mounted: total=%u used=%u",
|
||||
(unsigned)total, (unsigned)used);
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* UART / RTU */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
static uart_parity_t cfg_parity_to_uart(modbus_rtu_parity_t parity)
|
||||
{
|
||||
switch (parity)
|
||||
{
|
||||
case MB_RTU_PARITY_EVEN:
|
||||
return UART_PARITY_EVEN;
|
||||
|
||||
case MB_RTU_PARITY_ODD:
|
||||
return UART_PARITY_ODD;
|
||||
|
||||
case MB_RTU_PARITY_NONE:
|
||||
default:
|
||||
return UART_PARITY_DISABLE;
|
||||
}
|
||||
}
|
||||
|
||||
static uart_stop_bits_t cfg_stop_bits_to_uart(uint8_t stop_bits)
|
||||
{
|
||||
if (stop_bits == 2U)
|
||||
return UART_STOP_BITS_2;
|
||||
|
||||
return UART_STOP_BITS_1;
|
||||
}
|
||||
|
||||
static esp_err_t modbus_uart_init(const device_config_t *cfg)
|
||||
{
|
||||
uart_config_t uart_cfg;
|
||||
esp_err_t ret;
|
||||
uart_port_t uart_num;
|
||||
|
||||
if (cfg == NULL)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
|
||||
uart_num = (uart_port_t)cfg->rtu.uart_num;
|
||||
|
||||
memset(&uart_cfg, 0, sizeof(uart_cfg));
|
||||
uart_cfg.baud_rate = (int)cfg->rtu.baud_rate;
|
||||
uart_cfg.data_bits = UART_DATA_8_BITS;
|
||||
uart_cfg.parity = cfg_parity_to_uart(cfg->rtu.parity);
|
||||
uart_cfg.stop_bits = cfg_stop_bits_to_uart(cfg->rtu.stop_bits);
|
||||
uart_cfg.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
uart_cfg.source_clk = UART_SCLK_DEFAULT;
|
||||
|
||||
uart_driver_delete(uart_num);
|
||||
|
||||
ret = uart_driver_install(uart_num,
|
||||
MODBUS_RX_BUF_SIZE * 2,
|
||||
MODBUS_TX_BUF_SIZE * 2,
|
||||
0,
|
||||
NULL,
|
||||
0);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "uart_driver_install failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = uart_param_config(uart_num, &uart_cfg);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "uart_param_config failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Manual DE mode:
|
||||
* do NOT hand DE pin to UART RTS here.
|
||||
* TX/RX only.
|
||||
*/
|
||||
ret = uart_set_pin(uart_num,
|
||||
cfg->rtu.tx_pin,
|
||||
cfg->rtu.rx_pin,
|
||||
UART_PIN_NO_CHANGE,
|
||||
UART_PIN_NO_CHANGE);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "uart_set_pin failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "UART normal mode enabled (manual DE control)");
|
||||
ESP_LOGI(TAG,
|
||||
"RTU UART configured: uart=%u baud=%u parity=%d stop_bits=%u tx=%d rx=%d de=%d",
|
||||
(unsigned)cfg->rtu.uart_num,
|
||||
(unsigned)cfg->rtu.baud_rate,
|
||||
(int)cfg->rtu.parity,
|
||||
(unsigned)cfg->rtu.stop_bits,
|
||||
cfg->rtu.tx_pin,
|
||||
cfg->rtu.rx_pin,
|
||||
cfg->rtu.de_pin);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void modbus_rtu_task(void *arg)
|
||||
{
|
||||
uint8_t rx_buf[MODBUS_RX_BUF_SIZE];
|
||||
uint8_t tx_buf[MODBUS_TX_BUF_SIZE];
|
||||
|
||||
(void)arg;
|
||||
|
||||
ESP_LOGI(TAG, "modbus_rtu_task started");
|
||||
|
||||
while (1)
|
||||
{
|
||||
int len;
|
||||
size_t resp_len = 0U;
|
||||
bool ok;
|
||||
|
||||
len = modbus_rtu_read_frame(&g_rtu_ctx,
|
||||
rx_buf,
|
||||
sizeof(rx_buf),
|
||||
pdMS_TO_TICKS(100));
|
||||
|
||||
if (len < 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "RTU frame read error");
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (len == 0)
|
||||
continue;
|
||||
|
||||
ok = modbus_rtu_process_request(&g_rtu_ctx,
|
||||
rx_buf,
|
||||
(size_t)len,
|
||||
tx_buf,
|
||||
sizeof(tx_buf),
|
||||
&resp_len);
|
||||
|
||||
if (!ok)
|
||||
continue;
|
||||
|
||||
if (resp_len > 0U)
|
||||
{
|
||||
int written;
|
||||
|
||||
if (g_manual_de_pin >= 0)
|
||||
{
|
||||
gpio_set_level((gpio_num_t)g_manual_de_pin, MANUAL_DE_TX_LEVEL);
|
||||
esp_rom_delay_us(200);
|
||||
}
|
||||
|
||||
written = uart_write_bytes(g_rtu_ctx.uart_num,
|
||||
(const char *)tx_buf,
|
||||
(uint32_t)resp_len);
|
||||
|
||||
if (written < 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "uart_write_bytes failed");
|
||||
|
||||
if (g_manual_de_pin >= 0)
|
||||
gpio_set_level((gpio_num_t)g_manual_de_pin, MANUAL_DE_RX_LEVEL);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(
|
||||
uart_wait_tx_done(g_rtu_ctx.uart_num,
|
||||
pdMS_TO_TICKS(100)));
|
||||
|
||||
if (g_manual_de_pin >= 0)
|
||||
{
|
||||
esp_rom_delay_us(200);
|
||||
gpio_set_level((gpio_num_t)g_manual_de_pin, MANUAL_DE_RX_LEVEL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* Override Sync Task */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
static void modbus_override_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
while (1)
|
||||
{
|
||||
size_t d;
|
||||
|
||||
for (d = 0; d < g_device_count; d++)
|
||||
{
|
||||
modbus_device_t *dev = &g_devices[d];
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < dev->db.count; i++)
|
||||
{
|
||||
modbus_point_t *pt = &dev->db.points[i];
|
||||
|
||||
if (pt->is_helper)
|
||||
continue;
|
||||
|
||||
if (!pt->has_control_address)
|
||||
continue;
|
||||
|
||||
if (pt->data_type == MB_DATA_BOOL)
|
||||
{
|
||||
uint8_t value;
|
||||
|
||||
if (modbus_memory_read_bit(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->control_type),
|
||||
pt->control_offset,
|
||||
&value))
|
||||
{
|
||||
modbus_memory_write_bit(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->type),
|
||||
pt->offset,
|
||||
value);
|
||||
}
|
||||
}
|
||||
else if (pt->data_type == MB_DATA_UINT16)
|
||||
{
|
||||
uint16_t value;
|
||||
|
||||
if (modbus_memory_read_reg(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->control_type),
|
||||
pt->control_offset,
|
||||
&value))
|
||||
{
|
||||
modbus_memory_write_reg(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->type),
|
||||
pt->offset,
|
||||
value);
|
||||
}
|
||||
}
|
||||
else if (pt->data_type == MB_DATA_UINT32 ||
|
||||
pt->data_type == MB_DATA_FLOAT)
|
||||
{
|
||||
uint16_t w1, w2;
|
||||
|
||||
if (modbus_memory_read_reg(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->control_type),
|
||||
pt->control_offset,
|
||||
&w1) &&
|
||||
modbus_memory_read_reg(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->control_type),
|
||||
(uint16_t)(pt->control_offset + 1U),
|
||||
&w2))
|
||||
{
|
||||
modbus_memory_write_reg(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->type),
|
||||
pt->offset,
|
||||
w1);
|
||||
|
||||
modbus_memory_write_reg(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->type),
|
||||
(uint16_t)(pt->offset + 1U),
|
||||
w2);
|
||||
}
|
||||
}
|
||||
else if (pt->data_type == MB_DATA_DOUBLE)
|
||||
{
|
||||
uint16_t w1, w2, w3, w4;
|
||||
|
||||
if (modbus_memory_read_reg(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->control_type),
|
||||
pt->control_offset,
|
||||
&w1) &&
|
||||
modbus_memory_read_reg(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->control_type),
|
||||
(uint16_t)(pt->control_offset + 1U),
|
||||
&w2) &&
|
||||
modbus_memory_read_reg(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->control_type),
|
||||
(uint16_t)(pt->control_offset + 2U),
|
||||
&w3) &&
|
||||
modbus_memory_read_reg(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->control_type),
|
||||
(uint16_t)(pt->control_offset + 3U),
|
||||
&w4))
|
||||
{
|
||||
modbus_memory_write_reg(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->type),
|
||||
pt->offset,
|
||||
w1);
|
||||
|
||||
modbus_memory_write_reg(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->type),
|
||||
(uint16_t)(pt->offset + 1U),
|
||||
w2);
|
||||
|
||||
modbus_memory_write_reg(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->type),
|
||||
(uint16_t)(pt->offset + 2U),
|
||||
w3);
|
||||
|
||||
modbus_memory_write_reg(&dev->mem,
|
||||
modbus_points_type_to_mem(pt->type),
|
||||
(uint16_t)(pt->offset + 3U),
|
||||
w4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* Helpers for app_main */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
static uint8_t data_type_internal_cost(modbus_data_type_t type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MB_DATA_BOOL:
|
||||
case MB_DATA_UINT16:
|
||||
return 1U;
|
||||
|
||||
case MB_DATA_UINT32:
|
||||
case MB_DATA_FLOAT:
|
||||
return 2U;
|
||||
|
||||
case MB_DATA_DOUBLE:
|
||||
return 4U;
|
||||
|
||||
default:
|
||||
return 0U;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t pics_type_cost(pics_data_type_t type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case PICS_DATA_BOOLEAN:
|
||||
return 1U;
|
||||
case PICS_DATA_INTEGER:
|
||||
return 2U;
|
||||
case PICS_DATA_FLOAT:
|
||||
return 4U;
|
||||
case PICS_DATA_NONE:
|
||||
default:
|
||||
return 0U;
|
||||
}
|
||||
}
|
||||
|
||||
static void print_device_type_mix(const modbus_device_t *dev)
|
||||
{
|
||||
size_t i;
|
||||
size_t bool_count = 0;
|
||||
size_t uint16_count = 0;
|
||||
size_t uint32_count = 0;
|
||||
size_t float_count = 0;
|
||||
size_t double_count = 0;
|
||||
|
||||
size_t pics_bool_count = 0;
|
||||
size_t pics_int_count = 0;
|
||||
size_t pics_float_count = 0;
|
||||
size_t pics_none_count = 0;
|
||||
|
||||
size_t internal_points_from_base_rows = 0;
|
||||
size_t pics_registers_from_base_rows = 0;
|
||||
size_t base_rows = 0;
|
||||
|
||||
if (dev == NULL)
|
||||
return;
|
||||
|
||||
for (i = 0; i < dev->db.count; i++)
|
||||
{
|
||||
const modbus_point_t *pt = &dev->db.points[i];
|
||||
|
||||
if (pt->is_helper)
|
||||
continue;
|
||||
|
||||
base_rows++;
|
||||
|
||||
switch (pt->data_type)
|
||||
{
|
||||
case MB_DATA_BOOL:
|
||||
bool_count++;
|
||||
break;
|
||||
case MB_DATA_UINT16:
|
||||
uint16_count++;
|
||||
break;
|
||||
case MB_DATA_UINT32:
|
||||
uint32_count++;
|
||||
break;
|
||||
case MB_DATA_FLOAT:
|
||||
float_count++;
|
||||
break;
|
||||
case MB_DATA_DOUBLE:
|
||||
double_count++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (pt->pics_data_type)
|
||||
{
|
||||
case PICS_DATA_BOOLEAN:
|
||||
pics_bool_count++;
|
||||
break;
|
||||
case PICS_DATA_INTEGER:
|
||||
pics_int_count++;
|
||||
break;
|
||||
case PICS_DATA_FLOAT:
|
||||
pics_float_count++;
|
||||
break;
|
||||
case PICS_DATA_NONE:
|
||||
default:
|
||||
pics_none_count++;
|
||||
break;
|
||||
}
|
||||
|
||||
internal_points_from_base_rows += data_type_internal_cost(pt->data_type);
|
||||
pics_registers_from_base_rows += pics_type_cost(pt->pics_data_type);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"Device %u (%s) type mix: base_rows=%u internal_points=%u pics_regs=%u",
|
||||
(unsigned)dev->unit_id,
|
||||
dev->name,
|
||||
(unsigned)base_rows,
|
||||
(unsigned)internal_points_from_base_rows,
|
||||
(unsigned)pics_registers_from_base_rows);
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
" data_type counts: bool=%u uint16=%u uint32=%u float=%u double=%u",
|
||||
(unsigned)bool_count,
|
||||
(unsigned)uint16_count,
|
||||
(unsigned)uint32_count,
|
||||
(unsigned)float_count,
|
||||
(unsigned)double_count);
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
" pics_data_type counts: none=%u Boolean=%u Integer=%u Float=%u",
|
||||
(unsigned)pics_none_count,
|
||||
(unsigned)pics_bool_count,
|
||||
(unsigned)pics_int_count,
|
||||
(unsigned)pics_float_count);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* app_main */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
size_t i;
|
||||
|
||||
ESP_LOGI(TAG, "Starting Modbus RTU device");
|
||||
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
|
||||
{
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
ESP_ERROR_CHECK(spiffs_init());
|
||||
spiffs_list_files();
|
||||
|
||||
if (!config_store_load(DEVICE_CONFIG_PATH, &g_device_cfg))
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to load device config: %s", DEVICE_CONFIG_PATH);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"RTU config: uart=%u baud=%u parity=%d stop_bits=%u tx=%d rx=%d de=%d",
|
||||
(unsigned)g_device_cfg.rtu.uart_num,
|
||||
(unsigned)g_device_cfg.rtu.baud_rate,
|
||||
(int)g_device_cfg.rtu.parity,
|
||||
(unsigned)g_device_cfg.rtu.stop_bits,
|
||||
g_device_cfg.rtu.tx_pin,
|
||||
g_device_cfg.rtu.rx_pin,
|
||||
g_device_cfg.rtu.de_pin);
|
||||
|
||||
ESP_LOGI(TAG, "Configured device_count = %u",
|
||||
(unsigned)g_device_cfg.modbus.device_count);
|
||||
|
||||
for (i = 0; i < g_device_cfg.modbus.device_count; i++)
|
||||
{
|
||||
ESP_LOGI(TAG,
|
||||
"CFG device[%u]: enabled=%u unit_id=%u name=%s csv=%s",
|
||||
(unsigned)i,
|
||||
g_device_cfg.devices[i].enabled ? 1 : 0,
|
||||
(unsigned)g_device_cfg.devices[i].unit_id,
|
||||
g_device_cfg.devices[i].name,
|
||||
g_device_cfg.devices[i].csv);
|
||||
}
|
||||
|
||||
if (g_device_cfg.modbus.device_count == 0U)
|
||||
{
|
||||
ESP_LOGE(TAG, "No Modbus devices configured");
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_device_cfg.modbus.device_count > MAX_VIRTUAL_DEVICES)
|
||||
{
|
||||
ESP_LOGE(TAG, "Too many virtual devices (%u)",
|
||||
(unsigned)g_device_cfg.modbus.device_count);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_devices != NULL)
|
||||
{
|
||||
free(g_devices);
|
||||
g_devices = NULL;
|
||||
}
|
||||
|
||||
g_devices = calloc(g_device_cfg.modbus.device_count, sizeof(modbus_device_t));
|
||||
if (g_devices == NULL)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to allocate device table for %u devices",
|
||||
(unsigned)g_device_cfg.modbus.device_count);
|
||||
return;
|
||||
}
|
||||
|
||||
g_device_count = 0U;
|
||||
|
||||
modbus_rtu_init(&g_rtu_ctx,
|
||||
(uart_port_t)g_device_cfg.rtu.uart_num,
|
||||
g_device_cfg.rtu.baud_rate);
|
||||
|
||||
ESP_ERROR_CHECK(modbus_uart_init(&g_device_cfg));
|
||||
|
||||
g_manual_de_pin = g_device_cfg.rtu.de_pin;
|
||||
if (g_manual_de_pin >= 0)
|
||||
{
|
||||
gpio_reset_pin((gpio_num_t)g_manual_de_pin);
|
||||
gpio_set_direction((gpio_num_t)g_manual_de_pin, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level((gpio_num_t)g_manual_de_pin, MANUAL_DE_RX_LEVEL);
|
||||
ESP_LOGI(TAG, "Manual DE initialized on GPIO%d, idle level=%d",
|
||||
g_manual_de_pin, MANUAL_DE_RX_LEVEL);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"UART driver installed: uart=%d tx=%d rx=%d de=%d baud=%d",
|
||||
g_device_cfg.rtu.uart_num,
|
||||
g_device_cfg.rtu.tx_pin,
|
||||
g_device_cfg.rtu.rx_pin,
|
||||
g_device_cfg.rtu.de_pin,
|
||||
(int)g_device_cfg.rtu.baud_rate);
|
||||
|
||||
for (i = 0; i < g_device_cfg.modbus.device_count; i++)
|
||||
{
|
||||
modbus_device_t *dev;
|
||||
const virtual_device_settings_t *cfg_dev = &g_device_cfg.devices[i];
|
||||
|
||||
if (!cfg_dev->enabled)
|
||||
{
|
||||
ESP_LOGI(TAG, "Skipping disabled device[%u]", (unsigned)i);
|
||||
continue;
|
||||
}
|
||||
|
||||
dev = &g_devices[g_device_count];
|
||||
memset(dev, 0, sizeof(*dev));
|
||||
|
||||
dev->unit_id = cfg_dev->unit_id;
|
||||
|
||||
strncpy(dev->name, cfg_dev->name, sizeof(dev->name) - 1U);
|
||||
dev->name[sizeof(dev->name) - 1U] = '\0';
|
||||
|
||||
if (strncmp(cfg_dev->csv, "/spiffs/", 8U) == 0)
|
||||
{
|
||||
strncpy(dev->csv_path, cfg_dev->csv, sizeof(dev->csv_path) - 1U);
|
||||
dev->csv_path[sizeof(dev->csv_path) - 1U] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(dev->csv_path,
|
||||
sizeof(dev->csv_path),
|
||||
"/spiffs/%s",
|
||||
cfg_dev->csv);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"Preparing device[%u]: unit_id=%u name=%s csv=%s",
|
||||
(unsigned)i,
|
||||
(unsigned)dev->unit_id,
|
||||
dev->name,
|
||||
dev->csv_path);
|
||||
|
||||
if (!file_exists(dev->csv_path))
|
||||
{
|
||||
ESP_LOGE(TAG, "CSV file not found for device %u: %s",
|
||||
(unsigned)dev->unit_id,
|
||||
dev->csv_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
modbus_points_init(&dev->db);
|
||||
|
||||
if (!modbus_points_load(&dev->db, dev->csv_path))
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to load CSV for device %u: %s",
|
||||
(unsigned)dev->unit_id,
|
||||
dev->csv_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dev->db.count > MODBUS_MAX_POINTS)
|
||||
{
|
||||
ESP_LOGE(TAG,
|
||||
"Device %u exceeded MODBUS_MAX_POINTS (%u > %u)",
|
||||
(unsigned)dev->unit_id,
|
||||
(unsigned)dev->db.count,
|
||||
(unsigned)MODBUS_MAX_POINTS);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (!modbus_memory_init(&dev->mem, &dev->db))
|
||||
{
|
||||
ESP_LOGE(TAG, "Memory init failed for device %u",
|
||||
(unsigned)dev->unit_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!modbus_rtu_add_device(&g_rtu_ctx,
|
||||
dev->unit_id,
|
||||
&dev->mem,
|
||||
dev->name))
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to register RTU device unit_id=%u name=%s",
|
||||
(unsigned)dev->unit_id,
|
||||
dev->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Device %u (%s) loaded %u Modbus points from %s",
|
||||
(unsigned)dev->unit_id,
|
||||
dev->name,
|
||||
(unsigned)dev->db.count,
|
||||
dev->csv_path);
|
||||
|
||||
g_device_count++;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "----- Runtime Modbus Device Table -----");
|
||||
|
||||
for (i = 0; i < g_device_count; i++)
|
||||
{
|
||||
ESP_LOGI(TAG,
|
||||
"RUNTIME device[%u]: unit_id=%u name=%s csv=%s points=%u",
|
||||
(unsigned)i,
|
||||
(unsigned)g_devices[i].unit_id,
|
||||
g_devices[i].name,
|
||||
g_devices[i].csv_path,
|
||||
(unsigned)g_devices[i].db.count);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "---------------------------------------");
|
||||
|
||||
ESP_LOGI(TAG, "----- Modbus Point Capacity -----");
|
||||
|
||||
{
|
||||
size_t total_points_used = 0;
|
||||
size_t total_points_capacity = g_device_count * MODBUS_MAX_POINTS;
|
||||
size_t total_points_remaining = 0;
|
||||
|
||||
size_t free_heap = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
size_t largest_block = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
|
||||
|
||||
size_t point_struct_size = sizeof(modbus_point_t);
|
||||
size_t heap_equivalent_points = 0;
|
||||
|
||||
if (point_struct_size > 0U)
|
||||
heap_equivalent_points = free_heap / point_struct_size;
|
||||
|
||||
for (i = 0; i < g_device_count; i++)
|
||||
{
|
||||
const modbus_device_t *dev = &g_devices[i];
|
||||
size_t remaining = 0;
|
||||
float pct = 0.0f;
|
||||
|
||||
if (dev->db.count < MODBUS_MAX_POINTS)
|
||||
remaining = MODBUS_MAX_POINTS - dev->db.count;
|
||||
|
||||
if (MODBUS_MAX_POINTS > 0U)
|
||||
pct = (100.0f * (float)dev->db.count) / (float)MODBUS_MAX_POINTS;
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"Device %u (%s): used=%u / max=%u, remaining=%u (%.1f%% used)",
|
||||
(unsigned)dev->unit_id,
|
||||
dev->name,
|
||||
(unsigned)dev->db.count,
|
||||
(unsigned)MODBUS_MAX_POINTS,
|
||||
(unsigned)remaining,
|
||||
pct);
|
||||
|
||||
total_points_used += dev->db.count;
|
||||
total_points_remaining += remaining;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"Configured capacity: used=%u / max=%u, remaining=%u",
|
||||
(unsigned)total_points_used,
|
||||
(unsigned)total_points_capacity,
|
||||
(unsigned)total_points_remaining);
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"Free heap: %u bytes, largest block: %u bytes, point size: %u bytes",
|
||||
(unsigned)free_heap,
|
||||
(unsigned)largest_block,
|
||||
(unsigned)point_struct_size);
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"Heap-equivalent points available (informational only): %u",
|
||||
(unsigned)heap_equivalent_points);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "---------------------------------");
|
||||
|
||||
ESP_LOGI(TAG, "----- Point Cost Guide -----");
|
||||
ESP_LOGI(TAG, "Internal point usage (counts against MODBUS_MAX_POINTS):");
|
||||
ESP_LOGI(TAG, " data_type=bool -> 1 point");
|
||||
ESP_LOGI(TAG, " data_type=uint16 -> 1 point");
|
||||
ESP_LOGI(TAG, " data_type=uint32 -> 2 points");
|
||||
ESP_LOGI(TAG, " data_type=float -> 2 points");
|
||||
ESP_LOGI(TAG, " data_type=double -> 4 points");
|
||||
|
||||
ESP_LOGI(TAG, "PICS staging usage (for pics_address spacing only):");
|
||||
ESP_LOGI(TAG, " pics_data_type=Boolean -> 1 register");
|
||||
ESP_LOGI(TAG, " pics_data_type=Integer -> 2 registers");
|
||||
ESP_LOGI(TAG, " pics_data_type=Float -> 4 registers");
|
||||
ESP_LOGI(TAG, " pics_data_type=None -> 0 registers");
|
||||
|
||||
ESP_LOGI(TAG, "Planning examples:");
|
||||
ESP_LOGI(TAG, " 100 uint16 CSV rows -> about 100 internal points");
|
||||
ESP_LOGI(TAG, " 100 float CSV rows -> about 200 internal points");
|
||||
ESP_LOGI(TAG, " 100 double CSV rows -> about 400 internal points");
|
||||
ESP_LOGI(TAG, "--------------------------------");
|
||||
|
||||
ESP_LOGI(TAG, "----- Device Type Mix -----");
|
||||
|
||||
for (i = 0; i < g_device_count; i++)
|
||||
{
|
||||
print_device_type_mix(&g_devices[i]);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "---------------------------");
|
||||
|
||||
ESP_LOGI(TAG, "----- Modbus Memory Usage -----");
|
||||
|
||||
{
|
||||
size_t total_points = 0;
|
||||
size_t d;
|
||||
|
||||
for (d = 0; d < g_device_count; d++)
|
||||
{
|
||||
const modbus_device_t *dev = &g_devices[d];
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"Device %u (%s): %u / %u points used (%.1f%%)",
|
||||
(unsigned)dev->unit_id,
|
||||
dev->name,
|
||||
(unsigned)dev->db.count,
|
||||
(unsigned)MODBUS_MAX_POINTS,
|
||||
(100.0f * (float)dev->db.count) / (float)MODBUS_MAX_POINTS);
|
||||
|
||||
total_points += dev->db.count;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "TOTAL POINTS LOADED: %u", (unsigned)total_points);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "--------------------------------");
|
||||
|
||||
if (g_device_count == 0U)
|
||||
{
|
||||
ESP_LOGE(TAG, "No valid Modbus devices loaded, not starting RTU");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
BaseType_t rc;
|
||||
|
||||
ESP_LOGI(TAG, "Creating modbus_rtu_task");
|
||||
rc = xTaskCreate(modbus_rtu_task,
|
||||
"modbus_rtu_task",
|
||||
8192,
|
||||
NULL,
|
||||
5,
|
||||
NULL);
|
||||
ESP_LOGI(TAG, "modbus_rtu_task create rc=%ld", (long)rc);
|
||||
|
||||
ESP_LOGI(TAG, "Creating modbus_override_task");
|
||||
rc = xTaskCreate(modbus_override_task,
|
||||
"modbus_override_task",
|
||||
8192,
|
||||
NULL,
|
||||
5,
|
||||
NULL);
|
||||
ESP_LOGI(TAG, "modbus_override_task create rc=%ld", (long)rc);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user