#include "config_store.h" #include #include #include #include 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; }