diff --git a/release/copy_headers.sh b/release/copy_headers.sh index dda334a..0d9deaa 100755 --- a/release/copy_headers.sh +++ b/release/copy_headers.sh @@ -49,6 +49,37 @@ copy_proto_module() fi } +# --- system/ 模块下的头文件 --- +copy_sys_module() +{ + local module="$1" + local header="$2" + local src="$SRC_DIR/system/$module/inc/$header" + + if [ -f "$src" ] + then + cp -f "$src" "$DST_DIR/" + echo " [system/$module] $header" + else + echo " [WARN] $src not found, skip" + fi +} + +# --- system/ 公共头文件 --- +copy_sys_header() +{ + local header="$1" + local src="$SRC_DIR/system/inc/$header" + + if [ -f "$src" ] + then + cp -f "$src" "$DST_DIR/" + echo " [system/inc] $header" + else + echo " [WARN] $src not found, skip" + fi +} + # ================================================================ # 公共库模块(按顺序添加) # ================================================================ @@ -71,4 +102,10 @@ copy_module "libcomm" "myComm.h" copy_proto_module "libmodbus" "myModbus.h" copy_proto_module "lib60870" "myIec60870.h" +# ================================================================ +# 系统模块(system/) +# ================================================================ +copy_sys_header "mySystem.h" +copy_sys_module "libmodbus_m" "myModbusM.h" + echo "=== done ===" diff --git a/release/src/makefile b/release/src/makefile index 5cf63e6..c811e86 100644 --- a/release/src/makefile +++ b/release/src/makefile @@ -1,4 +1,4 @@ -SUBDIRS := ./public ./protocol +SUBDIRS := ./public ./protocol ./system define make_subdir for d in $(SUBDIRS); do [ -f "$$d/makefile" ] && (cd "$$d" && make -f makefile $1) || true; done endef diff --git a/release/src/system/libmodbus_m/makefile b/release/src/system/libmodbus_m/makefile new file mode 100644 index 0000000..cbd9be9 --- /dev/null +++ b/release/src/system/libmodbus_m/makefile @@ -0,0 +1,40 @@ +include ./../../../linux.mk +L := $(notdir $(realpath $(CURDIR)/..)) +M := libmodbus_m +O := $(LIB_REL)/$(M).a +S := $(SRC_ROOT_DIR)/$(L)/libmodbus_m/src +I := -I$(SRC_ROOT_DIR)/$(L)/libmodbus_m/inc \ + -I$(SRC_ROOT_DIR)/$(L)/inc \ + -I$(SRC_ROOT_DIR)/protocol/libmodbus/inc \ + -I$(SRC_ROOT_DIR)/public/libxml/inc \ + -I$(SRC_ROOT_DIR)/public/libdatacenter/inc \ + -I$(SRC_ROOT_DIR)/public/liblog/inc \ + -I$(SRC_ROOT_DIR)/public/libfunc/inc \ + -I$(SRC_ROOT_DIR)/public/libcmd/inc \ + -I$(SRC_ROOT_DIR)/public/libtask/inc \ + -I$(SRC_ROOT_DIR)/public/libmy_xxhash/inc +B := $(CURDIR)/$(M)/obj +SRCS := $(wildcard $(S)/*.cpp) +OBJS := $(patsubst $(S)/%.cpp, $(B)/%.o, $(SRCS)) +F := $(CXX_FLAGS) $(I) + +.PHONY: all +all: + @mkdir -p $(ROOT_DIR)/release/inc + @$(MAKE) $(O) + +$(O): $(OBJS) + @mkdir -p $(dir $@) + $(AR) rcs $@ $^ + @echo "[$(M)] built" + +$(B)/%.o: $(S)/%.cpp + @mkdir -p $(dir $@) + $(CXX) $(F) -c $< -o $@ + +.PHONY: clean +clean: + rm -rf $(B) $(O) + +.PHONY: rebuild +rebuild: clean all diff --git a/release/src/system/makefile b/release/src/system/makefile index 75098b1..09452cf 100644 --- a/release/src/system/makefile +++ b/release/src/system/makefile @@ -1,7 +1,8 @@ -SUBDIRS := +SUBDIRS := ./libmodbus_m define make_subdir - @for d in $(SUBDIRS); do [ -f "$$d/makefile" ] && (cd "$$d" && make -f makefile $1) || true; done + for d in $(SUBDIRS); do [ -f "$$d/makefile" ] && (cd "$$d" && make -f makefile $1) || true; done endef -.PHONY: all; all:; $(call make_subdir,all) -.PHONY: clean; clean:; $(call make_subdir,clean) -.PHONY: rebuild; rebuild: clean all +.PHONY: all clean rebuild +all:; @$(call make_subdir,all) +clean:; @$(call make_subdir,clean) +rebuild: clean all diff --git a/src/system/inc/mySystem.h b/src/system/inc/mySystem.h new file mode 100644 index 0000000..b0de486 --- /dev/null +++ b/src/system/inc/mySystem.h @@ -0,0 +1,30 @@ +/** + * @file mySystem.h + * @brief 系统管理模块公共定义 + * @details 供 libmodbus_m/libiec/libcom_decode/RTU 等系统模块共用。 + */ +#ifndef _MY_SYSTEM_H_ +#define _MY_SYSTEM_H_ + +#include "myBase.h" +#include "myTask.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief 应用任务上下文(RTU 多任务运行时使用) */ +typedef struct +{ + const char *name; /**< 任务名称 */ + stru_task_event_t p_event; /**< 事件句柄 */ + stru_task_msg_queue_t p_queue; /**< 消息队列句柄 */ + uint32_t run_cnt; /**< 运行计数 */ +} stru_app; + +#ifdef __cplusplus +} +#endif + +#endif /* _MY_SYSTEM_H_ */ diff --git a/src/system/libmodbus_m/inc/modbus_m.h b/src/system/libmodbus_m/inc/modbus_m.h new file mode 100644 index 0000000..8de85e4 --- /dev/null +++ b/src/system/libmodbus_m/inc/modbus_m.h @@ -0,0 +1,83 @@ +#pragma once + +#include "myBase.h" +#include "mySystem.h" +#include "myModbus.h" +#include +#include +#include + +#define MODULE_MODBUS_M "modbus_m" + +#define MODBUS_M_STR_LEN 64 +#define MODBUS_M_MAX_SLAVE_NUM 16 + +// 通讯通道配置 +typedef struct +{ + uint32_t id; // 通道ID + std::string mode; // "tcp" 或 "rtu" + std::string host; // TCP 目标IP + uint16_t port; // TCP 目标端口 + std::string device; // RTU 串口设备 + int baud; // RTU 波特率 + char parity; // RTU 校验位 + int data_bit; // RTU 数据位 + int stop_bit; // RTU 停止位 + int timeout_ms; // 请求超时 + int retry_cnt; // 失败重试次数 + modbus_t *ctx; // libmodbus 上下文 + bool connected; // 连接状态 +}stru_modbus_m_channel; + +// 点表条目 +typedef struct +{ + uint16_t no; // 序号 + uint8_t type; // 数据类型: 2=bit, 4=uint16, 6=float32, 7=uint32, 13=string + uint32_t channel; // 归属通道ID + uint8_t slave; // 从站地址 + uint8_t fc; // 功能码 + uint16_t addr; // 寄存器/线圈起始地址 + uint16_t count; // 连续读取数量 + uint8_t bit; // 位位置(0-15),type=2且fc=03/04时有效 + float factor; // 遥测系数 + float offset; // 遥测偏移 + std::string saddr; // 内部信号地址 + std::string desc; // 描述 + void *p_val; // 数据指针 +}stru_modbus_m_item; + +// 点表页 +typedef struct +{ + std::string desc; + std::vector st_vec; // 遥信 + std::vector mx_vec; // 遥测 + std::vector co_vec; // 遥控 + std::vector ao_vec; // 参数 +}stru_modbus_m_point; + +// 写请求(用于 Co/Ao 异步写入) +typedef struct +{ + uint32_t channel; + uint8_t slave; + uint8_t fc; + uint16_t addr; + uint16_t value; + bool is_coil; // true=写线圈, false=写寄存器 +}stru_modbus_m_write_req; + +// 模块主配置 +typedef struct +{ + std::vector channels; + stru_modbus_m_point point; +}stru_modbus_m_cfg; + + +// API 声明 +int modbus_m_init(); +int modbus_m_cfg_parse(const std::string &path); +void modbus_m_show_info(); diff --git a/src/system/libmodbus_m/inc/myModbusM.h b/src/system/libmodbus_m/inc/myModbusM.h new file mode 100644 index 0000000..1f161d9 --- /dev/null +++ b/src/system/libmodbus_m/inc/myModbusM.h @@ -0,0 +1,13 @@ +/** + * @file myModbusM.h + * @brief Modbus 主站管理模块统一入口 + * @details 原工程自研模块,通过 XML 配置通道和点表,轮询读取 Modbus 从站数据, + * 写入 libdatacenter,支持遥控/遥调异步写入。 + * 调用者只需 #include "myModbusM.h"。 + */ +#ifndef _MY_MODBUS_M_H_ +#define _MY_MODBUS_M_H_ + +#include "modbus_m.h" + +#endif /* _MY_MODBUS_M_H_ */ diff --git a/src/system/libmodbus_m/src/modbus_m.cpp b/src/system/libmodbus_m/src/modbus_m.cpp new file mode 100644 index 0000000..beff9a6 --- /dev/null +++ b/src/system/libmodbus_m/src/modbus_m.cpp @@ -0,0 +1,1019 @@ +#include "modbus_m.h" +#include "myLog.h" +#include "myFunc.h" +#include "myCmd.h" +#include "myDatacenter.h" +#include "tinyxml2.h" + +#include +#include + +using namespace tinyxml2; + +LOCAL std::string g_modbus_m_base_path; +LOCAL stru_modbus_m_cfg g_modbus_m_cfg; +LOCAL std::mutex g_write_mutex; +LOCAL std::vector g_write_queue; + + +// ==================== 工具函数 ==================== + +LOCAL std::string get_base_path() +{ + char proc_dir[512] = {0}; + if(0 != func_proc_self_dir(proc_dir, sizeof(proc_dir))) + { + MY_LOG_E("func_get_process_self_dir failed"); + return ""; + } + return std::string(proc_dir); +} + +// ==================== 数据类型映射 ==================== + +LOCAL uint8_t modbus_type_to_local_type(uint8_t type) +{ + switch(type) + { + case 2: return DATA_TYPE_B; // bit/线圈 + case 4: return DATA_TYPE_U16; // uint16 + case 6: return DATA_TYPE_F32; // float32 + case 7: return DATA_TYPE_U32; // uint32 + case 13: return DATA_TYPE_STR; // string + default: return DATA_TYPE_U16; + } +} + +LOCAL uint8_t dc_data_type_size(uint8_t data_type) +{ + switch(data_type) + { + case DATA_TYPE_B: return 1; + case DATA_TYPE_U8: return 1; + case DATA_TYPE_S16: + case DATA_TYPE_U16: return 2; + case DATA_TYPE_S32: + case DATA_TYPE_U32: + case DATA_TYPE_F32: return 4; + case DATA_TYPE_STR: return 128; + default: return 2; + } +} + +// ==================== XML 解析 ==================== + +LOCAL int parse_channels(XMLElement *p_root) +{ + XMLElement *p_ch = p_root->FirstChildElement("Channel"); + while(NULL != p_ch) + { + stru_modbus_m_channel ch; + ch.id = (uint32_t)p_ch->IntAttribute("id", 0); + ch.mode = p_ch->Attribute("mode") ? p_ch->Attribute("mode") : "tcp"; + ch.host = p_ch->Attribute("host") ? p_ch->Attribute("host") : ""; + ch.port = (uint16_t)p_ch->IntAttribute("port", 502); + ch.device = p_ch->Attribute("device") ? p_ch->Attribute("device") : ""; + ch.baud = p_ch->IntAttribute("baud", 9600); + ch.parity = p_ch->Attribute("parity") ? p_ch->Attribute("parity")[0] : 'N'; + ch.data_bit = p_ch->IntAttribute("data_bit", 8); + ch.stop_bit = p_ch->IntAttribute("stop_bit", 1); + ch.timeout_ms = p_ch->IntAttribute("timeout_ms", 1000); + ch.retry_cnt = p_ch->IntAttribute("retry_cnt", 3); + ch.ctx = NULL; + ch.connected = false; + + g_modbus_m_cfg.channels.push_back(ch); + MY_LOG_I("channel %d: mode=%s, host=%s:%d", ch.id, ch.mode.c_str(), ch.host.c_str(), ch.port); + + p_ch = p_ch->NextSiblingElement("Channel"); + } + + if(g_modbus_m_cfg.channels.empty()) + { + MY_LOG_E("no channel configured"); + return -1; + } + return 0; +} + +LOCAL int parse_point_item(XMLElement *p_item, std::vector &vec) +{ + stru_modbus_m_item item; + item.no = 0; + item.type = 0; + item.channel = 0; + item.slave = 0; + item.fc = 0; + item.addr = 0; + item.count = 0; + item.bit = 0; + item.factor = 0.0f; + item.offset = 0.0f; + item.p_val = NULL; + + item.no = (uint16_t)p_item->IntAttribute("no", 0); + item.type = (uint8_t)p_item->IntAttribute("type", 4); + item.channel = (uint32_t)p_item->IntAttribute("channel", 0); + item.slave = (uint8_t)p_item->IntAttribute("slave", 1); + item.fc = (uint8_t)p_item->IntAttribute("fc", 0x03); + item.addr = (uint16_t)p_item->IntAttribute("addr", 0); + item.count = (uint16_t)p_item->IntAttribute("count", 1); + item.bit = (uint8_t)p_item->IntAttribute("bit", 0); + item.factor = (float)p_item->FloatAttribute("factor", 1.0f); + item.offset = (float)p_item->FloatAttribute("offset", 0.0f); + + if(NULL != p_item->Attribute("saddr")) + item.saddr = p_item->Attribute("saddr"); + if(NULL != p_item->Attribute("desc")) + item.desc = p_item->Attribute("desc"); + + // 验证通道ID + bool ch_found = false; + for(auto &ch : g_modbus_m_cfg.channels) + { + if(ch.id == item.channel) + { + ch_found = true; + break; + } + } + if(!ch_found) + { + MY_LOG_E("item no=%d: channel %d not found", item.no, item.channel); + return -1; + } + + // 创建数据指针 + uint8_t local_type = modbus_type_to_local_type(item.type); + item.p_val = dc_create_data(local_type); + if(NULL == item.p_val) + { + MY_LOG_E("dc_create_data_ptr_by_type failed, type=%d", local_type); + return -1; + } + + vec.push_back(item); + return 0; +} + +LOCAL int parse_points(XMLElement *p_root) +{ + XMLElement *p_point = p_root->FirstChildElement("Point"); + if(NULL == p_point) + { + MY_LOG_E("Point not found"); + return -1; + } + + if(NULL != p_point->Attribute("desc")) + g_modbus_m_cfg.point.desc = p_point->Attribute("desc"); + + // 解析 St(遥信) + XMLElement *p_st = p_point->FirstChildElement("St"); + if(NULL != p_st) + { + XMLElement *p_item = p_st->FirstChildElement("Item"); + while(NULL != p_item) + { + if(0 != parse_point_item(p_item, g_modbus_m_cfg.point.st_vec)) + { + return -1; + } + p_item = p_item->NextSiblingElement("Item"); + } + MY_LOG_I("parsed %zu st items", g_modbus_m_cfg.point.st_vec.size()); + } + + // 解析 Mx(遥测) + XMLElement *p_mx = p_point->FirstChildElement("Mx"); + if(NULL != p_mx) + { + XMLElement *p_item = p_mx->FirstChildElement("Item"); + while(NULL != p_item) + { + if(0 != parse_point_item(p_item, g_modbus_m_cfg.point.mx_vec)) + { + return -1; + } + p_item = p_item->NextSiblingElement("Item"); + } + MY_LOG_I("parsed %zu mx items", g_modbus_m_cfg.point.mx_vec.size()); + } + + // 解析 Co(遥控) + XMLElement *p_co = p_point->FirstChildElement("Co"); + if(NULL != p_co) + { + XMLElement *p_item = p_co->FirstChildElement("Item"); + while(NULL != p_item) + { + if(0 != parse_point_item(p_item, g_modbus_m_cfg.point.co_vec)) + { + return -1; + } + p_item = p_item->NextSiblingElement("Item"); + } + MY_LOG_I("parsed %zu co items", g_modbus_m_cfg.point.co_vec.size()); + } + + // 解析 Ao(参数) + XMLElement *p_ao = p_point->FirstChildElement("Ao"); + if(NULL != p_ao) + { + XMLElement *p_item = p_ao->FirstChildElement("Item"); + while(NULL != p_item) + { + if(0 != parse_point_item(p_item, g_modbus_m_cfg.point.ao_vec)) + { + return -1; + } + p_item = p_item->NextSiblingElement("Item"); + } + MY_LOG_I("parsed %zu ao items", g_modbus_m_cfg.point.ao_vec.size()); + } + + return 0; +} + +int modbus_m_cfg_parse(const std::string &path) +{ + XMLDocument doc; + if(XML_SUCCESS != doc.LoadFile(path.c_str())) + { + MY_LOG_E("load xml failed: %s", path.c_str()); + return -1; + } + + XMLElement *p_root = doc.RootElement(); + if(NULL == p_root) + { + MY_LOG_E("root element not found"); + return -1; + } + + if(0 != parse_channels(p_root)) + { + MY_LOG_E("parse channels failed"); + return -1; + } + + if(0 != parse_points(p_root)) + { + MY_LOG_E("parse points failed"); + return -1; + } + + MY_LOG_I("modbus_m cfg parsed: %zu channels, st=%zu, mx=%zu, co=%zu, ao=%zu", + g_modbus_m_cfg.channels.size(), + g_modbus_m_cfg.point.st_vec.size(), + g_modbus_m_cfg.point.mx_vec.size(), + g_modbus_m_cfg.point.co_vec.size(), + g_modbus_m_cfg.point.ao_vec.size()); + + return 0; +} + + +// ==================== 通道连接管理 ==================== + +LOCAL int channel_connect(stru_modbus_m_channel *p_ch) +{ + if(NULL == p_ch) + { + return -1; + } + + // 释放旧连接 + if(NULL != p_ch->ctx) + { + modbus_close(p_ch->ctx); + modbus_free(p_ch->ctx); + p_ch->ctx = NULL; + } + + // 创建上下文 + if(p_ch->mode == "tcp") + { + p_ch->ctx = modbus_new_tcp(p_ch->host.c_str(), p_ch->port); + if(NULL == p_ch->ctx) + { + MY_LOG_E("modbus_new_tcp failed: %s:%d", p_ch->host.c_str(), p_ch->port); + return -1; + } + // TCP 模式下设置默认从站地址 + modbus_set_slave(p_ch->ctx, MODBUS_TCP_SLAVE); + } + else if(p_ch->mode == "rtu") + { + p_ch->ctx = modbus_new_rtu(p_ch->device.c_str(), p_ch->baud, p_ch->parity, p_ch->data_bit, p_ch->stop_bit); + if(NULL == p_ch->ctx) + { + MY_LOG_E("modbus_new_rtu failed: %s", p_ch->device.c_str()); + return -1; + } + } + else + { + MY_LOG_E("unknown mode: %s", p_ch->mode.c_str()); + return -1; + } + + // 设置超时 + uint32_t sec = p_ch->timeout_ms / 1000; + uint32_t usec = (p_ch->timeout_ms % 1000) * 1000; + modbus_set_response_timeout(p_ch->ctx, sec, usec); + modbus_set_error_recovery(p_ch->ctx, (modbus_error_recovery_mode)(MODBUS_ERROR_RECOVERY_LINK | MODBUS_ERROR_RECOVERY_PROTOCOL)); + + // 建立连接 + if(0 != modbus_connect(p_ch->ctx)) + { + MY_LOG_E("modbus_connect failed: ch=%d, mode=%s", p_ch->id, p_ch->mode.c_str()); + modbus_free(p_ch->ctx); + p_ch->ctx = NULL; + p_ch->connected = false; + return -1; + } + + p_ch->connected = true; + MY_LOG_I("channel %d connected: mode=%s", p_ch->id, p_ch->mode.c_str()); + return 0; +} + +LOCAL int channels_init() +{ + for(auto &ch : g_modbus_m_cfg.channels) + { + channel_connect(&ch); + } + return 0; +} + +LOCAL void channels_close() +{ + for(auto &ch : g_modbus_m_cfg.channels) + { + if(NULL != ch.ctx) + { + modbus_close(ch.ctx); + modbus_free(ch.ctx); + ch.ctx = NULL; + } + ch.connected = false; + } +} + + +// ==================== 数据读取 ==================== + +// 读取线圈 (FC 0x01) 或离散输入 (FC 0x02) +LOCAL int read_bits(stru_modbus_m_channel *p_ch, uint8_t slave, uint8_t fc, uint16_t addr, uint16_t count, uint8_t *p_dest) +{ + if(NULL == p_ch || NULL == p_ch->ctx || !p_ch->connected) + { + return -1; + } + + modbus_set_slave(p_ch->ctx, slave); + + int ret = -1; + if(fc == 0x01) + { + ret = modbus_read_bits(p_ch->ctx, addr, count, p_dest); + } + else if(fc == 0x02) + { + ret = modbus_read_input_bits(p_ch->ctx, addr, count, p_dest); + } + + return ret; +} + +// 读取寄存器 (FC 0x03 或 0x04) +LOCAL int read_registers(stru_modbus_m_channel *p_ch, uint8_t slave, uint8_t fc, uint16_t addr, uint16_t count, uint16_t *p_dest) +{ + if(NULL == p_ch || NULL == p_ch->ctx || !p_ch->connected) + { + return -1; + } + + modbus_set_slave(p_ch->ctx, slave); + + int ret = -1; + if(fc == 0x03) + { + ret = modbus_read_registers(p_ch->ctx, addr, count, p_dest); + } + else if(fc == 0x04) + { + ret = modbus_read_input_registers(p_ch->ctx, addr, count, p_dest); + } + + return ret; +} + + +// 读取单个 Item 值(按 fc 分发) +LOCAL int read_item_value(stru_modbus_m_channel *p_ch, stru_modbus_m_item *p_item) +{ + if(NULL == p_ch || NULL == p_item) + { + return -1; + } + + uint8_t local_type = modbus_type_to_local_type(p_item->type); + + // FC 0x01/0x02: 读线圈/离散输入(位) + if(p_item->fc == 0x01 || p_item->fc == 0x02) + { + uint8_t bit_val = 0; + int ret = read_bits(p_ch, p_item->slave, p_item->fc, p_item->addr, p_item->count, &bit_val); + if(ret > 0) + { + uint8_t val = bit_val ? 1 : 0; + memcpy(p_item->p_val, &val, 1); + } + return ret; + } + + // FC 0x03/0x04: 读保持/输入寄存器 + if(p_item->fc == 0x03 || p_item->fc == 0x04) + { + // 临时缓冲区(最大 125 个寄存器) + uint16_t reg_buf[256]; + uint16_t read_cnt = p_item->count > 125 ? 125 : p_item->count; + + int ret = read_registers(p_ch, p_item->slave, p_item->fc, p_item->addr, read_cnt, reg_buf); + if(ret <= 0) + { + return ret; + } + + // type=2: 从寄存器提取位(FC03/04 读线圈模式) + if(p_item->type == 2 && p_item->bit < 16) + { + uint8_t val = (reg_buf[0] >> p_item->bit) & 0x01; + memcpy(p_item->p_val, &val, 1); + return ret; + } + + // type=4 (uint16) + if(p_item->type == 4) + { + uint16_t val = reg_buf[0]; + if(p_item->factor != 1.0f || p_item->offset != 0.0f) + { + float fval = val * p_item->factor + p_item->offset; + val = (uint16_t)fval; + } + memcpy(p_item->p_val, &val, 2); + return ret; + } + + // type=6 (float32): 2 个连续寄存器 + if(p_item->type == 6 && read_cnt >= 2) + { + float val = modbus_get_float(®_buf[0]); + if(p_item->factor != 1.0f || p_item->offset != 0.0f) + { + val = val * p_item->factor + p_item->offset; + } + memcpy(p_item->p_val, &val, 4); + return ret; + } + + // type=7 (uint32): 2 个连续寄存器 + if(p_item->type == 7 && read_cnt >= 2) + { + uint32_t val = ((uint32_t)reg_buf[0] << 16) | reg_buf[1]; + memcpy(p_item->p_val, &val, 4); + return ret; + } + + // type=13 (string) + if(p_item->type == 13) + { + char *p_str = (char *)p_item->p_val; + for(uint16_t i = 0; i < read_cnt && i < 64; i++) + { + p_str[i * 2] = (reg_buf[i] >> 8) & 0xFF; + p_str[i * 2 + 1] = reg_buf[i] & 0xFF; + } + p_str[read_cnt * 2] = '\0'; + return ret; + } + + // 默认: 按 uint16 处理 + memcpy(p_item->p_val, ®_buf[0], 2); + return ret; + } + + return -1; +} + + +// ==================== 批量轮询 ==================== + +// 读取组: (channel, slave, fc) 三元组 +typedef struct +{ + uint32_t channel; + uint8_t slave; + uint8_t fc; + uint16_t min_addr; + uint16_t max_addr; + std::vector items; +}stru_poll_group; + +LOCAL void build_poll_groups(std::vector &items, std::vector &groups) +{ + for(auto &item : items) + { + // 只处理读类型的 fc + if(item.fc != 0x01 && item.fc != 0x02 && item.fc != 0x03 && item.fc != 0x04) + { + continue; + } + + // 查找已有组 + bool found = false; + for(auto &g : groups) + { + if(g.channel == item.channel && g.slave == item.slave && g.fc == item.fc) + { + g.items.push_back(&item); + if(item.addr < g.min_addr) g.min_addr = item.addr; + if(item.addr + item.count > g.max_addr) g.max_addr = item.addr + item.count; + found = true; + break; + } + } + + if(!found) + { + stru_poll_group g; + g.channel = item.channel; + g.slave = item.slave; + g.fc = item.fc; + g.min_addr = item.addr; + g.max_addr = item.addr + item.count; + g.items.push_back(&item); + groups.push_back(g); + } + } +} + +LOCAL void poll_groups(std::vector &groups) +{ + for(auto &g : groups) + { + // 查找通道 + stru_modbus_m_channel *p_ch = NULL; + for(auto &ch : g_modbus_m_cfg.channels) + { + if(ch.id == g.channel && ch.connected) + { + p_ch = &ch; + break; + } + } + if(NULL == p_ch) + { + continue; + } + + uint16_t total_count = g.max_addr - g.min_addr; + + // FC 01/02: 读位 + if(g.fc == 0x01 || g.fc == 0x02) + { + uint8_t bits[256] = {0}; + int ret = read_bits(p_ch, g.slave, g.fc, g.min_addr, total_count > 2000 ? 2000 : total_count, bits); + if(ret > 0) + { + for(auto *p_item : g.items) + { + uint16_t offset = p_item->addr - g.min_addr; + uint8_t val = bits[offset] ? 1 : 0; + memcpy(p_item->p_val, &val, 1); + dc_set_out_signal_val(p_item->saddr.c_str(), p_item->p_val, MODULE_MODBUS_M); + } + } + } + + // FC 03/04: 读寄存器 + if(g.fc == 0x03 || g.fc == 0x04) + { + uint16_t regs[256] = {0}; + int ret = read_registers(p_ch, g.slave, g.fc, g.min_addr, total_count > 125 ? 125 : total_count, regs); + if(ret > 0) + { + for(auto *p_item : g.items) + { + uint16_t offset = (p_item->addr - g.min_addr); + + if(p_item->type == 2) + { + // FC03/04 线圈模式:从寄存器提取位 + uint16_t reg_idx = offset; + uint8_t val = (regs[reg_idx] >> p_item->bit) & 0x01; + memcpy(p_item->p_val, &val, 1); + } + else if(p_item->type == 4) + { + uint16_t val = regs[offset]; + if(p_item->factor != 1.0f || p_item->offset != 0.0f) + { + float fval = val * p_item->factor + p_item->offset; + val = (uint16_t)fval; + } + memcpy(p_item->p_val, &val, 2); + } + else if(p_item->type == 6 && offset + 1 < 256) + { + // float val = modbus_get_float(®s[offset]); + float val = modbus_get_float_abcd(®s[offset]); + if(p_item->factor != 1.0f || p_item->offset != 0.0f) + { + val = val * p_item->factor + p_item->offset; + } + memcpy(p_item->p_val, &val, 4); + } + else if(p_item->type == 7 && offset + 1 < 256) + { + uint32_t val = ((uint32_t)regs[offset] << 16) | regs[offset + 1]; + memcpy(p_item->p_val, &val, 4); + } + + dc_set_out_signal_val(p_item->saddr.c_str(), p_item->p_val, MODULE_MODBUS_M); + } + } + } + } +} + + +// ==================== 写操作 ==================== + +LOCAL void process_write_queue() +{ + std::vector queue; + { + std::lock_guard lock(g_write_mutex); + queue.swap(g_write_queue); + } + + for(auto &req : queue) + { + // 查找通道 + stru_modbus_m_channel *p_ch = NULL; + for(auto &ch : g_modbus_m_cfg.channels) + { + if(ch.id == req.channel && ch.ctx != NULL) + { + p_ch = &ch; + break; + } + } + if(NULL == p_ch) + { + LOG_E("write: channel %d not found", req.channel); + continue; + } + + modbus_set_slave(p_ch->ctx, req.slave); + + int ret = -1; + if(req.is_coil) + { + ret = modbus_write_bit(p_ch->ctx, req.addr, req.value ? 1 : 0); + } + else + { + ret = modbus_write_register(p_ch->ctx, req.addr, req.value); + } + + if(ret < 0) + { + LOG_E("write failed: ch=%d, slave=%d, fc=0x%02x, addr=%d", req.channel, req.slave, req.fc, req.addr); + } + } +} + +LOCAL void enqueue_write(const stru_modbus_m_write_req &req) +{ + std::lock_guard lock(g_write_mutex); + g_write_queue.push_back(req); +} + + +// ==================== 数据中心信号回调 ==================== + +LOCAL void modbus_m_signal_co_change_callback(const char *saddr, dc_ctrl_step_t step, uint8_t data_type, uint8_t setting_zone, const void *p_data) +{ + if(step != DC_CTRL_DIRECT) + { + return; + } + + for(auto &item : g_modbus_m_cfg.point.co_vec) + { + if(item.saddr == saddr) + { + stru_modbus_m_write_req req; + req.channel = item.channel; + req.slave = item.slave; + req.fc = item.fc; + req.addr = item.addr; + req.is_coil = true; + + if(data_type == DATA_TYPE_B) + { + req.value = p_data ? (*(uint8_t *)p_data ? 1 : 0) : 0; + } + else + { + req.value = p_data ? (*(uint8_t *)p_data ? 1 : 0) : 0; + } + + enqueue_write(req); + MY_LOG_I("co write: ch=%d, slave=%d, addr=%d, val=%d", req.channel, req.slave, req.addr, req.value); + break; + } + } +} + +LOCAL void modbus_m_signal_ao_change_callback(const char *saddr, dc_ctrl_step_t step, uint8_t data_type, uint8_t setting_zone, const void *p_data) +{ + if(step != DC_CTRL_DIRECT) + { + return; + } + + for(auto &item : g_modbus_m_cfg.point.ao_vec) + { + if(item.saddr == saddr) + { + stru_modbus_m_write_req req; + req.channel = item.channel; + req.slave = item.slave; + req.fc = item.fc; + req.addr = item.addr; + req.is_coil = false; + + if(data_type == DATA_TYPE_U16) + { + req.value = p_data ? *(uint16_t *)p_data : 0; + } + else if(data_type == DATA_TYPE_F32) + { + float f = p_data ? *(float *)p_data : 0; + req.value = (uint16_t)f; + } + else + { + req.value = p_data ? *(uint16_t *)p_data : 0; + } + + enqueue_write(req); + MY_LOG_I("ao write: ch=%d, slave=%d, addr=%d, val=%d", req.channel, req.slave, req.addr, req.value); + break; + } + } +} + + +// ==================== 数据中心信号注册 ==================== + +LOCAL int signal_register_items(const std::vector &items, const std::string &signal_type) +{ + for(auto &item : items) + { + uint8_t local_type = modbus_type_to_local_type(item.type); + + if(signal_type == "st") + { + if(0 != dc_signal_out(item.saddr.c_str(), item.desc.c_str(), local_type, item.p_val, MODULE_MODBUS_M)) + { + MY_LOG_E("dc_signal_out failed: %s", item.saddr.c_str()); + return -1; + } + } + else if(signal_type == "mx") + { + if(0 != dc_signal_out(item.saddr.c_str(), item.desc.c_str(), local_type, item.p_val, MODULE_MODBUS_M)) + { + MY_LOG_E("dc_signal_out failed: %s", item.saddr.c_str()); + return -1; + } + } + else if(signal_type == "co") + { + if(0 != dc_signal_yk(item.saddr.c_str(), item.desc.c_str(), local_type, DC_CTRL_DIRECT, item.p_val, modbus_m_signal_co_change_callback, MODULE_MODBUS_M)) + { + MY_LOG_E("dc_signal_yk failed: %s", item.saddr.c_str()); + return -1; + } + } + else if(signal_type == "ao") + { + if(0 != dc_signal_ao(item.saddr.c_str(), item.desc.c_str(), local_type, DC_CTRL_DIRECT, item.p_val, modbus_m_signal_ao_change_callback, MODULE_MODBUS_M)) + { + MY_LOG_E("dc_signal_ao failed: %s", item.saddr.c_str()); + return -1; + } + } + } + + return 0; +} + +LOCAL int signals_init() +{ + if(0 != signal_register_items(g_modbus_m_cfg.point.st_vec, "st")) + { + return -1; + } + if(0 != signal_register_items(g_modbus_m_cfg.point.mx_vec, "mx")) + { + return -1; + } + if(0 != signal_register_items(g_modbus_m_cfg.point.co_vec, "co")) + { + return -1; + } + if(0 != signal_register_items(g_modbus_m_cfg.point.ao_vec, "ao")) + { + return -1; + } + + MY_LOG_I("signals registered: st=%zu, mx=%zu, co=%zu, ao=%zu", + g_modbus_m_cfg.point.st_vec.size(), + g_modbus_m_cfg.point.mx_vec.size(), + g_modbus_m_cfg.point.co_vec.size(), + g_modbus_m_cfg.point.ao_vec.size()); + + return 0; +} + + +// ==================== 模块初始化 ==================== + +int modbus_m_init() +{ + if(0 != signals_init()) + { + MY_LOG_E("signals_init failed"); + return -1; + } + + if(0 != channels_init()) + { + MY_LOG_E("channels_init failed"); + // 部分通道失败也可继续 + } + + return 0; +} + + +// ==================== 信息显示 ==================== + +void modbus_m_show_info() +{ + printf("===== Modbus Master Info =====\n"); + printf("Channels: %zu\n", g_modbus_m_cfg.channels.size()); + for(auto &ch : g_modbus_m_cfg.channels) + { + printf(" ch[%d]: mode=%s, connected=%d", ch.id, ch.mode.c_str(), ch.connected); + if(ch.mode == "tcp") + { + printf(", %s:%d", ch.host.c_str(), ch.port); + } + else + { + printf(", %s %d %c%d/%d", ch.device.c_str(), ch.baud, ch.parity, ch.data_bit, ch.stop_bit); + } + printf("\n"); + } + printf("Points: st=%zu, mx=%zu, co=%zu, ao=%zu\n", + g_modbus_m_cfg.point.st_vec.size(), + g_modbus_m_cfg.point.mx_vec.size(), + g_modbus_m_cfg.point.co_vec.size(), + g_modbus_m_cfg.point.ao_vec.size()); +} + + +// ==================== 命令行 ==================== + +LOCAL void cmd_modbus_m(int argc, char *argv[]) +{ + const char *str = "\ + modbus_m info ----------> 查看所有通道和点表信息\n"; + + if(argc < 2) + { + printf("%s", str); + return; + } + + if(0 == strcmp(argv[1], "info")) + { + modbus_m_show_info(); + } +} + +LOCAL void cmd_modbus_m_complete(const char *buf, char ***completions, int *ncomp) +{ + static const char *subs[] = {"info"}; + cmd_sub_complete(buf, completions, ncomp, subs, sizeof(subs)/sizeof(subs[0])); +} + +CMD_REGISTER_C(MODULE_MODBUS_M, cmd_modbus_m, "Modbus主站线程的控制命令", cmd_modbus_m_complete); + + +// ==================== 应用线程入口 ==================== + +int app_modbus_m_init1(void *arg) +{ + stru_app *p_app = (stru_app *)arg; + + dc_signal_out("modbus_m.run_cnt", "modbus_m.run_cnt", DATA_TYPE_U32, &p_app->run_cnt, MODULE_MODBUS_M); + + std::string base_path = get_base_path(); + if(base_path.empty()) + { + MY_LOG_E("get_base_path failed"); + return -1; + } + g_modbus_m_base_path = base_path + "config/MODBUS/"; + + std::string cfg_path = g_modbus_m_base_path + "modbus_m.xml"; + if(0 != modbus_m_cfg_parse(cfg_path)) + { + MY_LOG_E("modbus_m_cfg_parse failed: %s", cfg_path.c_str()); + return -1; + } + + if(0 != modbus_m_init()) + { + LOG_E("modbus_m_init failed"); + return -1; + } + + return 0; +} + +int app_modbus_m_init2(void *arg) +{ + return 0; +} + +void *app_modbus_m(void *arg) +{ + if(NULL == arg) + { + LOG_E("app_modbus_m arg null"); + return NULL; + } + + stru_app *p_app = (stru_app *)arg; + + uint32_t event; + std::vector st_groups; + std::vector mx_groups; + + // 预构建轮询分组 + build_poll_groups(g_modbus_m_cfg.point.st_vec, st_groups); + build_poll_groups(g_modbus_m_cfg.point.mx_vec, mx_groups); + MY_LOG_I("poll groups: st=%zu, mx=%zu", st_groups.size(), mx_groups.size()); + + while(1) + { + task_event_recv(p_app->p_event, + 0x01 | 0x02 | 0x04, + 0x02 | 0x04, + TASK_EVENT_WAIT_FOREVER, + &event); + + if(event & 0x01) + { + // 处理写请求队列 + process_write_queue(); + } + + if(event & 0x02) + { + // 轮询遥信(100ms) + poll_groups(st_groups); + } + + if(event & 0x04) + { + // 轮询遥测/参数(1000ms) + poll_groups(mx_groups); + p_app->run_cnt++; + } + } + + return NULL; +}