Modbus TCP - Multi Devices
This commit is contained in:
864
main/modbus_points.c
Normal file
864
main/modbus_points.c
Normal file
@@ -0,0 +1,864 @@
|
||||
#include "modbus_points.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* ---------- local helpers ---------- */
|
||||
|
||||
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_equals(const char *a, const char *b)
|
||||
{
|
||||
if (a == NULL || b == NULL)
|
||||
return false;
|
||||
|
||||
return strcmp(a, b) == 0;
|
||||
}
|
||||
|
||||
static modbus_point_type_t parse_point_type(const char *s)
|
||||
{
|
||||
if (str_equals(s, "Coil"))
|
||||
return MB_POINT_COIL;
|
||||
if (str_equals(s, "Discrete_Input"))
|
||||
return MB_POINT_DISCRETE_INPUT;
|
||||
if (str_equals(s, "Input_Register"))
|
||||
return MB_POINT_INPUT_REGISTER;
|
||||
if (str_equals(s, "Holding_Register"))
|
||||
return MB_POINT_HOLDING_REGISTER;
|
||||
|
||||
return MB_POINT_INVALID;
|
||||
}
|
||||
|
||||
static modbus_data_type_t parse_data_type(const char *s)
|
||||
{
|
||||
if (str_equals(s, "bool"))
|
||||
return MB_DATA_BOOL;
|
||||
if (str_equals(s, "uint16"))
|
||||
return MB_DATA_UINT16;
|
||||
if (str_equals(s, "uint32"))
|
||||
return MB_DATA_UINT32;
|
||||
if (str_equals(s, "float"))
|
||||
return MB_DATA_FLOAT;
|
||||
if (str_equals(s, "double"))
|
||||
return MB_DATA_DOUBLE;
|
||||
|
||||
return MB_DATA_INVALID;
|
||||
}
|
||||
|
||||
static pics_data_type_t parse_pics_data_type(const char *s)
|
||||
{
|
||||
if (str_equals(s, "Boolean"))
|
||||
return PICS_DATA_BOOLEAN;
|
||||
|
||||
if (str_equals(s, "Integer"))
|
||||
return PICS_DATA_INTEGER;
|
||||
|
||||
if (str_equals(s, "Float"))
|
||||
return PICS_DATA_FLOAT;
|
||||
|
||||
if (str_equals(s, "String"))
|
||||
return PICS_DATA_STRING;
|
||||
|
||||
if (s == NULL || *s == '\0')
|
||||
return PICS_DATA_NONE;
|
||||
|
||||
return PICS_DATA_INVALID;
|
||||
}
|
||||
|
||||
static bool parse_u32(const char *s, uint32_t *value)
|
||||
{
|
||||
char *endptr;
|
||||
unsigned long v;
|
||||
|
||||
if (s == NULL || value == NULL || *s == '\0')
|
||||
return false;
|
||||
|
||||
v = strtoul(s, &endptr, 10);
|
||||
if (*endptr != '\0')
|
||||
return false;
|
||||
|
||||
*value = (uint32_t)v;
|
||||
return true;
|
||||
}
|
||||
|
||||
static modbus_point_type_t infer_point_type_from_address(uint32_t raw_address)
|
||||
{
|
||||
uint32_t family_digit;
|
||||
|
||||
if (raw_address == 0U)
|
||||
return MB_POINT_INVALID;
|
||||
|
||||
if (raw_address < 10000U)
|
||||
return MB_POINT_COIL;
|
||||
|
||||
if (raw_address >= 100000U)
|
||||
family_digit = raw_address / 100000U;
|
||||
else
|
||||
family_digit = raw_address / 10000U;
|
||||
|
||||
switch (family_digit)
|
||||
{
|
||||
case 1U:
|
||||
return MB_POINT_DISCRETE_INPUT;
|
||||
case 3U:
|
||||
return MB_POINT_INPUT_REGISTER;
|
||||
case 4U:
|
||||
return MB_POINT_HOLDING_REGISTER;
|
||||
default:
|
||||
return MB_POINT_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static bool normalize_address(modbus_point_type_t type, uint32_t raw_address, uint16_t *offset)
|
||||
{
|
||||
uint32_t index_1_based;
|
||||
uint32_t off;
|
||||
uint32_t family_digit = 0U;
|
||||
|
||||
if (offset == NULL || raw_address == 0U)
|
||||
return false;
|
||||
|
||||
if (raw_address >= 100000U)
|
||||
{
|
||||
family_digit = raw_address / 100000U;
|
||||
index_1_based = raw_address % 100000U;
|
||||
}
|
||||
else if (raw_address >= 10000U)
|
||||
{
|
||||
family_digit = raw_address / 10000U;
|
||||
index_1_based = raw_address % 10000U;
|
||||
}
|
||||
else
|
||||
{
|
||||
family_digit = 0U;
|
||||
index_1_based = raw_address;
|
||||
}
|
||||
|
||||
if (index_1_based == 0U)
|
||||
return false;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MB_POINT_COIL:
|
||||
if (family_digit != 0U)
|
||||
return false;
|
||||
off = index_1_based - 1U;
|
||||
break;
|
||||
|
||||
case MB_POINT_DISCRETE_INPUT:
|
||||
if (family_digit != 1U)
|
||||
return false;
|
||||
off = index_1_based - 1U;
|
||||
break;
|
||||
|
||||
case MB_POINT_INPUT_REGISTER:
|
||||
if (family_digit != 3U)
|
||||
return false;
|
||||
off = index_1_based - 1U;
|
||||
break;
|
||||
|
||||
case MB_POINT_HOLDING_REGISTER:
|
||||
if (family_digit != 4U)
|
||||
return false;
|
||||
off = index_1_based - 1U;
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (off > 0xFFFFU)
|
||||
return false;
|
||||
|
||||
*offset = (uint16_t)off;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool validate_type_and_datatype(modbus_point_type_t type, modbus_data_type_t data_type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MB_POINT_COIL:
|
||||
case MB_POINT_DISCRETE_INPUT:
|
||||
return data_type == MB_DATA_BOOL;
|
||||
|
||||
case MB_POINT_INPUT_REGISTER:
|
||||
case MB_POINT_HOLDING_REGISTER:
|
||||
return data_type == MB_DATA_UINT16 ||
|
||||
data_type == MB_DATA_UINT32 ||
|
||||
data_type == MB_DATA_FLOAT ||
|
||||
data_type == MB_DATA_DOUBLE;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t data_type_reg_span(modbus_data_type_t data_type)
|
||||
{
|
||||
switch (data_type)
|
||||
{
|
||||
case MB_DATA_BOOL:
|
||||
case MB_DATA_UINT16:
|
||||
return 1U;
|
||||
|
||||
case MB_DATA_UINT32:
|
||||
case MB_DATA_FLOAT:
|
||||
return 2U;
|
||||
|
||||
case MB_DATA_DOUBLE:
|
||||
return 4U;
|
||||
|
||||
default:
|
||||
return 0U;
|
||||
}
|
||||
}
|
||||
|
||||
static bool db_has_duplicate_name(const modbus_db_t *db, const char *name)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (db == NULL || name == NULL)
|
||||
return false;
|
||||
|
||||
for (i = 0; i < db->count; i++)
|
||||
{
|
||||
if (strcmp(db->points[i].name, name) == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool db_has_duplicate_type_offset(const modbus_db_t *db,
|
||||
modbus_point_type_t type,
|
||||
uint16_t offset)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (db == NULL)
|
||||
return false;
|
||||
|
||||
for (i = 0; i < db->count; i++)
|
||||
{
|
||||
if (db->points[i].type == type && db->points[i].offset == offset)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ranges_overlap(uint16_t start_a, uint8_t span_a,
|
||||
uint16_t start_b, uint8_t span_b)
|
||||
{
|
||||
uint32_t end_a;
|
||||
uint32_t end_b;
|
||||
|
||||
if (span_a == 0U || span_b == 0U)
|
||||
return false;
|
||||
|
||||
end_a = (uint32_t)start_a + (uint32_t)span_a - 1U;
|
||||
end_b = (uint32_t)start_b + (uint32_t)span_b - 1U;
|
||||
|
||||
return !((end_a < start_b) || (end_b < start_a));
|
||||
}
|
||||
|
||||
const modbus_point_t *modbus_points_find_by_pics_offset(const modbus_db_t *db,
|
||||
modbus_point_type_t type,
|
||||
uint16_t offset)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (db == NULL)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < db->count; i++)
|
||||
{
|
||||
if (db->points[i].has_pics_address &&
|
||||
db->points[i].pics_type == type &&
|
||||
db->points[i].pics_offset == offset)
|
||||
return &db->points[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const modbus_point_t *modbus_points_find_by_pics_range(const modbus_db_t *db,
|
||||
modbus_point_type_t type,
|
||||
uint16_t offset)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (db == NULL)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < db->count; i++)
|
||||
{
|
||||
const modbus_point_t *pt = &db->points[i];
|
||||
uint32_t end_offset;
|
||||
|
||||
if (!pt->has_pics_address || pt->pics_reg_span == 0U)
|
||||
continue;
|
||||
|
||||
if (pt->pics_type != type)
|
||||
continue;
|
||||
|
||||
end_offset = (uint32_t)pt->pics_offset + (uint32_t)pt->pics_reg_span - 1U;
|
||||
|
||||
if ((uint32_t)offset >= (uint32_t)pt->pics_offset &&
|
||||
(uint32_t)offset <= end_offset)
|
||||
return pt;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool db_has_duplicate_pics_range(const modbus_db_t *db,
|
||||
modbus_point_type_t pics_type,
|
||||
uint16_t pics_offset,
|
||||
uint8_t pics_reg_span)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (db == NULL || pics_reg_span == 0U)
|
||||
return false;
|
||||
|
||||
for (i = 0; i < db->count; i++)
|
||||
{
|
||||
const modbus_point_t *pt = &db->points[i];
|
||||
|
||||
if (!pt->has_pics_address || pt->pics_reg_span == 0U)
|
||||
continue;
|
||||
|
||||
if (pt->pics_type != pics_type)
|
||||
continue;
|
||||
|
||||
if (ranges_overlap(pt->pics_offset, pt->pics_reg_span,
|
||||
pics_offset, pics_reg_span))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool validate_control_mapping(const modbus_point_t *pt)
|
||||
{
|
||||
if (pt == NULL)
|
||||
return false;
|
||||
|
||||
if (!pt->has_control_address)
|
||||
return true;
|
||||
|
||||
if (!modbus_points_is_writable_type(pt->control_type))
|
||||
return false;
|
||||
|
||||
switch (pt->type)
|
||||
{
|
||||
case MB_POINT_DISCRETE_INPUT:
|
||||
return pt->control_type == MB_POINT_COIL;
|
||||
|
||||
case MB_POINT_INPUT_REGISTER:
|
||||
return pt->control_type == MB_POINT_HOLDING_REGISTER;
|
||||
|
||||
case MB_POINT_COIL:
|
||||
case MB_POINT_HOLDING_REGISTER:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool append_helper_points(modbus_db_t *db, const modbus_point_t *base_pt)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
if (db == NULL || base_pt == NULL)
|
||||
return false;
|
||||
|
||||
if (base_pt->reg_span <= 1U)
|
||||
return true;
|
||||
|
||||
for (i = 1U; i < base_pt->reg_span; i++)
|
||||
{
|
||||
modbus_point_t helper = *base_pt;
|
||||
|
||||
helper.raw_address = base_pt->raw_address + (uint32_t)i;
|
||||
helper.offset = (uint16_t)(base_pt->offset + i);
|
||||
helper.data_type = MB_DATA_UINT16;
|
||||
helper.reg_span = 1U;
|
||||
helper.is_helper = true;
|
||||
helper.has_control_address = false;
|
||||
helper.control_raw_address = 0U;
|
||||
helper.control_type = MB_POINT_INVALID;
|
||||
helper.control_offset = 0U;
|
||||
helper.has_pics_address = false;
|
||||
helper.pics_raw_address = 0U;
|
||||
helper.pics_type = MB_POINT_INVALID;
|
||||
helper.pics_offset = 0U;
|
||||
helper.pics_data_type = PICS_DATA_NONE;
|
||||
helper.pics_reg_span = 0U;
|
||||
memset(helper.pics_words, 0, sizeof(helper.pics_words));
|
||||
helper.bit_value = 0U;
|
||||
helper.reg_value = 0U;
|
||||
|
||||
snprintf(helper.name,
|
||||
sizeof(helper.name),
|
||||
"%.25s__w%u",
|
||||
base_pt->name,
|
||||
(unsigned)(i + 1U));
|
||||
|
||||
if (db->count >= MODBUS_MAX_POINTS)
|
||||
return false;
|
||||
|
||||
if (db_has_duplicate_name(db, helper.name))
|
||||
return false;
|
||||
|
||||
if (db_has_duplicate_type_offset(db, helper.type, helper.offset))
|
||||
return false;
|
||||
|
||||
db->points[db->count++] = helper;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_csv_line(const char *line_in, modbus_point_t *pt)
|
||||
{
|
||||
char line[MODBUS_MAX_CSV_LINE_LEN];
|
||||
char *fields[8];
|
||||
size_t field_count = 0;
|
||||
char *p;
|
||||
uint32_t raw_address;
|
||||
|
||||
if (line_in == NULL || pt == NULL)
|
||||
return false;
|
||||
|
||||
strncpy(line, line_in, sizeof(line) - 1U);
|
||||
line[sizeof(line) - 1U] = '\0';
|
||||
|
||||
p = 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)
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < field_count; i++)
|
||||
trim_whitespace(fields[i]);
|
||||
|
||||
memset(pt, 0, sizeof(*pt));
|
||||
pt->control_type = MB_POINT_INVALID;
|
||||
pt->bit_value = 0U;
|
||||
pt->reg_value = 0U;
|
||||
|
||||
pt->type = parse_point_type(fields[0]);
|
||||
if (pt->type == MB_POINT_INVALID)
|
||||
return false;
|
||||
|
||||
if (!parse_u32(fields[1], &raw_address))
|
||||
return false;
|
||||
pt->raw_address = raw_address;
|
||||
|
||||
if (!normalize_address(pt->type, pt->raw_address, &pt->offset))
|
||||
return false;
|
||||
|
||||
if (fields[2][0] == '\0')
|
||||
return false;
|
||||
|
||||
strncpy(pt->name, fields[2], sizeof(pt->name) - 1U);
|
||||
pt->name[sizeof(pt->name) - 1U] = '\0';
|
||||
|
||||
pt->data_type = parse_data_type(fields[3]);
|
||||
if (pt->data_type == MB_DATA_INVALID)
|
||||
return false;
|
||||
|
||||
if (!validate_type_and_datatype(pt->type, pt->data_type))
|
||||
return false;
|
||||
|
||||
pt->reg_span = data_type_reg_span(pt->data_type);
|
||||
pt->is_helper = false;
|
||||
|
||||
if (pt->reg_span == 0U)
|
||||
return false;
|
||||
|
||||
if (fields[4][0] != '\0')
|
||||
{
|
||||
uint32_t control_raw;
|
||||
|
||||
if (!parse_u32(fields[4], &control_raw))
|
||||
return false;
|
||||
|
||||
pt->has_control_address = true;
|
||||
pt->control_raw_address = control_raw;
|
||||
pt->control_type = infer_point_type_from_address(control_raw);
|
||||
|
||||
if (pt->control_type == MB_POINT_INVALID)
|
||||
return false;
|
||||
|
||||
if (!normalize_address(pt->control_type, pt->control_raw_address, &pt->control_offset))
|
||||
return false;
|
||||
|
||||
if (!validate_control_mapping(pt))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
pt->has_control_address = false;
|
||||
pt->control_raw_address = 0U;
|
||||
pt->control_type = MB_POINT_INVALID;
|
||||
pt->control_offset = 0U;
|
||||
}
|
||||
|
||||
if (fields[5][0] != '\0')
|
||||
{
|
||||
uint32_t pics_raw;
|
||||
|
||||
if (!parse_u32(fields[5], &pics_raw))
|
||||
return false;
|
||||
|
||||
pt->has_pics_address = true;
|
||||
pt->pics_raw_address = pics_raw;
|
||||
pt->pics_type = infer_point_type_from_address(pics_raw);
|
||||
|
||||
if (pt->pics_type == MB_POINT_INVALID)
|
||||
return false;
|
||||
|
||||
if (!normalize_address(pt->pics_type, pt->pics_raw_address, &pt->pics_offset))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
pt->has_pics_address = false;
|
||||
pt->pics_raw_address = 0U;
|
||||
pt->pics_type = MB_POINT_INVALID;
|
||||
pt->pics_offset = 0U;
|
||||
}
|
||||
|
||||
pt->pics_data_type = parse_pics_data_type(fields[6]);
|
||||
|
||||
if (pt->pics_data_type == PICS_DATA_INVALID)
|
||||
return false;
|
||||
|
||||
if (pt->pics_data_type == PICS_DATA_STRING)
|
||||
return false;
|
||||
|
||||
switch (pt->pics_data_type)
|
||||
{
|
||||
case PICS_DATA_BOOLEAN:
|
||||
pt->pics_reg_span = 1U;
|
||||
break;
|
||||
case PICS_DATA_INTEGER:
|
||||
pt->pics_reg_span = 2U;
|
||||
break;
|
||||
case PICS_DATA_FLOAT:
|
||||
pt->pics_reg_span = 4U;
|
||||
break;
|
||||
case PICS_DATA_NONE:
|
||||
pt->pics_reg_span = 0U;
|
||||
break;
|
||||
default:
|
||||
pt->pics_reg_span = 0U;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pt->has_pics_address && pt->pics_data_type == PICS_DATA_NONE)
|
||||
return false;
|
||||
|
||||
if (!pt->has_pics_address && pt->pics_data_type != PICS_DATA_NONE)
|
||||
return false;
|
||||
|
||||
if (pt->has_pics_address)
|
||||
{
|
||||
switch (pt->pics_data_type)
|
||||
{
|
||||
case PICS_DATA_INTEGER:
|
||||
case PICS_DATA_FLOAT:
|
||||
if (pt->pics_type != MB_POINT_HOLDING_REGISTER)
|
||||
return false;
|
||||
break;
|
||||
|
||||
case PICS_DATA_BOOLEAN:
|
||||
if (pt->pics_type != MB_POINT_COIL &&
|
||||
pt->pics_type != MB_POINT_HOLDING_REGISTER)
|
||||
return false;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(pt->notes, fields[7], sizeof(pt->notes) - 1U);
|
||||
pt->notes[sizeof(pt->notes) - 1U] = '\0';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ---------- public API ---------- */
|
||||
|
||||
void modbus_points_init(modbus_db_t *db)
|
||||
{
|
||||
modbus_points_reset(db);
|
||||
}
|
||||
|
||||
void modbus_points_reset(modbus_db_t *db)
|
||||
{
|
||||
if (db == NULL)
|
||||
return;
|
||||
|
||||
memset(db, 0, sizeof(*db));
|
||||
}
|
||||
|
||||
bool modbus_points_load(modbus_db_t *db, const char *filename)
|
||||
{
|
||||
FILE *fp;
|
||||
char line[MODBUS_MAX_CSV_LINE_LEN];
|
||||
size_t line_num = 0;
|
||||
|
||||
if (db == NULL || filename == NULL)
|
||||
return false;
|
||||
|
||||
fp = fopen(filename, "r");
|
||||
if (fp == NULL)
|
||||
{
|
||||
printf("CSV ERROR: failed to open file: %s\n", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
modbus_points_reset(db);
|
||||
|
||||
while (fgets(line, sizeof(line), fp) != NULL)
|
||||
{
|
||||
modbus_point_t pt;
|
||||
char *newline;
|
||||
|
||||
line_num++;
|
||||
|
||||
newline = strchr(line, '\n');
|
||||
if (newline != NULL)
|
||||
*newline = '\0';
|
||||
|
||||
newline = strchr(line, '\r');
|
||||
if (newline != NULL)
|
||||
*newline = '\0';
|
||||
|
||||
trim_whitespace(line);
|
||||
|
||||
if (line[0] == '\0')
|
||||
continue;
|
||||
|
||||
if (line_num == 1U)
|
||||
continue;
|
||||
|
||||
{
|
||||
char temp_line[MODBUS_MAX_CSV_LINE_LEN];
|
||||
char *fields[8];
|
||||
size_t field_count = 0;
|
||||
char *p;
|
||||
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)
|
||||
{
|
||||
printf("CSV ERROR line %u: exceeded MODBUS_MAX_POINTS (%u)\n",
|
||||
(unsigned)line_num,
|
||||
(unsigned)MODBUS_MAX_POINTS);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parse_csv_line(line, &pt))
|
||||
{
|
||||
printf("CSV ERROR line %u: parse failed: %s\n",
|
||||
(unsigned)line_num,
|
||||
line);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (db_has_duplicate_name(db, pt.name))
|
||||
{
|
||||
printf("CSV ERROR line %u: duplicate name '%s'\n",
|
||||
(unsigned)line_num,
|
||||
pt.name);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (db_has_duplicate_type_offset(db, pt.type, pt.offset))
|
||||
{
|
||||
printf("CSV ERROR line %u: duplicate Modbus address for name '%s' raw=%lu offset=%u\n",
|
||||
(unsigned)line_num,
|
||||
pt.name,
|
||||
(unsigned long)pt.raw_address,
|
||||
(unsigned)pt.offset);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pt.has_pics_address &&
|
||||
db_has_duplicate_pics_range(db,
|
||||
pt.pics_type,
|
||||
pt.pics_offset,
|
||||
pt.pics_reg_span))
|
||||
{
|
||||
printf("CSV ERROR line %u: overlapping PICS range for name '%s' pics_raw=%lu pics_offset=%u span=%u\n",
|
||||
(unsigned)line_num,
|
||||
pt.name,
|
||||
(unsigned long)pt.pics_raw_address,
|
||||
(unsigned)pt.pics_offset,
|
||||
(unsigned)pt.pics_reg_span);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
db->points[db->count++] = pt;
|
||||
|
||||
if (!append_helper_points(db, &pt))
|
||||
{
|
||||
printf("CSV ERROR line %u: failed to append helper points for '%s'\n",
|
||||
(unsigned)line_num,
|
||||
pt.name);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
const modbus_point_t *modbus_points_find_by_name(const modbus_db_t *db, const char *name)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (db == NULL || name == NULL)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < db->count; i++)
|
||||
{
|
||||
if (strcmp(db->points[i].name, name) == 0)
|
||||
return &db->points[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const modbus_point_t *modbus_points_find_by_type_offset(const modbus_db_t *db,
|
||||
modbus_point_type_t type,
|
||||
uint16_t offset)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (db == NULL)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < db->count; i++)
|
||||
{
|
||||
if (db->points[i].type == type && db->points[i].offset == offset)
|
||||
return &db->points[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
modbus_mem_type_t modbus_points_type_to_mem(modbus_point_type_t type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MB_POINT_COIL:
|
||||
return MB_MEM_COIL;
|
||||
case MB_POINT_DISCRETE_INPUT:
|
||||
return MB_MEM_DISCRETE_INPUT;
|
||||
case MB_POINT_INPUT_REGISTER:
|
||||
return MB_MEM_INPUT_REGISTER;
|
||||
case MB_POINT_HOLDING_REGISTER:
|
||||
return MB_MEM_HOLDING_REGISTER;
|
||||
default:
|
||||
return MB_MEM_COIL;
|
||||
}
|
||||
}
|
||||
|
||||
bool modbus_points_is_bit_type(modbus_point_type_t type)
|
||||
{
|
||||
return (type == MB_POINT_COIL || type == MB_POINT_DISCRETE_INPUT);
|
||||
}
|
||||
|
||||
bool modbus_points_is_reg_type(modbus_point_type_t type)
|
||||
{
|
||||
return (type == MB_POINT_INPUT_REGISTER || type == MB_POINT_HOLDING_REGISTER);
|
||||
}
|
||||
|
||||
bool modbus_points_is_writable_type(modbus_point_type_t type)
|
||||
{
|
||||
return (type == MB_POINT_COIL || type == MB_POINT_HOLDING_REGISTER);
|
||||
}
|
||||
Reference in New Issue
Block a user