Added max_points in .ini, increased spiff csv file character limit, and file name limit. Added more prints for how many devices you can configure based on current memory size and max_points.
This commit is contained in:
@@ -101,6 +101,32 @@ static void copy_str(char *dst, size_t dst_size, const char *src)
|
|||||||
dst[dst_size - 1U] = '\0';
|
dst[dst_size - 1U] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool copy_dyn_str(char **dst, const char *src)
|
||||||
|
{
|
||||||
|
char *new_value;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
if (dst == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (src == NULL)
|
||||||
|
src = "";
|
||||||
|
|
||||||
|
len = strlen(src);
|
||||||
|
|
||||||
|
new_value = malloc(len + 1U);
|
||||||
|
if (new_value == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
memcpy(new_value, src, len + 1U);
|
||||||
|
|
||||||
|
if (*dst != NULL)
|
||||||
|
free(*dst);
|
||||||
|
|
||||||
|
*dst = new_value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static int parse_device_section_index(const char *section)
|
static int parse_device_section_index(const char *section)
|
||||||
{
|
{
|
||||||
unsigned long idx;
|
unsigned long idx;
|
||||||
@@ -149,7 +175,7 @@ static void set_device_defaults(virtual_device_settings_t *dev, uint8_t index)
|
|||||||
|
|
||||||
snprintf(default_name, sizeof(default_name), "Device_%u", (unsigned)(index + 1U));
|
snprintf(default_name, sizeof(default_name), "Device_%u", (unsigned)(index + 1U));
|
||||||
copy_str(dev->name, sizeof(dev->name), default_name);
|
copy_str(dev->name, sizeof(dev->name), default_name);
|
||||||
dev->csv[0] = '\0';
|
dev->csv = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void config_store_set_defaults(device_config_t *cfg)
|
void config_store_set_defaults(device_config_t *cfg)
|
||||||
@@ -171,20 +197,91 @@ void config_store_set_defaults(device_config_t *cfg)
|
|||||||
cfg->modbus.port = 502;
|
cfg->modbus.port = 502;
|
||||||
cfg->modbus.max_clients = 4;
|
cfg->modbus.max_clients = 4;
|
||||||
cfg->modbus.device_count = 1;
|
cfg->modbus.device_count = 1;
|
||||||
|
cfg->modbus.max_points = 150;
|
||||||
|
|
||||||
for (i = 0; i < MAX_VIRTUAL_DEVICES; i++)
|
for (i = 0; i < MAX_VIRTUAL_DEVICES; i++)
|
||||||
set_device_defaults(&cfg->devices[i], (uint8_t)i);
|
set_device_defaults(&cfg->devices[i], (uint8_t)i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void config_store_free(device_config_t *cfg)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (cfg == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (i = 0; i < MAX_VIRTUAL_DEVICES; i++)
|
||||||
|
{
|
||||||
|
if (cfg->devices[i].csv != NULL)
|
||||||
|
{
|
||||||
|
free(cfg->devices[i].csv);
|
||||||
|
cfg->devices[i].csv = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool read_dynamic_line(FILE *fp, char **out_line)
|
||||||
|
{
|
||||||
|
char *line = NULL;
|
||||||
|
size_t capacity = 128U;
|
||||||
|
size_t length = 0U;
|
||||||
|
int ch;
|
||||||
|
|
||||||
|
if (fp == NULL || out_line == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*out_line = NULL;
|
||||||
|
|
||||||
|
line = malloc(capacity);
|
||||||
|
if (line == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while ((ch = fgetc(fp)) != EOF)
|
||||||
|
{
|
||||||
|
char *new_line;
|
||||||
|
|
||||||
|
if (length + 1U >= capacity)
|
||||||
|
{
|
||||||
|
size_t new_capacity = capacity * 2U;
|
||||||
|
|
||||||
|
new_line = realloc(line, new_capacity);
|
||||||
|
if (new_line == NULL)
|
||||||
|
{
|
||||||
|
free(line);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
line = new_line;
|
||||||
|
capacity = new_capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
line[length++] = (char)ch;
|
||||||
|
|
||||||
|
if (ch == '\n')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length == 0U && ch == EOF)
|
||||||
|
{
|
||||||
|
free(line);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
line[length] = '\0';
|
||||||
|
*out_line = line;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool config_store_load(const char *filename, device_config_t *cfg)
|
bool config_store_load(const char *filename, device_config_t *cfg)
|
||||||
{
|
{
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
char line[256];
|
char *line = NULL;
|
||||||
char section[32];
|
char section[32];
|
||||||
|
|
||||||
if (filename == NULL || cfg == NULL)
|
if (filename == NULL || cfg == NULL)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
config_store_free(cfg);
|
||||||
config_store_set_defaults(cfg);
|
config_store_set_defaults(cfg);
|
||||||
memset(section, 0, sizeof(section));
|
memset(section, 0, sizeof(section));
|
||||||
|
|
||||||
@@ -192,7 +289,7 @@ bool config_store_load(const char *filename, device_config_t *cfg)
|
|||||||
if (fp == NULL)
|
if (fp == NULL)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
while (fgets(line, sizeof(line), fp) != NULL)
|
while (read_dynamic_line(fp, &line))
|
||||||
{
|
{
|
||||||
char *eq;
|
char *eq;
|
||||||
char *key;
|
char *key;
|
||||||
@@ -202,10 +299,19 @@ bool config_store_load(const char *filename, device_config_t *cfg)
|
|||||||
trim_whitespace(line);
|
trim_whitespace(line);
|
||||||
|
|
||||||
if (line[0] == '\0')
|
if (line[0] == '\0')
|
||||||
|
{
|
||||||
|
free(line);
|
||||||
|
line = NULL;
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (line[0] == ';' || line[0] == '#')
|
if (line[0] == ';' || line[0] == '#')
|
||||||
|
{
|
||||||
|
free(line);
|
||||||
|
line = NULL;
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (line[0] == '[')
|
if (line[0] == '[')
|
||||||
{
|
{
|
||||||
@@ -217,12 +323,18 @@ bool config_store_load(const char *filename, device_config_t *cfg)
|
|||||||
copy_str(section, sizeof(section), &line[1]);
|
copy_str(section, sizeof(section), &line[1]);
|
||||||
trim_whitespace(section);
|
trim_whitespace(section);
|
||||||
}
|
}
|
||||||
|
free(line);
|
||||||
|
line = NULL;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
eq = strchr(line, '=');
|
eq = strchr(line, '=');
|
||||||
if (eq == NULL)
|
if (eq == NULL)
|
||||||
|
{
|
||||||
|
free(line);
|
||||||
|
line = NULL;
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
*eq = '\0';
|
*eq = '\0';
|
||||||
key = line;
|
key = line;
|
||||||
@@ -274,6 +386,13 @@ bool config_store_load(const char *filename, device_config_t *cfg)
|
|||||||
cfg->modbus.device_count = (uint8_t)v;
|
cfg->modbus.device_count = (uint8_t)v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (str_ieq(key, "max_points"))
|
||||||
|
{
|
||||||
|
if (parse_u32(value, &v) && v > 0U)
|
||||||
|
{
|
||||||
|
cfg->modbus.max_points = (size_t)v;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -301,16 +420,28 @@ bool config_store_load(const char *filename, device_config_t *cfg)
|
|||||||
}
|
}
|
||||||
else if (str_ieq(key, "csv"))
|
else if (str_ieq(key, "csv"))
|
||||||
{
|
{
|
||||||
copy_str(dev->csv, sizeof(dev->csv), value);
|
if (!copy_dyn_str(&dev->csv, value))
|
||||||
|
{
|
||||||
|
free(line);
|
||||||
|
line = NULL;
|
||||||
|
fclose(fp);
|
||||||
|
config_store_free(cfg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
free(line);
|
||||||
|
line = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
|
||||||
if (cfg->wifi.ssid[0] == '\0')
|
if (cfg->wifi.ssid[0] == '\0')
|
||||||
|
{
|
||||||
|
printf("CONFIG ERROR: wifi.ssid is empty\n");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (cfg->modbus.port == 0U)
|
if (cfg->modbus.port == 0U)
|
||||||
return false;
|
return false;
|
||||||
@@ -318,6 +449,9 @@ bool config_store_load(const char *filename, device_config_t *cfg)
|
|||||||
if (cfg->modbus.max_clients == 0U)
|
if (cfg->modbus.max_clients == 0U)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (cfg->modbus.max_points == 0U)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (cfg->modbus.device_count == 0U || cfg->modbus.device_count > MAX_VIRTUAL_DEVICES)
|
if (cfg->modbus.device_count == 0U || cfg->modbus.device_count > MAX_VIRTUAL_DEVICES)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -330,10 +464,17 @@ bool config_store_load(const char *filename, device_config_t *cfg)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (dev->unit_id == 0U)
|
if (dev->unit_id == 0U)
|
||||||
|
{
|
||||||
|
printf("CONFIG ERROR: enable device %u has invalid unit_id 0\n",
|
||||||
|
(unsigned)(i + 1U));
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (dev->csv[0] == '\0')
|
if (dev->csv == NULL || dev->csv[0] == '\0')
|
||||||
|
{
|
||||||
|
printf("CONFIG ERROR: enabled device %u has empty csv\n", (unsigned)(i + 1U));
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (dev->name[0] == '\0')
|
if (dev->name[0] == '\0')
|
||||||
{
|
{
|
||||||
@@ -348,7 +489,12 @@ bool config_store_load(const char *filename, device_config_t *cfg)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (dev->unit_id == cfg->devices[j].unit_id)
|
if (dev->unit_id == cfg->devices[j].unit_id)
|
||||||
return false;
|
{
|
||||||
|
printf("CONFIG ERROR: duplicate unit_id %u between device %u and device %u\n",
|
||||||
|
(unsigned)dev->unit_id,
|
||||||
|
(unsigned)(i + 1U),
|
||||||
|
(unsigned)(j + 1U));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
#define DEVICE_NAME_MAX_LEN 32
|
#define DEVICE_NAME_MAX_LEN 32
|
||||||
#define DEVICE_CSV_MAX_LEN 64
|
|
||||||
#define WIFI_SSID_MAX_LEN 32
|
#define WIFI_SSID_MAX_LEN 32
|
||||||
#define WIFI_PASSWORD_MAX_LEN 64
|
#define WIFI_PASSWORD_MAX_LEN 64
|
||||||
#define IPV4_STR_MAX_LEN 16
|
#define IPV4_STR_MAX_LEN 16
|
||||||
@@ -29,6 +28,7 @@ typedef struct
|
|||||||
uint16_t port;
|
uint16_t port;
|
||||||
uint8_t max_clients;
|
uint8_t max_clients;
|
||||||
uint8_t device_count;
|
uint8_t device_count;
|
||||||
|
size_t max_points;
|
||||||
} modbus_settings_t;
|
} modbus_settings_t;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
@@ -36,7 +36,7 @@ typedef struct
|
|||||||
bool enabled;
|
bool enabled;
|
||||||
uint8_t unit_id;
|
uint8_t unit_id;
|
||||||
char name[DEVICE_NAME_MAX_LEN];
|
char name[DEVICE_NAME_MAX_LEN];
|
||||||
char csv[DEVICE_CSV_MAX_LEN];
|
char *csv;
|
||||||
} virtual_device_settings_t;
|
} virtual_device_settings_t;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
@@ -46,6 +46,7 @@ typedef struct
|
|||||||
virtual_device_settings_t devices[MAX_VIRTUAL_DEVICES];
|
virtual_device_settings_t devices[MAX_VIRTUAL_DEVICES];
|
||||||
} device_config_t;
|
} device_config_t;
|
||||||
|
|
||||||
|
void config_store_free(device_config_t *cfg);
|
||||||
void config_store_set_defaults(device_config_t *cfg);
|
void config_store_set_defaults(device_config_t *cfg);
|
||||||
bool config_store_load(const char *filename, device_config_t *cfg);
|
bool config_store_load(const char *filename, device_config_t *cfg);
|
||||||
|
|
||||||
|
|||||||
195
main/main.c
195
main/main.c
@@ -49,7 +49,7 @@ typedef struct
|
|||||||
{
|
{
|
||||||
uint8_t unit_id;
|
uint8_t unit_id;
|
||||||
char name[DEVICE_NAME_MAX_LEN];
|
char name[DEVICE_NAME_MAX_LEN];
|
||||||
char csv_path[128];
|
char *csv_path;
|
||||||
|
|
||||||
modbus_db_t db;
|
modbus_db_t db;
|
||||||
modbus_memory_t mem;
|
modbus_memory_t mem;
|
||||||
@@ -91,6 +91,41 @@ static bool file_exists(const char *path)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *build_spiffs_path(const char *filename)
|
||||||
|
{
|
||||||
|
const char *prefix = "/spiffs/";
|
||||||
|
char *path;
|
||||||
|
size_t prefix_len;
|
||||||
|
size_t filename_len;
|
||||||
|
|
||||||
|
if (filename == NULL || filename[0] == '\0')
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (filename[0] == '/')
|
||||||
|
{
|
||||||
|
filename_len = strlen(filename);
|
||||||
|
|
||||||
|
path = heap_caps_malloc(filename_len + 1U, MALLOC_CAP_8BIT);
|
||||||
|
if (path == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
memcpy(path, filename, filename_len + 1U);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix_len = strlen(prefix);
|
||||||
|
filename_len = strlen(filename);
|
||||||
|
|
||||||
|
path = heap_caps_malloc(prefix_len + filename_len + 1U, MALLOC_CAP_8BIT);
|
||||||
|
if (path == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
memcpy(path, prefix, prefix_len);
|
||||||
|
memcpy(path + prefix_len, filename, filename_len + 1U);
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
static void spiffs_list_files(void)
|
static void spiffs_list_files(void)
|
||||||
{
|
{
|
||||||
DIR *dir;
|
DIR *dir;
|
||||||
@@ -847,6 +882,7 @@ void app_main(void)
|
|||||||
|
|
||||||
ESP_LOGI(TAG, "Modbus Port: %u", (unsigned)g_device_cfg.modbus.port);
|
ESP_LOGI(TAG, "Modbus Port: %u", (unsigned)g_device_cfg.modbus.port);
|
||||||
ESP_LOGI(TAG, "Configured device_count = %u", (unsigned)g_device_cfg.modbus.device_count);
|
ESP_LOGI(TAG, "Configured device_count = %u", (unsigned)g_device_cfg.modbus.device_count);
|
||||||
|
ESP_LOGI(TAG, "Configured max_points per device = %u", (unsigned)g_device_cfg.modbus.max_points);
|
||||||
|
|
||||||
for (i = 0; i < g_device_cfg.modbus.device_count; i++)
|
for (i = 0; i < g_device_cfg.modbus.device_count; i++)
|
||||||
{
|
{
|
||||||
@@ -856,7 +892,7 @@ void app_main(void)
|
|||||||
g_device_cfg.devices[i].enabled ? 1 : 0,
|
g_device_cfg.devices[i].enabled ? 1 : 0,
|
||||||
(unsigned)g_device_cfg.devices[i].unit_id,
|
(unsigned)g_device_cfg.devices[i].unit_id,
|
||||||
g_device_cfg.devices[i].name,
|
g_device_cfg.devices[i].name,
|
||||||
g_device_cfg.devices[i].csv);
|
(g_device_cfg.devices[i].csv != NULL) ? g_device_cfg.devices[i].csv : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_device_cfg.modbus.device_count == 0U)
|
if (g_device_cfg.modbus.device_count == 0U)
|
||||||
@@ -932,10 +968,14 @@ void app_main(void)
|
|||||||
strncpy(dev->name, cfg_dev->name, sizeof(dev->name) - 1U);
|
strncpy(dev->name, cfg_dev->name, sizeof(dev->name) - 1U);
|
||||||
dev->name[sizeof(dev->name) - 1U] = '\0';
|
dev->name[sizeof(dev->name) - 1U] = '\0';
|
||||||
|
|
||||||
snprintf(dev->csv_path,
|
dev->csv_path = build_spiffs_path(cfg_dev->csv);
|
||||||
sizeof(dev->csv_path),
|
if (dev->csv_path == NULL)
|
||||||
"/spiffs/%s",
|
{
|
||||||
cfg_dev->csv);
|
ESP_LOGE(TAG, "Failed to allocate CSV path for device %u",
|
||||||
|
(unsigned)dev->unit_id);
|
||||||
|
heap_caps_free(dev);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG,
|
ESP_LOGI(TAG,
|
||||||
"Preparing device[%u]: unit_id=%u name=%s csv=%s",
|
"Preparing device[%u]: unit_id=%u name=%s csv=%s",
|
||||||
@@ -949,6 +989,8 @@ void app_main(void)
|
|||||||
ESP_LOGE(TAG, "CSV file not found for device %u: %s",
|
ESP_LOGE(TAG, "CSV file not found for device %u: %s",
|
||||||
(unsigned)dev->unit_id,
|
(unsigned)dev->unit_id,
|
||||||
dev->csv_path);
|
dev->csv_path);
|
||||||
|
if(dev->csv_path != NULL)
|
||||||
|
heap_caps_free(dev->csv_path);
|
||||||
heap_caps_free(dev);
|
heap_caps_free(dev);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -959,42 +1001,36 @@ void app_main(void)
|
|||||||
|
|
||||||
modbus_points_init(&dev->db);
|
modbus_points_init(&dev->db);
|
||||||
|
|
||||||
if (!modbus_points_load(&dev->db, dev->csv_path))
|
if (!modbus_points_load(&dev->db, dev->csv_path, g_device_cfg.modbus.max_points))
|
||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "Failed to load CSV for device %u: %s",
|
ESP_LOGE(TAG, "Failed to load CSV for device %u: %s",
|
||||||
(unsigned)dev->unit_id,
|
(unsigned)dev->unit_id,
|
||||||
dev->csv_path);
|
dev->csv_path);
|
||||||
|
|
||||||
|
if(dev->csv_path != NULL)
|
||||||
|
heap_caps_free(dev->csv_path);
|
||||||
heap_caps_free(dev);
|
heap_caps_free(dev);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Safety check: prevent memory overflow */
|
|
||||||
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);
|
|
||||||
|
|
||||||
ESP_LOGE(TAG, "Startup aborted to prevent memory corruption.");
|
|
||||||
|
|
||||||
heap_caps_free(dev);
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!modbus_memory_init(&dev->mem, &dev->db))
|
if (!modbus_memory_init(&dev->mem, &dev->db))
|
||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "Memory init failed for device %u",
|
ESP_LOGE(TAG, "Memory init failed for device %u",
|
||||||
(unsigned)dev->unit_id);
|
(unsigned)dev->unit_id);
|
||||||
|
|
||||||
|
modbus_points_reset(&dev->db);
|
||||||
|
|
||||||
|
if(dev->csv_path != NULL)
|
||||||
|
heap_caps_free(dev->csv_path);
|
||||||
heap_caps_free(dev);
|
heap_caps_free(dev);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Device %u (%s) loaded %u Modbus points from %s",
|
ESP_LOGI(TAG, "Device %u (%s) loaded %u / %u Modbus point entries from %s",
|
||||||
(unsigned)dev->unit_id,
|
(unsigned)dev->unit_id,
|
||||||
dev->name,
|
dev->name,
|
||||||
(unsigned)dev->db.count,
|
(unsigned)dev->db.count,
|
||||||
|
(unsigned)dev->db.capacity,
|
||||||
dev->csv_path);
|
dev->csv_path);
|
||||||
|
|
||||||
g_devices[g_device_count] = dev;
|
g_devices[g_device_count] = dev;
|
||||||
@@ -1020,17 +1056,23 @@ void app_main(void)
|
|||||||
|
|
||||||
{
|
{
|
||||||
size_t total_points_used = 0;
|
size_t total_points_used = 0;
|
||||||
size_t total_points_capacity = g_device_count * MODBUS_MAX_POINTS;
|
size_t total_points_capacity = 0;
|
||||||
size_t total_points_remaining = 0;
|
size_t total_points_remaining = 0;
|
||||||
|
|
||||||
size_t free_heap = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
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 largest_block = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
|
||||||
|
|
||||||
size_t point_struct_size = sizeof(modbus_point_t);
|
size_t point_struct_size = sizeof(modbus_point_t);
|
||||||
size_t heap_equivalent_points = 0;
|
size_t configured_points_per_device = g_device_cfg.modbus.max_points;
|
||||||
|
size_t configured_db_bytes_per_device = configured_points_per_device * point_struct_size;
|
||||||
|
size_t estimated_additional_devices_free_heap = 0;
|
||||||
|
size_t estimated_additional_devices_largest_block = 0;
|
||||||
|
|
||||||
if (point_struct_size > 0U)
|
if (configured_db_bytes_per_device > 0U)
|
||||||
heap_equivalent_points = free_heap / point_struct_size;
|
{
|
||||||
|
estimated_additional_devices_free_heap = free_heap / configured_db_bytes_per_device;
|
||||||
|
estimated_additional_devices_largest_block = largest_block / configured_db_bytes_per_device;
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < g_device_count; i++)
|
for (i = 0; i < g_device_count; i++)
|
||||||
{
|
{
|
||||||
@@ -1038,46 +1080,56 @@ void app_main(void)
|
|||||||
size_t remaining = 0;
|
size_t remaining = 0;
|
||||||
float pct = 0.0f;
|
float pct = 0.0f;
|
||||||
|
|
||||||
if (dev->db.count < MODBUS_MAX_POINTS)
|
if (dev == NULL)
|
||||||
remaining = MODBUS_MAX_POINTS - dev->db.count;
|
continue;
|
||||||
|
|
||||||
if (MODBUS_MAX_POINTS > 0U)
|
if (dev->db.count < dev->db.capacity)
|
||||||
pct = (100.0f * (float)dev->db.count) / (float)MODBUS_MAX_POINTS;
|
remaining = dev->db.capacity - dev->db.count;
|
||||||
|
|
||||||
|
if (dev->db.capacity > 0U)
|
||||||
|
pct = (100.0f * (float)dev->db.count) / (float)dev->db.capacity;
|
||||||
|
|
||||||
ESP_LOGI(TAG,
|
ESP_LOGI(TAG,
|
||||||
"Device %u (%s): used=%u / max=%u, remaining=%u (%.1f%% used)",
|
"Device %u (%s): used=%u / max=%u, remaining=%u (%.1f%% used)",
|
||||||
(unsigned)dev->unit_id,
|
(unsigned)dev->unit_id,
|
||||||
dev->name,
|
dev->name,
|
||||||
(unsigned)dev->db.count,
|
(unsigned)dev->db.count,
|
||||||
(unsigned)MODBUS_MAX_POINTS,
|
(unsigned)dev->db.capacity,
|
||||||
(unsigned)remaining,
|
(unsigned)remaining,
|
||||||
pct);
|
pct);
|
||||||
|
|
||||||
total_points_used += dev->db.count;
|
total_points_used += dev->db.count;
|
||||||
|
total_points_capacity += dev->db.capacity;
|
||||||
total_points_remaining += remaining;
|
total_points_remaining += remaining;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG,
|
ESP_LOGI(TAG,
|
||||||
"Configured capacity: used=%u / max=%u, remaining=%u",
|
"Configured point capacity: used=%u / max=%u, remaining=%u",
|
||||||
(unsigned)total_points_used,
|
(unsigned)total_points_used,
|
||||||
(unsigned)total_points_capacity,
|
(unsigned)total_points_capacity,
|
||||||
(unsigned)total_points_remaining);
|
(unsigned)total_points_remaining);
|
||||||
|
|
||||||
ESP_LOGI(TAG,
|
ESP_LOGI(TAG,
|
||||||
"Free heap: %u bytes, largest block: %u bytes, point size: %u bytes",
|
"Configured max_points per device=%u, point size=%u bytes, DB bytes per device=%u",
|
||||||
(unsigned)free_heap,
|
(unsigned)configured_points_per_device,
|
||||||
(unsigned)largest_block,
|
(unsigned)point_struct_size,
|
||||||
(unsigned)point_struct_size);
|
(unsigned)configured_db_bytes_per_device);
|
||||||
|
|
||||||
ESP_LOGI(TAG,
|
ESP_LOGI(TAG,
|
||||||
"Heap-equivalent points available (informational only): %u",
|
"Free heap=%u bytes, largest block=%u bytes",
|
||||||
(unsigned)heap_equivalent_points);
|
(unsigned)free_heap,
|
||||||
|
(unsigned)largest_block);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG,
|
||||||
|
"Estimated additional devices possible with current max_points: free_heap=%u, largest_block=%u",
|
||||||
|
(unsigned)estimated_additional_devices_free_heap,
|
||||||
|
(unsigned)estimated_additional_devices_largest_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "---------------------------------");
|
ESP_LOGI(TAG, "---------------------------------");
|
||||||
|
|
||||||
ESP_LOGI(TAG, "----- Point Cost Guide -----");
|
ESP_LOGI(TAG, "----- Point Cost Guide -----");
|
||||||
ESP_LOGI(TAG, "Internal point usage (counts against MODBUS_MAX_POINTS):");
|
ESP_LOGI(TAG, "Internal point usage (counts against configured max_points):");
|
||||||
ESP_LOGI(TAG, " data_type=bool -> 1 point");
|
ESP_LOGI(TAG, " data_type=bool -> 1 point");
|
||||||
ESP_LOGI(TAG, " data_type=uint16 -> 1 point");
|
ESP_LOGI(TAG, " data_type=uint16 -> 1 point");
|
||||||
ESP_LOGI(TAG, " data_type=uint32 -> 2 points");
|
ESP_LOGI(TAG, " data_type=uint32 -> 2 points");
|
||||||
@@ -1107,30 +1159,39 @@ void app_main(void)
|
|||||||
|
|
||||||
ESP_LOGI(TAG, "----- Modbus Memory Usage -----");
|
ESP_LOGI(TAG, "----- Modbus Memory Usage -----");
|
||||||
|
|
||||||
size_t total_points = 0;
|
|
||||||
|
|
||||||
for (size_t d = 0; d < g_device_count; d++)
|
|
||||||
{
|
{
|
||||||
const modbus_device_t *dev = g_devices[d];
|
size_t total_points = 0;
|
||||||
|
size_t total_capacity = 0;
|
||||||
|
|
||||||
if (dev == NULL)
|
for (size_t d = 0; d < g_device_count; d++)
|
||||||
continue;
|
{
|
||||||
|
const modbus_device_t *dev = g_devices[d];
|
||||||
|
float pct = 0.0f;
|
||||||
|
|
||||||
|
if (dev == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (dev->db.capacity > 0U)
|
||||||
|
pct = (100.0f * (float)dev->db.count) / (float)dev->db.capacity;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG,
|
||||||
|
"Device %u (%s): %u / %u points used (%.1f%%)",
|
||||||
|
(unsigned)dev->unit_id,
|
||||||
|
dev->name,
|
||||||
|
(unsigned)dev->db.count,
|
||||||
|
(unsigned)dev->db.capacity,
|
||||||
|
pct);
|
||||||
|
|
||||||
|
total_points += dev->db.count;
|
||||||
|
total_capacity += dev->db.capacity;
|
||||||
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG,
|
ESP_LOGI(TAG,
|
||||||
"Device %u (%s): %u / %u points used (%.1f%%)",
|
"TOTAL POINTS LOADED: %u / %u",
|
||||||
(unsigned)dev->unit_id,
|
(unsigned)total_points,
|
||||||
dev->name,
|
(unsigned)total_capacity);
|
||||||
(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, "--------------------------------");
|
ESP_LOGI(TAG, "--------------------------------");
|
||||||
|
|
||||||
if (g_device_count == 0U)
|
if (g_device_count == 0U)
|
||||||
|
|||||||
@@ -41,6 +41,17 @@ static bool str_equals(const char *a, const char *b)
|
|||||||
return strcmp(a, b) == 0;
|
return strcmp(a, b) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool line_starts_with_valid_point_type(const char *line)
|
||||||
|
{
|
||||||
|
if (line == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return strncmp(line, "Coil,", 5U) == 0 ||
|
||||||
|
strncmp(line, "Discrete_Input,", 15U) == 0 ||
|
||||||
|
strncmp(line, "Input_Register,", 15U) == 0 ||
|
||||||
|
strncmp(line, "Holding_Register,", 17U) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
static modbus_point_type_t parse_point_type(const char *s)
|
static modbus_point_type_t parse_point_type(const char *s)
|
||||||
{
|
{
|
||||||
if (str_equals(s, "Coil"))
|
if (str_equals(s, "Coil"))
|
||||||
@@ -413,7 +424,7 @@ static bool append_helper_points(modbus_db_t *db, const modbus_point_t *base_pt)
|
|||||||
helper.reg_value = 0U;
|
helper.reg_value = 0U;
|
||||||
|
|
||||||
|
|
||||||
if (db->count >= MODBUS_MAX_POINTS)
|
if (db->count >= db->capacity)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
||||||
@@ -602,7 +613,12 @@ static bool parse_csv_line(const char *line_in, modbus_point_t *pt)
|
|||||||
|
|
||||||
void modbus_points_init(modbus_db_t *db)
|
void modbus_points_init(modbus_db_t *db)
|
||||||
{
|
{
|
||||||
modbus_points_reset(db);
|
if (db == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
db->points = NULL;
|
||||||
|
db->count = 0;
|
||||||
|
db->capacity = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void modbus_points_reset(modbus_db_t *db)
|
void modbus_points_reset(modbus_db_t *db)
|
||||||
@@ -610,10 +626,17 @@ void modbus_points_reset(modbus_db_t *db)
|
|||||||
if (db == NULL)
|
if (db == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
memset(db, 0, sizeof(*db));
|
if (db->points != NULL)
|
||||||
|
{
|
||||||
|
free(db->points);
|
||||||
|
db->points = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
db->count = 0;
|
||||||
|
db->capacity = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool modbus_points_load(modbus_db_t *db, const char *filename)
|
bool modbus_points_load(modbus_db_t *db, const char *filename, size_t max_points)
|
||||||
{
|
{
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
char line[MODBUS_MAX_CSV_LINE_LEN];
|
char line[MODBUS_MAX_CSV_LINE_LEN];
|
||||||
@@ -629,8 +652,26 @@ bool modbus_points_load(modbus_db_t *db, const char *filename)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (max_points == 0U)
|
||||||
|
{
|
||||||
|
printf("CSV ERROR: max_points cannot be 0\n");
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
modbus_points_reset(db);
|
modbus_points_reset(db);
|
||||||
|
|
||||||
|
db->capacity = max_points;
|
||||||
|
db->points = calloc(db->capacity, sizeof(modbus_point_t));
|
||||||
|
if (db->points == NULL)
|
||||||
|
{
|
||||||
|
printf("CSV ERROR: failed to allocate point table (%u points, %u bytes)\n",
|
||||||
|
(unsigned)max_points,
|
||||||
|
(unsigned)(max_points * sizeof(modbus_point_t)));
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
while (fgets(line, sizeof(line), fp) != NULL)
|
while (fgets(line, sizeof(line), fp) != NULL)
|
||||||
{
|
{
|
||||||
modbus_point_t pt;
|
modbus_point_t pt;
|
||||||
@@ -654,53 +695,21 @@ bool modbus_points_load(modbus_db_t *db, const char *filename)
|
|||||||
if (line_num == 1U)
|
if (line_num == 1U)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (!line_starts_with_valid_point_type(line))
|
||||||
{
|
{
|
||||||
char temp_line[MODBUS_MAX_CSV_LINE_LEN];
|
printf("CSV WARNING line %u: skipping non-data line: %s\n",
|
||||||
char *fields[8];
|
(unsigned)line_num,
|
||||||
size_t field_count = 0;
|
line);
|
||||||
char *p;
|
continue;
|
||||||
bool all_fields_empty = true;
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
strncpy(temp_line, line, sizeof(temp_line) - 1U);
|
|
||||||
temp_line[sizeof(temp_line) - 1U] = '\0';
|
|
||||||
|
|
||||||
p = temp_line;
|
|
||||||
fields[field_count++] = p;
|
|
||||||
|
|
||||||
while (*p != '\0' && field_count < 8U)
|
|
||||||
{
|
|
||||||
if (*p == ',')
|
|
||||||
{
|
|
||||||
*p = '\0';
|
|
||||||
fields[field_count++] = p + 1;
|
|
||||||
}
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field_count == 8U)
|
|
||||||
{
|
|
||||||
for (i = 0; i < field_count; i++)
|
|
||||||
{
|
|
||||||
trim_whitespace(fields[i]);
|
|
||||||
if (fields[i][0] != '\0')
|
|
||||||
{
|
|
||||||
all_fields_empty = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (all_fields_empty)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (db->count >= MODBUS_MAX_POINTS)
|
if (db->count >= db->capacity)
|
||||||
{
|
{
|
||||||
printf("CSV ERROR line %u: exceeded MODBUS_MAX_POINTS (%u)\n",
|
printf("CSV ERROR line %u: exceeded configured max_points (%u)\n",
|
||||||
(unsigned)line_num,
|
(unsigned)line_num,
|
||||||
(unsigned)MODBUS_MAX_POINTS);
|
(unsigned)db->capacity);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
modbus_points_reset(db);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -710,6 +719,7 @@ bool modbus_points_load(modbus_db_t *db, const char *filename)
|
|||||||
(unsigned)line_num,
|
(unsigned)line_num,
|
||||||
line);
|
line);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
modbus_points_reset(db);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -721,6 +731,7 @@ bool modbus_points_load(modbus_db_t *db, const char *filename)
|
|||||||
(unsigned)pt.offset);
|
(unsigned)pt.offset);
|
||||||
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
modbus_points_reset(db);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -736,6 +747,7 @@ bool modbus_points_load(modbus_db_t *db, const char *filename)
|
|||||||
(unsigned)pt.pics_offset,
|
(unsigned)pt.pics_offset,
|
||||||
(unsigned)pt.pics_reg_span);
|
(unsigned)pt.pics_reg_span);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
modbus_points_reset(db);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,6 +758,7 @@ bool modbus_points_load(modbus_db_t *db, const char *filename)
|
|||||||
printf("CSV ERROR line %u: failed to append helper points\n",
|
printf("CSV ERROR line %u: failed to append helper points\n",
|
||||||
(unsigned)line_num);
|
(unsigned)line_num);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
modbus_points_reset(db);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include "modbus_memory.h"
|
#include "modbus_memory.h"
|
||||||
|
|
||||||
#define MODBUS_MAX_POINTS 80 /* Max point entries per device, including helper registers */
|
|
||||||
/*
|
/*
|
||||||
Modbus Data Type
|
Modbus Data Type
|
||||||
- bool = 1 register
|
- bool = 1 register
|
||||||
@@ -23,7 +22,7 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define MODBUS_MAX_CSV_LINE_LEN 256
|
#define MODBUS_MAX_CSV_LINE_LEN 512
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
@@ -86,8 +85,9 @@ typedef struct modbus_point
|
|||||||
|
|
||||||
typedef struct modbus_db
|
typedef struct modbus_db
|
||||||
{
|
{
|
||||||
modbus_point_t points[MODBUS_MAX_POINTS];
|
modbus_point_t *points;
|
||||||
size_t count;
|
size_t count;
|
||||||
|
size_t capacity;
|
||||||
} modbus_db_t;
|
} modbus_db_t;
|
||||||
|
|
||||||
/* Initialize / clear point database */
|
/* Initialize / clear point database */
|
||||||
@@ -95,7 +95,7 @@ void modbus_points_init(modbus_db_t *db);
|
|||||||
void modbus_points_reset(modbus_db_t *db);
|
void modbus_points_reset(modbus_db_t *db);
|
||||||
|
|
||||||
/* Load points from CSV */
|
/* Load points from CSV */
|
||||||
bool modbus_points_load(modbus_db_t *db, const char *filename);
|
bool modbus_points_load(modbus_db_t *db, const char *filename, size_t max_points);
|
||||||
|
|
||||||
/* Lookup helpers */
|
/* Lookup helpers */
|
||||||
const modbus_point_t *modbus_points_find_by_type_offset(const modbus_db_t *db,
|
const modbus_point_t *modbus_points_find_by_type_offset(const modbus_db_t *db,
|
||||||
|
|||||||
@@ -2247,7 +2247,8 @@ CONFIG_LWIP_TIMERS_ONDEMAND=y
|
|||||||
CONFIG_LWIP_ND6=y
|
CONFIG_LWIP_ND6=y
|
||||||
# default:
|
# default:
|
||||||
# CONFIG_LWIP_FORCE_ROUTER_FORWARDING is not set
|
# CONFIG_LWIP_FORCE_ROUTER_FORWARDING is not set
|
||||||
CONFIG_LWIP_MAX_SOCKETS=16
|
# default:
|
||||||
|
CONFIG_LWIP_MAX_SOCKETS=10
|
||||||
# default:
|
# default:
|
||||||
# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set
|
# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set
|
||||||
# default:
|
# default:
|
||||||
@@ -3103,8 +3104,7 @@ CONFIG_SPIFFS_GC_MAX_RUNS=10
|
|||||||
# CONFIG_SPIFFS_GC_STATS is not set
|
# CONFIG_SPIFFS_GC_STATS is not set
|
||||||
# default:
|
# default:
|
||||||
CONFIG_SPIFFS_PAGE_SIZE=256
|
CONFIG_SPIFFS_PAGE_SIZE=256
|
||||||
# default:
|
CONFIG_SPIFFS_OBJ_NAME_LEN=128
|
||||||
CONFIG_SPIFFS_OBJ_NAME_LEN=32
|
|
||||||
# default:
|
# default:
|
||||||
# CONFIG_SPIFFS_FOLLOW_SYMLINKS is not set
|
# CONFIG_SPIFFS_FOLLOW_SYMLINKS is not set
|
||||||
# default:
|
# default:
|
||||||
|
|||||||
1
sdkconfig.defaults
Normal file
1
sdkconfig.defaults
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CONFIG_SPIFFS_OBJ_NAME_LEN=128
|
||||||
Reference in New Issue
Block a user