428 lines
9.7 KiB
C
428 lines
9.7 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 bool parse_i32(const char *s, int *out)
|
|
{
|
|
char *endptr = NULL;
|
|
long v;
|
|
|
|
if (s == NULL || out == NULL || *s == '\0')
|
|
return false;
|
|
|
|
v = strtol(s, &endptr, 10);
|
|
if (endptr == NULL || *endptr != '\0')
|
|
return false;
|
|
|
|
*out = (int)v;
|
|
return true;
|
|
}
|
|
|
|
#define CONFIG_SPIFFS_BASE_PATH "/spiffs"
|
|
|
|
static void copy_csv_path(char *dst, size_t dst_size, const char *src)
|
|
{
|
|
if (dst == NULL || dst_size == 0U)
|
|
return;
|
|
|
|
if (src == NULL)
|
|
src = "";
|
|
|
|
if (src[0] == '/')
|
|
{
|
|
copy_str(dst, dst_size, src);
|
|
}
|
|
else
|
|
{
|
|
snprintf(dst, dst_size, CONFIG_SPIFFS_BASE_PATH "/%s", src);
|
|
dst[dst_size - 1U] = '\0';
|
|
}
|
|
}
|
|
|
|
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 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 bool parse_parity(const char *s, modbus_rtu_parity_t *out)
|
|
{
|
|
if (s == NULL || out == NULL)
|
|
return false;
|
|
|
|
if (str_ieq(s, "none"))
|
|
{
|
|
*out = MB_RTU_PARITY_NONE;
|
|
return true;
|
|
}
|
|
|
|
if (str_ieq(s, "even"))
|
|
{
|
|
*out = MB_RTU_PARITY_EVEN;
|
|
return true;
|
|
}
|
|
|
|
if (str_ieq(s, "odd"))
|
|
{
|
|
*out = MB_RTU_PARITY_ODD;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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[0] = '\0';
|
|
}
|
|
|
|
void config_store_set_defaults(device_config_t *cfg)
|
|
{
|
|
size_t i;
|
|
|
|
if (cfg == NULL)
|
|
return;
|
|
|
|
memset(cfg, 0, sizeof(*cfg));
|
|
|
|
cfg->rtu.baud_rate = 19200U;
|
|
cfg->rtu.parity = MB_RTU_PARITY_NONE;
|
|
cfg->rtu.stop_bits = 1U;
|
|
cfg->rtu.uart_num = 1U;
|
|
cfg->rtu.tx_pin = 17;
|
|
cfg->rtu.rx_pin = 16;
|
|
cfg->rtu.de_pin = -1; /* shield AUTO mode */
|
|
|
|
cfg->modbus.device_count = 1U;
|
|
|
|
for (i = 0; i < MAX_VIRTUAL_DEVICES; i++)
|
|
set_device_defaults(&cfg->devices[i], (uint8_t)i);
|
|
}
|
|
|
|
bool config_store_load(const char *filename, device_config_t *cfg)
|
|
{
|
|
FILE *fp;
|
|
char line[256];
|
|
char section[32];
|
|
|
|
if (filename == NULL || cfg == NULL)
|
|
return false;
|
|
|
|
config_store_set_defaults(cfg);
|
|
memset(section, 0, sizeof(section));
|
|
|
|
fp = fopen(filename, "r");
|
|
if (fp == NULL)
|
|
return false;
|
|
|
|
while (fgets(line, sizeof(line), fp) != NULL)
|
|
{
|
|
char *eq;
|
|
char *key;
|
|
char *value;
|
|
|
|
line[strcspn(line, "\r\n")] = '\0';
|
|
trim_whitespace(line);
|
|
|
|
if (line[0] == '\0')
|
|
continue;
|
|
|
|
if (line[0] == ';' || line[0] == '#')
|
|
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);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
eq = strchr(line, '=');
|
|
if (eq == NULL)
|
|
continue;
|
|
|
|
*eq = '\0';
|
|
key = line;
|
|
value = eq + 1;
|
|
|
|
trim_whitespace(key);
|
|
trim_whitespace(value);
|
|
|
|
if (str_ieq(section, "rtu"))
|
|
{
|
|
uint32_t v;
|
|
int iv;
|
|
modbus_rtu_parity_t parity;
|
|
|
|
if (str_ieq(key, "baud_rate"))
|
|
{
|
|
if (parse_u32(value, &v) && v > 0U)
|
|
cfg->rtu.baud_rate = v;
|
|
}
|
|
else if (str_ieq(key, "parity"))
|
|
{
|
|
if (parse_parity(value, &parity))
|
|
cfg->rtu.parity = parity;
|
|
}
|
|
else if (str_ieq(key, "stop_bits"))
|
|
{
|
|
if (parse_u32(value, &v) && (v == 1U || v == 2U))
|
|
cfg->rtu.stop_bits = (uint8_t)v;
|
|
}
|
|
else if (str_ieq(key, "uart_num"))
|
|
{
|
|
if (parse_u32(value, &v) && v <= 2U)
|
|
cfg->rtu.uart_num = (uint8_t)v;
|
|
}
|
|
else if (str_ieq(key, "tx_pin"))
|
|
{
|
|
if (parse_i32(value, &iv))
|
|
cfg->rtu.tx_pin = iv;
|
|
}
|
|
else if (str_ieq(key, "rx_pin"))
|
|
{
|
|
if (parse_i32(value, &iv))
|
|
cfg->rtu.rx_pin = iv;
|
|
}
|
|
else if (str_ieq(key, "de_pin"))
|
|
{
|
|
if (parse_i32(value, &iv))
|
|
cfg->rtu.de_pin = iv;
|
|
}
|
|
}
|
|
else if (str_ieq(section, "modbus"))
|
|
{
|
|
uint32_t v;
|
|
|
|
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
|
|
{
|
|
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 <= 247U)
|
|
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"))
|
|
{
|
|
copy_csv_path(dev->csv, sizeof(dev->csv), value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
if (cfg->rtu.baud_rate == 0U)
|
|
return false;
|
|
|
|
if (cfg->rtu.stop_bits != 1U && cfg->rtu.stop_bits != 2U)
|
|
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 || dev->unit_id > 247U)
|
|
return false;
|
|
|
|
if (dev->csv[0] == '\0')
|
|
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)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
} |