Files
Modbus/main/modbus_memory.c

916 lines
24 KiB
C

#include "modbus_memory.h"
#include "modbus_points.h"
#include <string.h>
#include "esp_log.h"
#define TAG_MEM "MODBUS_MEM"
static modbus_point_type_t mem_to_point_type(modbus_mem_type_t type)
{
switch (type)
{
case MB_MEM_COIL:
return MB_POINT_COIL;
case MB_MEM_DISCRETE_INPUT:
return MB_POINT_DISCRETE_INPUT;
case MB_MEM_INPUT_REGISTER:
return MB_POINT_INPUT_REGISTER;
case MB_MEM_HOLDING_REGISTER:
return MB_POINT_HOLDING_REGISTER;
default:
return MB_POINT_INVALID;
}
}
static modbus_point_t *find_point_rw(modbus_memory_t *mem,
modbus_mem_type_t mem_type,
uint16_t offset)
{
size_t i;
modbus_point_type_t point_type;
if (mem == NULL || mem->db == NULL)
return NULL;
point_type = mem_to_point_type(mem_type);
if (point_type == MB_POINT_INVALID)
return NULL;
for (i = 0; i < mem->db->count; i++)
{
if (mem->db->points[i].type == point_type &&
mem->db->points[i].offset == offset)
{
return &mem->db->points[i];
}
}
return NULL;
}
static const modbus_point_t *find_point_ro(modbus_memory_t *mem,
modbus_mem_type_t mem_type,
uint16_t offset)
{
size_t i;
modbus_point_type_t point_type;
if (mem == NULL || mem->db == NULL)
return NULL;
point_type = mem_to_point_type(mem_type);
if (point_type == MB_POINT_INVALID)
return NULL;
for (i = 0; i < mem->db->count; i++)
{
if (mem->db->points[i].type == point_type &&
mem->db->points[i].offset == offset)
{
return &mem->db->points[i];
}
}
return NULL;
}
static modbus_point_t *find_pics_point_rw(modbus_memory_t *mem,
modbus_mem_type_t mem_type,
uint16_t offset)
{
modbus_point_type_t point_type;
const modbus_point_t *pt;
if (mem == NULL || mem->db == NULL)
return NULL;
point_type = mem_to_point_type(mem_type);
if (point_type == MB_POINT_INVALID)
return NULL;
pt = modbus_points_find_by_pics_range(mem->db, point_type, offset);
return (modbus_point_t *)pt;
}
static bool write_point_words(modbus_memory_t *mem,
modbus_point_t *pt,
const uint16_t *words,
uint8_t span)
{
uint8_t i;
modbus_mem_type_t mem_type;
if (mem == NULL || pt == NULL || words == NULL)
return false;
if (modbus_points_is_bit_type(pt->type))
{
pt->bit_value = (words[0] != 0U) ? 1U : 0U;
return true;
}
mem_type = modbus_points_type_to_mem(pt->type);
for (i = 0; i < span; i++)
{
modbus_point_t *word_pt;
word_pt = find_point_rw(mem, mem_type, (uint16_t)(pt->offset + i));
if (word_pt == NULL)
return false;
word_pt->reg_value = words[i];
}
return true;
}
static void sync_points_controlled_by(modbus_memory_t *mem,
const modbus_point_t *control_pt)
{
size_t i;
uint8_t j;
modbus_mem_type_t control_mem_type;
modbus_mem_type_t target_mem_type;
if (mem == NULL || mem->db == NULL || control_pt == NULL)
return;
for (i = 0; i < mem->db->count; i++)
{
modbus_point_t *target_pt = &mem->db->points[i];
if (!target_pt->has_control_address)
continue;
if (target_pt->control_type != control_pt->type)
continue;
if (target_pt->control_offset != control_pt->offset)
continue;
if (modbus_points_is_bit_type(target_pt->type))
{
target_pt->bit_value = control_pt->bit_value;
continue;
}
control_mem_type = modbus_points_type_to_mem(control_pt->type);
target_mem_type = modbus_points_type_to_mem(target_pt->type);
for (j = 0; j < target_pt->reg_span; j++)
{
const modbus_point_t *src_pt;
modbus_point_t *dst_pt;
src_pt = find_point_ro(mem, control_mem_type,
(uint16_t)(control_pt->offset + j));
dst_pt = find_point_rw(mem, target_mem_type,
(uint16_t)(target_pt->offset + j));
if (src_pt == NULL || dst_pt == NULL)
break;
dst_pt->reg_value = src_pt->reg_value;
}
}
}
static bool apply_pics_to_official_point(modbus_memory_t *mem, modbus_point_t *pt)
{
uint16_t out_words[4] = {0U, 0U, 0U, 0U};
if (mem == NULL || pt == NULL || !pt->has_pics_address)
return false;
switch (pt->pics_data_type)
{
case PICS_DATA_BOOLEAN:
{
uint16_t v = (pt->pics_words[0] != 0U) ? 1U : 0U;
if (modbus_points_is_bit_type(pt->type))
{
pt->bit_value = (v != 0U) ? 1U : 0U;
sync_points_controlled_by(mem, pt);
return true;
}
out_words[0] = v;
if (!write_point_words(mem, pt, out_words, 1U))
return false;
sync_points_controlled_by(mem, pt);
return true;
}
case PICS_DATA_INTEGER:
{
uint32_t raw_u32;
int32_t pics_i32;
raw_u32 = ((uint32_t)pt->pics_words[1] << 16) |
(uint32_t)pt->pics_words[0];
pics_i32 = (int32_t)raw_u32;
switch (pt->data_type)
{
case MB_DATA_UINT16:
{
uint32_t v = (pics_i32 < 0) ? 0U : (uint32_t)pics_i32;
if (v > 65535U)
v = 65535U;
out_words[0] = (uint16_t)v;
if (!write_point_words(mem, pt, out_words, 1U))
return false;
break;
}
case MB_DATA_UINT32:
{
uint32_t v = (pics_i32 < 0) ? 0U : (uint32_t)pics_i32;
out_words[0] = (uint16_t)(v & 0xFFFFU);
out_words[1] = (uint16_t)((v >> 16) & 0xFFFFU);
if (!write_point_words(mem, pt, out_words, 2U))
return false;
break;
}
case MB_DATA_FLOAT:
{
float f = (float)pics_i32;
uint32_t raw_f32;
memcpy(&raw_f32, &f, sizeof(raw_f32));
out_words[0] = (uint16_t)(raw_f32 & 0xFFFFU);
out_words[1] = (uint16_t)((raw_f32 >> 16) & 0xFFFFU);
if (!write_point_words(mem, pt, out_words, 2U))
return false;
break;
}
case MB_DATA_DOUBLE:
{
double d = (double)pics_i32;
uint64_t raw_f64;
memcpy(&raw_f64, &d, sizeof(raw_f64));
out_words[0] = (uint16_t)(raw_f64 & 0xFFFFU);
out_words[1] = (uint16_t)((raw_f64 >> 16) & 0xFFFFU);
out_words[2] = (uint16_t)((raw_f64 >> 32) & 0xFFFFU);
out_words[3] = (uint16_t)((raw_f64 >> 48) & 0xFFFFU);
if (!write_point_words(mem, pt, out_words, 4U))
return false;
break;
}
default:
return false;
}
sync_points_controlled_by(mem, pt);
return true;
}
case PICS_DATA_FLOAT:
{
uint64_t raw_f64;
double pics_d;
raw_f64 = ((uint64_t)pt->pics_words[3] << 48) |
((uint64_t)pt->pics_words[2] << 32) |
((uint64_t)pt->pics_words[1] << 16) |
(uint64_t)pt->pics_words[0];
memcpy(&pics_d, &raw_f64, sizeof(pics_d));
switch (pt->data_type)
{
case MB_DATA_UINT16:
{
double v = pics_d;
if (v < 0.0)
v = 0.0;
if (v > 65535.0)
v = 65535.0;
out_words[0] = (uint16_t)v;
if (!write_point_words(mem, pt, out_words, 1U))
return false;
break;
}
case MB_DATA_UINT32:
{
double v = pics_d;
uint32_t u32;
if (v < 0.0)
v = 0.0;
if (v > 4294967295.0)
v = 4294967295.0;
u32 = (uint32_t)v;
out_words[0] = (uint16_t)(u32 & 0xFFFFU);
out_words[1] = (uint16_t)((u32 >> 16) & 0xFFFFU);
if (!write_point_words(mem, pt, out_words, 2U))
return false;
break;
}
case MB_DATA_FLOAT:
{
float f = (float)pics_d;
uint32_t raw_f32;
memcpy(&raw_f32, &f, sizeof(raw_f32));
out_words[0] = (uint16_t)(raw_f32 & 0xFFFFU);
out_words[1] = (uint16_t)((raw_f32 >> 16) & 0xFFFFU);
if (!write_point_words(mem, pt, out_words, 2U))
return false;
break;
}
case MB_DATA_DOUBLE:
{
uint64_t raw_out_f64;
memcpy(&raw_out_f64, &pics_d, sizeof(raw_out_f64));
out_words[0] = (uint16_t)(raw_out_f64 & 0xFFFFU);
out_words[1] = (uint16_t)((raw_out_f64 >> 16) & 0xFFFFU);
out_words[2] = (uint16_t)((raw_out_f64 >> 32) & 0xFFFFU);
out_words[3] = (uint16_t)((raw_out_f64 >> 48) & 0xFFFFU);
if (!write_point_words(mem, pt, out_words, 4U))
return false;
break;
}
default:
return false;
}
sync_points_controlled_by(mem, pt);
return true;
}
default:
return false;
}
}
static bool write_pics_staging_word(modbus_memory_t *mem,
modbus_mem_type_t mem_type,
uint16_t offset,
uint16_t value)
{
modbus_point_t *pt;
uint16_t word_index;
pt = find_pics_point_rw(mem, mem_type, offset);
if (pt == NULL)
return false;
if (!pt->has_pics_address || pt->pics_reg_span == 0U)
return false;
if (offset < pt->pics_offset)
return false;
word_index = (uint16_t)(offset - pt->pics_offset);
if (word_index >= pt->pics_reg_span || word_index >= 4U)
return false;
pt->pics_words[word_index] = value;
return true;
}
bool modbus_memory_init(modbus_memory_t *mem, modbus_db_t *db)
{
if (mem == NULL || db == NULL)
return false;
mem->db = db;
modbus_memory_reset(mem);
return true;
}
void modbus_memory_deinit(modbus_memory_t *mem)
{
if (mem == NULL)
return;
mem->db = NULL;
}
void modbus_memory_reset(modbus_memory_t *mem)
{
size_t i;
if (mem == NULL || mem->db == NULL)
return;
for (i = 0; i < mem->db->count; i++)
{
mem->db->points[i].bit_value = 0U;
mem->db->points[i].reg_value = 0U;
memset(mem->db->points[i].pics_words, 0, sizeof(mem->db->points[i].pics_words));
}
}
bool modbus_memory_read_coil(modbus_memory_t *mem, uint16_t offset, uint8_t *value)
{
const modbus_point_t *pt;
if (mem == NULL || value == NULL)
return false;
pt = find_point_ro(mem, MB_MEM_COIL, offset);
if (pt == NULL)
return false;
*value = (pt->bit_value != 0U) ? 1U : 0U;
return true;
}
bool modbus_memory_write_coil(modbus_memory_t *mem, uint16_t offset, uint8_t value)
{
modbus_point_t *pt;
if (mem == NULL)
return false;
pt = find_point_rw(mem, MB_MEM_COIL, offset);
if (pt != NULL)
{
pt->bit_value = (value != 0U) ? 1U : 0U;
sync_points_controlled_by(mem, pt);
return true;
}
pt = find_pics_point_rw(mem, MB_MEM_COIL, offset);
if (pt != NULL)
{
uint16_t staged = (value != 0U) ? 1U : 0U;
return write_pics_staging_word(mem, MB_MEM_COIL, offset, staged);
}
return false;
}
bool modbus_memory_read_discrete_input(modbus_memory_t *mem, uint16_t offset, uint8_t *value)
{
const modbus_point_t *pt;
if (mem == NULL || value == NULL)
return false;
pt = find_point_ro(mem, MB_MEM_DISCRETE_INPUT, offset);
if (pt == NULL)
return false;
*value = (pt->bit_value != 0U) ? 1U : 0U;
return true;
}
bool modbus_memory_write_discrete_input(modbus_memory_t *mem, uint16_t offset, uint8_t value)
{
modbus_point_t *pt;
if (mem == NULL)
return false;
pt = find_point_rw(mem, MB_MEM_DISCRETE_INPUT, offset);
if (pt == NULL)
return false;
pt->bit_value = (value != 0U) ? 1U : 0U;
return true;
}
bool modbus_memory_read_input_register(modbus_memory_t *mem, uint16_t offset, uint16_t *value)
{
const modbus_point_t *pt;
if (mem == NULL || value == NULL)
return false;
pt = find_point_ro(mem, MB_MEM_INPUT_REGISTER, offset);
if (pt == NULL)
return false;
*value = pt->reg_value;
return true;
}
bool modbus_memory_write_input_register(modbus_memory_t *mem, uint16_t offset, uint16_t value)
{
modbus_point_t *pt;
if (mem == NULL)
return false;
pt = find_point_rw(mem, MB_MEM_INPUT_REGISTER, offset);
if (pt == NULL)
return false;
pt->reg_value = value;
return true;
}
bool modbus_memory_read_holding_register(modbus_memory_t *mem, uint16_t offset, uint16_t *value)
{
const modbus_point_t *pt;
if (mem == NULL || mem->db == NULL || value == NULL)
return false;
/*
* First try normal/official holding register.
*/
pt = find_point_ro(mem, MB_MEM_HOLDING_REGISTER, offset);
if (pt != NULL)
{
*value = pt->reg_value;
return true;
}
/*
* Then allow reading PICS staging registers directly.
* This makes ModScan useful for verifying addresses like 410005-410008.
*/
pt = modbus_points_find_by_pics_range(mem->db,
MB_POINT_HOLDING_REGISTER,
offset);
if (pt != NULL && pt->has_pics_address)
{
uint16_t word_index;
if (offset < pt->pics_offset)
return false;
word_index = (uint16_t)(offset - pt->pics_offset);
if (word_index >= pt->pics_reg_span || word_index >= 4U)
return false;
*value = pt->pics_words[word_index];
return true;
}
return false;
}
bool modbus_memory_write_holding_register(modbus_memory_t *mem, uint16_t offset, uint16_t value)
{
modbus_point_t *pt;
if (mem == NULL)
return false;
pt = find_point_rw(mem, MB_MEM_HOLDING_REGISTER, offset);
if (pt != NULL)
{
pt->reg_value = value;
sync_points_controlled_by(mem, pt);
return true;
}
pt = find_pics_point_rw(mem, MB_MEM_HOLDING_REGISTER, offset);
if (pt != NULL)
{
if (!write_pics_staging_word(mem, MB_MEM_HOLDING_REGISTER, offset, value))
return false;
return apply_pics_to_official_point(mem,pt);
}
return false;
}
bool modbus_memory_read_bit(modbus_memory_t *mem,
modbus_mem_type_t type,
uint16_t offset,
uint8_t *value)
{
switch (type)
{
case MB_MEM_COIL:
return modbus_memory_read_coil(mem, offset, value);
case MB_MEM_DISCRETE_INPUT:
return modbus_memory_read_discrete_input(mem, offset, value);
default:
return false;
}
}
bool modbus_memory_write_bit(modbus_memory_t *mem,
modbus_mem_type_t type,
uint16_t offset,
uint8_t value)
{
switch (type)
{
case MB_MEM_COIL:
return modbus_memory_write_coil(mem, offset, value);
case MB_MEM_DISCRETE_INPUT:
return modbus_memory_write_discrete_input(mem, offset, value);
default:
return false;
}
}
bool modbus_memory_read_reg(modbus_memory_t *mem,
modbus_mem_type_t type,
uint16_t offset,
uint16_t *value)
{
switch (type)
{
case MB_MEM_INPUT_REGISTER:
return modbus_memory_read_input_register(mem, offset, value);
case MB_MEM_HOLDING_REGISTER:
return modbus_memory_read_holding_register(mem, offset, value);
default:
return false;
}
}
bool modbus_memory_write_reg(modbus_memory_t *mem,
modbus_mem_type_t type,
uint16_t offset,
uint16_t value)
{
switch (type)
{
case MB_MEM_INPUT_REGISTER:
return modbus_memory_write_input_register(mem, offset, value);
case MB_MEM_HOLDING_REGISTER:
return modbus_memory_write_holding_register(mem, offset, value);
default:
return false;
}
}
bool modbus_memory_valid_bit_range(modbus_memory_t *mem,
modbus_mem_type_t type,
uint16_t start_offset,
uint16_t quantity)
{
uint32_t end_offset;
(void)type;
if (mem == NULL || mem->db == NULL || quantity == 0U)
return false;
end_offset = (uint32_t)start_offset + (uint32_t)quantity - 1U;
if (end_offset > 0xFFFFU)
return false;
return true;
}
bool modbus_memory_valid_reg_range(modbus_memory_t *mem,
modbus_mem_type_t type,
uint16_t start_offset,
uint16_t quantity)
{
uint32_t end_offset;
(void)type;
if (mem == NULL || mem->db == NULL || quantity == 0U)
return false;
end_offset = (uint32_t)start_offset + (uint32_t)quantity - 1U;
if (end_offset > 0xFFFFU)
return false;
return true;
}
bool modbus_memory_read_coils(modbus_memory_t *mem,
uint16_t start_offset,
uint16_t quantity,
uint8_t *dest)
{
uint16_t i;
if (dest == NULL || !modbus_memory_valid_bit_range(mem, MB_MEM_COIL, start_offset, quantity))
return false;
for (i = 0; i < quantity; i++)
{
uint8_t value = 0U;
if (!modbus_memory_read_coil(mem, (uint16_t)(start_offset + i), &value))
value = 0U;
dest[i] = value;
}
return true;
}
bool modbus_memory_write_coils(modbus_memory_t *mem,
uint16_t start_offset,
uint16_t quantity,
const uint8_t *src)
{
uint16_t i;
if (src == NULL || !modbus_memory_valid_bit_range(mem, MB_MEM_COIL, start_offset, quantity))
return false;
for (i = 0; i < quantity; i++)
{
if (!modbus_memory_write_coil(mem, (uint16_t)(start_offset + i), src[i]))
return false;
}
return true;
}
bool modbus_memory_read_discrete_inputs(modbus_memory_t *mem,
uint16_t start_offset,
uint16_t quantity,
uint8_t *dest)
{
uint16_t i;
if (dest == NULL || !modbus_memory_valid_bit_range(mem, MB_MEM_DISCRETE_INPUT, start_offset, quantity))
return false;
for (i = 0; i < quantity; i++)
{
uint8_t value = 0U;
if (!modbus_memory_read_discrete_input(mem, (uint16_t)(start_offset + i), &value))
value = 0U;
dest[i] = value;
}
return true;
}
bool modbus_memory_write_discrete_inputs(modbus_memory_t *mem,
uint16_t start_offset,
uint16_t quantity,
const uint8_t *src)
{
uint16_t i;
if (src == NULL || !modbus_memory_valid_bit_range(mem, MB_MEM_DISCRETE_INPUT, start_offset, quantity))
return false;
for (i = 0; i < quantity; i++)
{
if (!modbus_memory_write_discrete_input(mem, (uint16_t)(start_offset + i), src[i]))
return false;
}
return true;
}
bool modbus_memory_read_input_registers(modbus_memory_t *mem,
uint16_t start_offset,
uint16_t quantity,
uint16_t *dest)
{
uint16_t i;
if (dest == NULL || !modbus_memory_valid_reg_range(mem, MB_MEM_INPUT_REGISTER, start_offset, quantity))
return false;
for (i = 0; i < quantity; i++)
{
uint16_t value = 0U;
if (!modbus_memory_read_input_register(mem, (uint16_t)(start_offset + i), &value))
value = 0U;
dest[i] = value;
}
return true;
}
bool modbus_memory_write_input_registers(modbus_memory_t *mem,
uint16_t start_offset,
uint16_t quantity,
const uint16_t *src)
{
uint16_t i;
if (src == NULL || !modbus_memory_valid_reg_range(mem, MB_MEM_INPUT_REGISTER, start_offset, quantity))
return false;
for (i = 0; i < quantity; i++)
{
if (!modbus_memory_write_input_register(mem, (uint16_t)(start_offset + i), src[i]))
return false;
}
return true;
}
bool modbus_memory_read_holding_registers(modbus_memory_t *mem,
uint16_t start_offset,
uint16_t quantity,
uint16_t *dest)
{
uint16_t i;
if (dest == NULL || !modbus_memory_valid_reg_range(mem, MB_MEM_HOLDING_REGISTER, start_offset, quantity))
return false;
for (i = 0; i < quantity; i++)
{
uint16_t value = 0U;
if (!modbus_memory_read_holding_register(mem, (uint16_t)(start_offset + i), &value))
value = 0U;
dest[i] = value;
}
return true;
}
bool modbus_memory_write_holding_registers(modbus_memory_t *mem,
uint16_t start_offset,
uint16_t quantity,
const uint16_t *src)
{
uint16_t i;
if (src == NULL || quantity == 0U || mem == NULL || mem->db == NULL)
return false;
for (i = 0; i < quantity; i++)
{
uint16_t off = (uint16_t)(start_offset + i);
if (find_point_rw(mem, MB_MEM_HOLDING_REGISTER, off) == NULL)
{
if (modbus_points_find_by_pics_range(mem->db,
MB_POINT_HOLDING_REGISTER,
off) == NULL)
{
return false;
}
}
}
for (i = 0; i < quantity; i++)
{
if (!modbus_memory_write_holding_register(mem,
(uint16_t)(start_offset + i),
src[i]))
{
return false;
}
}
{
modbus_point_t *pics_pt;
pics_pt = find_pics_point_rw(mem, MB_MEM_HOLDING_REGISTER, start_offset);
if (pics_pt != NULL)
{
if (!apply_pics_to_official_point(mem, pics_pt))
return false;
}
}
return true;
}