Files
Modbus/main/config_store.c

502 lines
12 KiB
C

#include "config_store.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
static void trim_whitespace(char *s)
{
char *start;
char *end;
if (s == NULL || *s == '\0')
return;
start = s;
while (*start != '\0' && isspace((unsigned char)*start))
start++;
if (start != s)
memmove(s, start, strlen(start) + 1U);
if (*s == '\0')
return;
end = s + strlen(s) - 1;
while (end >= s && isspace((unsigned char)*end))
{
*end = '\0';
end--;
}
}
static bool str_ieq(const char *a, const char *b)
{
unsigned char ca, cb;
if (a == NULL || b == NULL)
return false;
while (*a != '\0' && *b != '\0')
{
ca = (unsigned char)tolower((unsigned char)*a);
cb = (unsigned char)tolower((unsigned char)*b);
if (ca != cb)
return false;
a++;
b++;
}
return (*a == '\0' && *b == '\0');
}
static bool parse_bool(const char *s, bool *out)
{
if (s == NULL || out == NULL)
return false;
if (str_ieq(s, "true") || str_ieq(s, "yes") || strcmp(s, "1") == 0)
{
*out = true;
return true;
}
if (str_ieq(s, "false") || str_ieq(s, "no") || strcmp(s, "0") == 0)
{
*out = false;
return true;
}
return false;
}
static bool parse_u32(const char *s, uint32_t *out)
{
char *endptr = NULL;
unsigned long v;
if (s == NULL || out == NULL || *s == '\0')
return false;
v = strtoul(s, &endptr, 10);
if (endptr == NULL || *endptr != '\0')
return false;
*out = (uint32_t)v;
return true;
}
static void copy_str(char *dst, size_t dst_size, const char *src)
{
if (dst == NULL || dst_size == 0U)
return;
if (src == NULL)
src = "";
strncpy(dst, src, dst_size - 1U);
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)
{
unsigned long idx;
char *endptr = NULL;
if (section == NULL)
return -1;
if (strlen(section) < 8U)
return -1;
if (tolower((unsigned char)section[0]) != 'd' ||
tolower((unsigned char)section[1]) != 'e' ||
tolower((unsigned char)section[2]) != 'v' ||
tolower((unsigned char)section[3]) != 'i' ||
tolower((unsigned char)section[4]) != 'c' ||
tolower((unsigned char)section[5]) != 'e')
{
return -1;
}
if (section[6] != ' ')
return -1;
idx = strtoul(&section[7], &endptr, 10);
if (endptr == NULL || *endptr != '\0')
return -1;
if (idx == 0UL || idx > MAX_VIRTUAL_DEVICES)
return -1;
return (int)(idx - 1UL);
}
static void set_device_defaults(virtual_device_settings_t *dev, uint8_t index)
{
char default_name[DEVICE_NAME_MAX_LEN];
if (dev == NULL)
return;
memset(dev, 0, sizeof(*dev));
dev->enabled = false;
dev->unit_id = (uint8_t)(index + 1U);
snprintf(default_name, sizeof(default_name), "Device_%u", (unsigned)(index + 1U));
copy_str(dev->name, sizeof(dev->name), default_name);
dev->csv = NULL;
}
void config_store_set_defaults(device_config_t *cfg)
{
size_t i;
if (cfg == NULL)
return;
memset(cfg, 0, sizeof(*cfg));
copy_str(cfg->wifi.ssid, sizeof(cfg->wifi.ssid), "YOUR_WIFI_SSID");
copy_str(cfg->wifi.password, sizeof(cfg->wifi.password), "YOUR_WIFI_PASSWORD");
cfg->wifi.dhcp = true;
copy_str(cfg->wifi.static_ip, sizeof(cfg->wifi.static_ip), "192.168.1.50");
copy_str(cfg->wifi.gateway, sizeof(cfg->wifi.gateway), "192.168.1.1");
copy_str(cfg->wifi.netmask, sizeof(cfg->wifi.netmask), "255.255.255.0");
cfg->modbus.port = 502;
cfg->modbus.max_clients = 4;
cfg->modbus.device_count = 1;
cfg->modbus.max_points = 150;
for (i = 0; i < MAX_VIRTUAL_DEVICES; 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)
{
FILE *fp;
char *line = NULL;
char section[32];
if (filename == NULL || cfg == NULL)
return false;
config_store_free(cfg);
config_store_set_defaults(cfg);
memset(section, 0, sizeof(section));
fp = fopen(filename, "r");
if (fp == NULL)
return false;
while (read_dynamic_line(fp, &line))
{
char *eq;
char *key;
char *value;
line[strcspn(line, "\r\n")] = '\0';
trim_whitespace(line);
if (line[0] == '\0')
{
free(line);
line = NULL;
continue;
}
if (line[0] == ';' || line[0] == '#')
{
free(line);
line = NULL;
continue;
}
if (line[0] == '[')
{
size_t len = strlen(line);
if (len >= 3U && line[len - 1U] == ']')
{
line[len - 1U] = '\0';
copy_str(section, sizeof(section), &line[1]);
trim_whitespace(section);
}
free(line);
line = NULL;
continue;
}
eq = strchr(line, '=');
if (eq == NULL)
{
free(line);
line = NULL;
continue;
}
*eq = '\0';
key = line;
value = eq + 1;
trim_whitespace(key);
trim_whitespace(value);
if (str_ieq(section, "wifi"))
{
if (str_ieq(key, "ssid"))
copy_str(cfg->wifi.ssid, sizeof(cfg->wifi.ssid), value);
else if (str_ieq(key, "password"))
copy_str(cfg->wifi.password, sizeof(cfg->wifi.password), value);
else if (str_ieq(key, "dhcp"))
{
bool b;
if (parse_bool(value, &b))
cfg->wifi.dhcp = b;
}
else if (str_ieq(key, "static_ip"))
copy_str(cfg->wifi.static_ip, sizeof(cfg->wifi.static_ip), value);
else if (str_ieq(key, "gateway"))
copy_str(cfg->wifi.gateway, sizeof(cfg->wifi.gateway), value);
else if (str_ieq(key, "netmask"))
copy_str(cfg->wifi.netmask, sizeof(cfg->wifi.netmask), value);
}
else if (str_ieq(section, "modbus"))
{
uint32_t v;
if (str_ieq(key, "port"))
{
if (parse_u32(value, &v) && v <= 65535U)
cfg->modbus.port = (uint16_t)v;
}
else if (str_ieq(key, "max_clients"))
{
if (parse_u32(value, &v) && v > 0U && v <= 16U)
cfg->modbus.max_clients = (uint8_t)v;
}
else if (str_ieq(key, "device_count"))
{
if (parse_u32(value, &v) && v > 0U)
{
if (v > MAX_VIRTUAL_DEVICES)
cfg->modbus.device_count = MAX_VIRTUAL_DEVICES;
else
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
{
int dev_index = parse_device_section_index(section);
if (dev_index >= 0)
{
virtual_device_settings_t *dev = &cfg->devices[dev_index];
uint32_t v;
if (str_ieq(key, "enabled"))
{
bool b;
if (parse_bool(value, &b))
dev->enabled = b;
}
else if (str_ieq(key, "unit_id"))
{
if (parse_u32(value, &v) && v > 0U && v <= 255U)
dev->unit_id = (uint8_t)v;
}
else if (str_ieq(key, "name"))
{
copy_str(dev->name, sizeof(dev->name), value);
}
else if (str_ieq(key, "csv"))
{
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);
if (cfg->wifi.ssid[0] == '\0')
{
printf("CONFIG ERROR: wifi.ssid is empty\n");
return false;
}
if (cfg->modbus.port == 0U)
return false;
if (cfg->modbus.max_clients == 0U)
return false;
if (cfg->modbus.max_points == 0U)
return false;
if (cfg->modbus.device_count == 0U || cfg->modbus.device_count > MAX_VIRTUAL_DEVICES)
return false;
for (uint8_t i = 0; i < cfg->modbus.device_count; i++)
{
uint8_t j;
virtual_device_settings_t *dev = &cfg->devices[i];
if (!dev->enabled)
continue;
if (dev->unit_id == 0U)
{
printf("CONFIG ERROR: enable device %u has invalid unit_id 0\n",
(unsigned)(i + 1U));
return false;
}
if (dev->csv == NULL || dev->csv[0] == '\0')
{
printf("CONFIG ERROR: enabled device %u has empty csv\n", (unsigned)(i + 1U));
return false;
}
if (dev->name[0] == '\0')
{
char default_name[DEVICE_NAME_MAX_LEN];
snprintf(default_name, sizeof(default_name), "Device_%u", (unsigned)(i + 1U));
copy_str(dev->name, sizeof(dev->name), default_name);
}
for (j = (uint8_t)(i + 1U); j < cfg->modbus.device_count; j++)
{
if (!cfg->devices[j].enabled)
continue;
if (dev->unit_id == cfg->devices[j].unit_id)
{
printf("CONFIG ERROR: duplicate unit_id %u between device %u and device %u\n",
(unsigned)dev->unit_id,
(unsigned)(i + 1U),
(unsigned)(j + 1U));
}
}
}
return true;
}