502 lines
12 KiB
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(§ion[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;
|
|
} |