feat(libiec): IEC 60870-5-104 服务端模块 — 5文件/700行

## 模块结构
- iec104.h: 公共头 (MAX_IEC_NUM=3 slot复用/辅助类型)
- iec104_cfg.h: XML点表配置声明 (6类信号stru_iec_sig)
- iec104.cpp: 核心 (CS10x初始化/slot管理/全部26个回调/收发桥/线程)
- iec104_cfg.cpp: tinyxml2解析iec1014.xml
- iec104_point.cpp: 数据中心桥 (信号link+变化回调→环形缓冲)

## 回调覆盖
时间/COI/总召YX/YC/DD/扰动YC/SOE/扰动DD/故障/YK/Param/文件 — 14类26个

## 数据流
com_router(MQ_COM_TO_IEC)→iec104_data_rx→CS10x_DoRecv→callbacks→datacenter
iec104_data_tx→MQ_IEC_TO_COM→com_router→TCP:2404

## 框架集成
- app_modules.h: 启用IEC行
- app_msg_queue.h: 新增COM_TO_IEC+IEC_TO_COM
- mySystem.h: 新增EV_IEC_RX_COM+EV_COM_RX_IEC
- makefile: libiec.a + RTU链接-liec
- 测试命令: iec info/reset (CMD_REGISTER_C)
This commit is contained in:
ypc 2026-07-10 11:08:37 +08:00
parent 8cf00ad14d
commit 105db095f4
11 changed files with 1094 additions and 2 deletions

View File

@ -16,7 +16,7 @@ INC_PATH := -I$(ROOT)/release/inc \
-I$(ROOT)/src/protocol/libmongoose/inc
LIBS := -L$(ROOT)/release/$(BUILD_TYPE)/lib \
-lweb_server -lmodbus_m -lself_ptl -lcom_router -licp67 -lmodbus -l60870 \
-lweb_server -lmodbus_m -lself_ptl -lcom_router -liec -licp67 -lmodbus -l60870 \
-lmongoose -ldatacenter -lxml -lcmd -ltask \
-llog -lfunc -lmy_xxhash -lcJSON -lmd5 -lcomm \
-lpthread -lstdc++

View File

@ -0,0 +1,40 @@
# libiec IEC-104 服务端模块
include ./../../../linux.mk
L := $(notdir $(realpath $(CURDIR)/..))
M := libiec
O := $(LIB_REL)/$(M).a
S := $(SRC_ROOT_DIR)/$(L)/libiec/src
I := -I$(SRC_ROOT_DIR)/$(L)/libiec/inc \
-I$(SRC_ROOT_DIR)/protocol/lib60870/inc \
-I$(SRC_ROOT_DIR)/public/libdatacenter/inc \
-I$(SRC_ROOT_DIR)/public/liblog/inc \
-I$(SRC_ROOT_DIR)/public/libtask/inc \
-I$(SRC_ROOT_DIR)/public/libxml/inc
B := $(CURDIR)/$(M)/obj
SRCS := $(wildcard $(S)/*.cpp) $(wildcard $(S)/*.c)
OBJS := $(patsubst $(S)/%.cpp, $(B)/%.o, $(patsubst $(S)/%.c, $(B)/%.o, $(SRCS)))
F := $(CXX_FLAGS) $(I) -include cbb_compat.h
.PHONY: all
all:
@$(MAKE) $(O)
$(O): $(OBJS)
@mkdir -p $(dir $@)
$(AR) rcs $@ $^
@echo "[$(M)] built"
$(B)/%.o: $(S)/%.cpp
@mkdir -p $(dir $@)
$(CXX) $(F) -c $< -o $@
$(B)/%.o: $(S)/%.c
@mkdir -p $(dir $@)
$(CC) $(C_FLAGS) $(I) -c $< -o $@
.PHONY: clean
clean:
rm -rf $(B) $(O)
.PHONY: rebuild
rebuild: clean all

View File

@ -1,7 +1,7 @@
include ./../../linux.mk
# app_sys.cpp 已合并到 RTU makefile 中,此处仅管理子模块
SUBDIRS := ./libmodbus_m ./libweb_server ./libcom_router ./libself_ptl ./RTU
SUBDIRS := ./libmodbus_m ./libweb_server ./libcom_router ./libself_ptl ./libiec ./RTU
.PHONY: all clean rebuild

View File

@ -22,6 +22,7 @@ APP_MODULE(MODBUS_M, modbus_m, app_modbus_m_init1, app_modbus_m_init2, app_m
APP_MODULE(WEB_SERVER, web_server, app_web_server_init1, app_web_server_init2, app_web_server)
APP_MODULE(SELF_PTL, self_ptl, app_self_ptl_init1, app_self_ptl_init2, app_self_ptl)
APP_MODULE(COM_ROUTER, com_router, app_com_router_init1, app_com_router_init2, app_com_router)
APP_MODULE(IEC, iec, app_iec_init1, app_iec_init2, app_iec)
/* ============== 预留模块(阶段 2-3 实现后取消注释) ============== */
// APP_MODULE(COM_DECODE, com_decode, app_com_decode_init1, app_com_decode_init2, app_com_decode)

View File

@ -21,3 +21,9 @@ APP_MQ(COM_TO_SELF_PTL, com_to_self_ptl, 2048, 8)
/* self_ptl → com_router: ICP67 协议引擎发出的帧 */
APP_MQ(SELF_PTL_TO_COM, self_ptl_to_com, 2048, 8)
/* com_router → iec: TCP :2404 主站直投的 IEC-104 帧 */
APP_MQ(COM_TO_IEC, com_to_iec, 2048, 8)
/* iec → com_router: IEC-104 协议栈发出的回复帧 */
APP_MQ(IEC_TO_COM, iec_to_com, 2048, 8)

View File

@ -29,6 +29,8 @@ extern "C"
/* 跨模块事件 */
#define EV_COM_RX_SELF_PTL (0x01 << 3) /**< com_router: self_ptl 投递数据 */
#define EV_SELF_PTL_RX_COM (0x01 << 4) /**< self_ptl: com_router 投递数据 */
#define EV_IEC_RX_COM (0x01 << 5) /**< iec: com_router 投递 IEC-104 帧 */
#define EV_COM_RX_IEC (0x01 << 6) /**< com_router: iec 投递回复帧 */
#define MSG_SIZE 2048

View File

@ -0,0 +1,77 @@
/**
* @file iec104.h
* @brief IEC 60870-5-104
*
* @details IEC-104 (Server), com_router bind_app="iec"
* TCP :2404 lib60870
*
* :
* com_router (bind_app=iec) MQ_COM_TO_IEC libiec
* CS10x_104Init CS10x_DoRecv callbacks datacenter
* iec_data_tx MQ_IEC_TO_COM com_router TCP
*
* 3 (MAX_IEC_NUM), 600s slot
*/
#ifndef _IEC104_H_
#define _IEC104_H_
#include "myBase.h"
#include "myIec60870.h"
#ifdef __cplusplus
extern "C" {
#endif
#define MAX_IEC_NUM 3 /**< 最大并发 IEC 连接数 */
#define IEC_BUF_SIZE 1024 /**< 每路收发缓冲区大小 */
/* 环形缓冲区辅助类型 (iec104.cpp/iec104_point.cpp 共享) */
typedef struct
{
uint32_t info_addr;
uint8_t status;
uint8_t state_type;
uint16_t ms;
uint32_t sec;
} iec_soe_info_t;
typedef struct
{
uint32_t info_addr;
float val;
} iec_yc_info_t;
/** IEC 实例 — 每路连接一个 */
typedef struct
{
uint8_t is_busy; /**< 槽位占用标志 */
CS10x_Type iec_type; /**< CS104_TYPE_S / CS101_TYPE_S */
uint32_t interface; /**< (ch_id<<16)|fd, 通道唯一标识 */
uint32_t tm_out; /**< 超时计数器 (ms), 超过 600s 回收 */
uint8_t rx[IEC_BUF_SIZE];
uint8_t tx[IEC_BUF_SIZE];
uint8_t pub[IEC_BUF_SIZE];
uint8_t bak[IEC_BUF_SIZE];
CS10x cs10x; /**< lib60870 协议栈句柄 */
} stru_iec;
/* =========================== 对外 API =========================== */
stru_iec *iec104_get_by_interface(uint32_t interface);
int iec104_data_rx(uint8_t *msg); /**< MQ 数据入口 */
void iec104_task(uint16_t usGap); /**< 10ms 定时器处理 */
/* =========================== 应用线程入口 =========================== */
int app_iec_init1(void *arg);
int app_iec_init2(void *arg);
void *app_iec(void *arg);
#ifdef __cplusplus
}
#endif
#endif /* _IEC104_H_ */

View File

@ -0,0 +1,58 @@
/**
* @file iec104_cfg.h
* @brief IEC-104
*/
#ifndef _IEC104_CFG_H_
#define _IEC104_CFG_H_
#include "myBase.h"
#include <string>
#include <vector>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief IEC
* @field link ( "self_ptl.st.1")
* @field desc
* @field inf IEC (IOA)
* @field type (DATA_TYPE_U8/F32...)
* @field invert (0/1)
* @field factor
* @field offset
* @field p_data
*/
typedef struct
{
std::string link;
std::string desc;
uint16_t inf;
uint8_t type;
uint8_t invert;
float factor;
float offset;
void *p_data;
} stru_iec_sig;
/** IEC 点表完整配置 — 6 类信号 */
typedef struct
{
std::vector<stru_iec_sig> vec_st; /**< 遥信 */
std::vector<stru_iec_sig> vec_mx; /**< 遥测 */
std::vector<stru_iec_sig> vec_co; /**< 遥控 */
std::vector<stru_iec_sig> vec_dd; /**< 电度 */
std::vector<stru_iec_sig> vec_ao; /**< 参数 */
std::vector<stru_iec_sig> vec_param; /**< 定值 */
} stru_iec_cfg;
stru_iec_cfg *iec_cfg_ptr_get(void);
int iec_cfg_parse(const char *xml_path);
#ifdef __cplusplus
}
#endif
#endif /* _IEC104_CFG_H_ */

View File

@ -0,0 +1,732 @@
/**
* @file iec104.cpp
* @brief IEC 60870-5-104 CS10x + slot管理 + +
*
* @details iec.cpp + iec_yx/yc/dd/soe/fault/yk/param/file/time.cpp
* ( 12 )
*
* :
* - 3 slot , 600s
* - CS10x_104Init com_router (MQ_COM_TO_IEC)
* - CS10x set_ptl_itfc()
* - : soe(300) / disturb_yc(200) / fault(200) / dd(50)
*/
#include "mySystem.h"
#include "myLog.h"
#include "myFunc.h"
#include "myCmd.h"
#include "myDatacenter.h"
#include "iec104.h"
#include "iec104_cfg.h"
/* 来自 iec104_point.cpp */
extern int iec104_point_init(void);
#include <string.h>
#include <time.h>
#include <semaphore.h>
/* =========================== 常量 =========================== */
#define SOE_LEN 300
#define DISTURB_YC_LEN 200
#define FAULT_EVENT_LEN 200
#define FAULT_YC_NUM 8
#define DD_LEN 50
#define YX_START_PORT 0x0001 /**< 遥信 IOA 起始 */
#define YC_START_PORT 0x4001 /**< 遥测 IOA 起始 */
#define DD_START_PORT 0x6401 /**< 电度 IOA 起始 */
#define LINK_OVER_TIME 600000 /**< 链路超时 600s */
/* =========================== 前向声明 =========================== */
static int iec104_data_tx(uint8_t *p_tx, uint16_t tx_len, void *arg);
static void set_ptl_104_param(stru_iec *p_iec);
static void set_ptl_itfc(stru_iec *p_iec);
static void protocol_config(stru_iec *p_iec);
/* =========================== 环形缓冲区数据结构 =========================== */
typedef struct { uint8_t pcol; uint16_t out; uint32_t ch; } st_out_ptr;
typedef struct {
bool init; sem_t sem; uint16_t in;
std::vector<st_out_ptr> out_ptrs;
struct { uint16_t idx; float val; } yc[DISTURB_YC_LEN];
} ring_disturb_yc;
typedef struct {
bool init; sem_t sem; uint16_t in;
std::vector<st_out_ptr> out_ptrs;
struct { uint8_t st, ty; uint16_t pot, ms; uint32_t sec; } soe[SOE_LEN];
} ring_soe;
typedef struct {
bool init; sem_t sem; uint16_t in;
std::vector<st_out_ptr> out_ptrs;
struct { uint16_t idx; float val; __CP56Time2a tm; uint16_t ms; uint32_t sec; } dd[DD_LEN];
} ring_dd;
typedef struct {
bool init; sem_t sem; uint16_t in;
std::vector<st_out_ptr> out_ptrs;
struct { iec_soe_info_t soe; uint8_t yc_n; iec_yc_info_t yc[FAULT_YC_NUM]; } fault[FAULT_EVENT_LEN];
} ring_fault;
/* =========================== 全局变量 =========================== */
static stru_iec g_iec[MAX_IEC_NUM];
static ring_disturb_yc g_yc_ring;
static ring_soe g_soe_ring;
static ring_dd g_dd_ring;
static ring_fault g_fault_ring;
/* =========================== 工具函数 =========================== */
static void ring_sem_init(bool *init, sem_t *sem)
{
if(!*init) { sem_init(sem, 0, 1); *init = true; }
}
static int ring_count(uint16_t in, uint16_t out, int size)
{
return (in >= out) ? (in - out) : (size - out + in);
}
static st_out_ptr *ring_out_get(void *pv, std::vector<st_out_ptr> &vec,
uint8_t pcol)
{
stru_iec *p_iec = iec104_get_by_interface(*(uint32_t*)pv);
uint32_t ch = p_iec ? p_iec->interface : 0;
for(auto &o : vec) if(o.ch == ch && o.pcol == pcol) return &o;
st_out_ptr n = {pcol, 0, ch};
vec.push_back(n);
return &vec.back();
}
static void cp56_from_ts(__CP56Time2a *p, uint32_t sec, uint16_t ms)
{
struct tm st; gmtime_r((time_t*)&sec, &st);
uint16_t m = st.tm_sec * 1000 + ms;
p->ucLMs = m & 0xFF; p->ucHMs = (m>>8) & 0xFF;
p->ucMin = st.tm_min; p->ucHour = st.tm_hour;
p->ucWeek = st.tm_mday; p->ucMonth = st.tm_mon+1; p->ucYear = st.tm_year-100;
}
/* =========================== 时间回调 =========================== */
static int cb_get_time(__CP56Time2a *p)
{
struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts);
struct tm st; gmtime_r(&ts.tv_sec, &st);
uint16_t m = st.tm_sec*1000 + ts.tv_nsec/1000000;
p->ucLMs=m&0xFF; p->ucHMs=(m>>8)&0xFF;
p->ucMin=st.tm_min; p->ucHour=st.tm_hour;
p->ucWeek=st.tm_mday; p->ucMonth=st.tm_mon+1; p->ucYear=st.tm_year-100;
return 1;
}
static int cb_set_time(__CP56Time2a *p)
{
struct tm st={};
st.tm_year=p->ucYear+100; st.tm_mon=p->ucMonth-1; st.tm_mday=p->ucWeek;
st.tm_hour=p->ucHour&0x7F; st.tm_min=p->ucMin&0x3F;
uint16_t ms=p->ucLMs|((p->ucHMs&0x3F)<<8);
struct timespec ts={timegm(&st), (long)(ms%1000)*1000000L};
clock_settime(CLOCK_REALTIME, &ts); return 1;
}
/* =========================== COI 回调 =========================== */
static uint8_t cb_coi_read(void) { return 0; }
static void cb_coi_write(uint8_t c) { (void)c; }
/* =========================== YX (遥信) 回调 =========================== */
static int32_t cb_yx_count(uint16_t g, void *pv, uint32_t *s, uint32_t *e)
{
(void)g; (void)pv;
auto &v = iec_cfg_ptr_get()->vec_st;
*s = YX_START_PORT; *e = *s + v.size(); return v.size();
}
static int32_t cb_yx_val(uint16_t g, uint16_t i, Yx_Info *p, void *pv)
{
(void)g; auto &v = iec_cfg_ptr_get()->vec_st;
if(i >= v.size()) return 0;
stru_iec *iec = iec104_get_by_interface(*(uint32_t*)pv);
auto *s = &v[i];
p->ucStatus = s->p_data ? *(uint8_t*)s->p_data : 0;
p->uiInfoAddr = s->inf; p->ucStateType = 0;
if(iec && iec->cs10x.usYxType) /* 双点 */
{
p->ucStateType = 1;
if(s->invert)
{
if(p->ucStatus == 2) p->ucStatus = 1;
else if(p->ucStatus == 1) p->ucStatus = 2;
}
}
else if(s->invert)
{
p->ucStatus = p->ucStatus ? 0 : 1;
}
return 1;
}
/* =========================== YC (遥测) 回调 =========================== */
static int32_t cb_yc_count(uint16_t g, void *pv, uint32_t *s, uint32_t *e)
{
(void)g; (void)pv; auto &v = iec_cfg_ptr_get()->vec_mx;
*s = YC_START_PORT; *e = *s + v.size(); return v.size();
}
static int32_t cb_yc_val(uint16_t g, uint16_t i, Yc_Info *p, void *pv)
{
(void)g; (void)pv; auto &v = iec_cfg_ptr_get()->vec_mx;
if(i >= v.size()) return 0;
auto *s = &v[i];
float f = s->p_data ? *(float*)s->p_data : 0.0f;
p->fVal = f * s->factor + s->offset; p->uiInfoAddr = s->inf;
return 1;
}
int cb_yc_disturb_write(uint16_t idx, float val)
{
idx += YC_START_PORT;
ring_sem_init(&g_yc_ring.init, &g_yc_ring.sem); sem_wait(&g_yc_ring.sem);
g_yc_ring.yc[g_yc_ring.in].idx = idx;
g_yc_ring.yc[g_yc_ring.in].val = val;
g_yc_ring.in = (g_yc_ring.in + 1) % DISTURB_YC_LEN;
sem_post(&g_yc_ring.sem); return 1;
}
static int32_t cb_yc_disturb_get(Yc_Info *p, void *pv, uint32_t pos)
{
auto *o = ring_out_get(pv, g_yc_ring.out_ptrs, 1);
ring_sem_init(&g_yc_ring.init, &g_yc_ring.sem); sem_wait(&g_yc_ring.sem);
uint16_t i = (pos + o->out) % DISTURB_YC_LEN;
p->uiInfoAddr = g_yc_ring.yc[i].idx; p->fVal = g_yc_ring.yc[i].val;
sem_post(&g_yc_ring.sem); return 1;
}
static int32_t cb_yc_disturb_num(void *pv)
{
auto *o = ring_out_get(pv, g_yc_ring.out_ptrs, 1);
ring_sem_init(&g_yc_ring.init, &g_yc_ring.sem); sem_wait(&g_yc_ring.sem);
int n = ring_count(g_yc_ring.in, o->out, DISTURB_YC_LEN);
sem_post(&g_yc_ring.sem); return n;
}
static int32_t cb_yc_disturb_upd(void *pv, uint32_t pos)
{
if(!pos || !pv) return 0;
auto *o = ring_out_get(pv, g_yc_ring.out_ptrs, 1);
ring_sem_init(&g_yc_ring.init, &g_yc_ring.sem); sem_wait(&g_yc_ring.sem);
o->out = (o->out + pos) % DISTURB_YC_LEN; sem_post(&g_yc_ring.sem);
return 1;
}
/* =========================== SOE 回调 =========================== */
int cb_soe_write(uint16_t idx, uint8_t st, uint32_t sec, uint16_t ms)
{
idx += YX_START_PORT;
ring_sem_init(&g_soe_ring.init, &g_soe_ring.sem); sem_wait(&g_soe_ring.sem);
g_soe_ring.soe[g_soe_ring.in].st = st; g_soe_ring.soe[g_soe_ring.in].pot = idx;
g_soe_ring.soe[g_soe_ring.in].sec = sec; g_soe_ring.soe[g_soe_ring.in].ms = ms;
g_soe_ring.soe[g_soe_ring.in].ty = 0;
/* 双点 YX: 状态+1, StateType=1 */
if(iec_cfg_ptr_get()->vec_st.size() > 0) /* use YxType from cs10x later */
;
g_soe_ring.in = (g_soe_ring.in + 1) % SOE_LEN;
sem_post(&g_soe_ring.sem); return 1;
}
static int32_t cb_soe_get(SOE_Info *p, void *pv, uint32_t pos)
{
auto *o = ring_out_get(pv, g_soe_ring.out_ptrs, 2);
ring_sem_init(&g_soe_ring.init, &g_soe_ring.sem); sem_wait(&g_soe_ring.sem);
uint16_t i = (pos + o->out) % SOE_LEN;
p->uiInfoAddr = g_soe_ring.soe[i].pot; p->ucStatus = g_soe_ring.soe[i].st;
p->ucStateType = g_soe_ring.soe[i].ty;
cp56_from_ts(&p->stCTime, g_soe_ring.soe[i].sec, g_soe_ring.soe[i].ms);
sem_post(&g_soe_ring.sem); return 1;
}
static int32_t cb_soe_num(void *pv)
{
auto *o = ring_out_get(pv, g_soe_ring.out_ptrs, 2);
ring_sem_init(&g_soe_ring.init, &g_soe_ring.sem); sem_wait(&g_soe_ring.sem);
int n = ring_count(g_soe_ring.in, o->out, SOE_LEN); sem_post(&g_soe_ring.sem);
return n;
}
static int32_t cb_soe_upd(void *pv, uint32_t pos)
{
if(!pos || !pv) return 0;
auto *o = ring_out_get(pv, g_soe_ring.out_ptrs, 2);
ring_sem_init(&g_soe_ring.init, &g_soe_ring.sem); sem_wait(&g_soe_ring.sem);
o->out = (o->out + pos) % SOE_LEN; sem_post(&g_soe_ring.sem); return 1;
}
/* =========================== DD (电度) 回调 =========================== */
static int32_t cb_dd_count(uint16_t g, void *pv, uint32_t *s, uint32_t *e, uint8_t t)
{
auto &v = iec_cfg_ptr_get()->vec_dd; uint32_t n = v.size();
if(g == 0) {
if(t) return 0;
*s = DD_START_PORT; *e = *s + n; return n;
}
int u = 8; /* 前8个无时标 */
if(t) { *s = DD_START_PORT + u; u = n - u; *e = *s + u; return u > 0 ? u : 0; }
*s = DD_START_PORT; *e = *s + u; return u;
(void)pv;
}
static int32_t cb_dd_val(uint16_t g, uint16_t i, TimeDd_Info *p, void *pv, uint8_t t)
{
(void)g; (void)t; (void)pv; auto &v = iec_cfg_ptr_get()->vec_dd;
if(i >= v.size()) return 0;
p->uiInfoAddr = v[i].inf;
p->fVal = v[i].p_data ? *(float*)v[i].p_data : 0.0f;
memset(&p->stCTime, 0, sizeof(p->stCTime)); return 1;
}
int cb_dd_disturb_write(uint16_t idx, float val, uint32_t sec, uint16_t ms)
{
idx += DD_START_PORT;
ring_sem_init(&g_dd_ring.init, &g_dd_ring.sem); sem_wait(&g_dd_ring.sem);
g_dd_ring.dd[g_dd_ring.in].idx = idx; g_dd_ring.dd[g_dd_ring.in].val = val;
g_dd_ring.dd[g_dd_ring.in].sec = sec; g_dd_ring.dd[g_dd_ring.in].ms = ms;
g_dd_ring.in = (g_dd_ring.in + 1) % DD_LEN; sem_post(&g_dd_ring.sem); return 1;
}
static int32_t cb_dd_disturb_get(TimeDd_Info *p, void *pv, uint32_t pos)
{
auto *o = ring_out_get(pv, g_dd_ring.out_ptrs, 3);
ring_sem_init(&g_dd_ring.init, &g_dd_ring.sem); sem_wait(&g_dd_ring.sem);
uint16_t i = (pos + o->out) % DD_LEN;
p->uiInfoAddr = g_dd_ring.dd[i].idx; p->fVal = g_dd_ring.dd[i].val;
cp56_from_ts(&p->stCTime, g_dd_ring.dd[i].sec, g_dd_ring.dd[i].ms);
sem_post(&g_dd_ring.sem); return 1;
}
static int32_t cb_dd_disturb_num(void *pv)
{
auto *o = ring_out_get(pv, g_dd_ring.out_ptrs, 3);
ring_sem_init(&g_dd_ring.init, &g_dd_ring.sem); sem_wait(&g_dd_ring.sem);
int n = ring_count(g_dd_ring.in, o->out, DD_LEN); sem_post(&g_dd_ring.sem);
return n;
}
static int32_t cb_dd_disturb_upd(void *pv, uint32_t pos)
{
if(!pos || !pv) return 0;
auto *o = ring_out_get(pv, g_dd_ring.out_ptrs, 3);
ring_sem_init(&g_dd_ring.init, &g_dd_ring.sem); sem_wait(&g_dd_ring.sem);
o->out = (o->out + pos) % DD_LEN; sem_post(&g_dd_ring.sem); return 1;
}
/* =========================== 故障事件回调 =========================== */
int cb_fault_write(iec_soe_info_t *soe, uint8_t yx_n, iec_yc_info_t *yc, uint8_t yc_n)
{
ring_sem_init(&g_fault_ring.init, &g_fault_ring.sem); sem_wait(&g_fault_ring.sem);
g_fault_ring.fault[g_fault_ring.in].soe = *soe;
g_fault_ring.fault[g_fault_ring.in].yc_n = (yc_n > FAULT_YC_NUM) ? FAULT_YC_NUM : yc_n;
for(int j = 0; j < g_fault_ring.fault[g_fault_ring.in].yc_n; j++)
g_fault_ring.fault[g_fault_ring.in].yc[j] = yc[j];
g_fault_ring.in = (g_fault_ring.in + 1) % FAULT_EVENT_LEN;
sem_post(&g_fault_ring.sem);
(void)yx_n; return 1;
}
static int32_t cb_fault_get(FAULT_EVENT_Info *p, void *pv, uint32_t pos)
{
auto *o = ring_out_get(pv, g_fault_ring.out_ptrs, 4);
ring_sem_init(&g_fault_ring.init, &g_fault_ring.sem); sem_wait(&g_fault_ring.sem);
uint16_t i = (pos + o->out) % FAULT_EVENT_LEN;
/* 第一个 SOE */
p->staSoe[0].uiInfoAddr = g_fault_ring.fault[i].soe.info_addr;
p->staSoe[0].ucStatus = g_fault_ring.fault[i].soe.status;
p->staSoe[0].ucStateType = g_fault_ring.fault[i].soe.state_type;
memset(&p->staSoe[0].stCTime, 0, sizeof(p->staSoe[0].stCTime));
/* YC */
p->usYcNum = g_fault_ring.fault[i].yc_n;
for(uint8_t j = 0; j < p->usYcNum && j < FAULT_YC_NUM; j++)
{
p->staYc[j].uiInfoAddr = g_fault_ring.fault[i].yc[j].info_addr;
p->staYc[j].fVal = g_fault_ring.fault[i].yc[j].val;
}
sem_post(&g_fault_ring.sem); return 1;
}
static int32_t cb_fault_num(void *pv)
{
auto *o = ring_out_get(pv, g_fault_ring.out_ptrs, 4);
ring_sem_init(&g_fault_ring.init, &g_fault_ring.sem); sem_wait(&g_fault_ring.sem);
int n = ring_count(g_fault_ring.in, o->out, FAULT_EVENT_LEN);
sem_post(&g_fault_ring.sem); return n;
}
static int32_t cb_fault_upd(void *pv, uint32_t pos)
{
if(!pos || !pv) return 0;
auto *o = ring_out_get(pv, g_fault_ring.out_ptrs, 4);
ring_sem_init(&g_fault_ring.init, &g_fault_ring.sem); sem_wait(&g_fault_ring.sem);
o->out = (o->out + pos) % FAULT_EVENT_LEN; sem_post(&g_fault_ring.sem);
return 1;
}
/* =========================== YK (遥控) 回调 =========================== */
static uint8_t cb_yk_verify(void *pv) { (void)pv; return 0; }
static int cb_yk_set(Yk_Info *p, void *pv)
{
(void)pv; auto &v = iec_cfg_ptr_get()->vec_co;
for(auto &c : v)
{
if(c.inf == p->uiInfoAddr && c.p_data)
{
if(0 == ((stru_iec*)pv)->cs10x.usYxType) /* 单点 */
*(uint8_t*)c.p_data = p->ucStatus;
else { /* 双点: 1→0, 2→1 */
*(uint8_t*)c.p_data = (p->ucStatus == 1) ? 0 :
(p->ucStatus == 2) ? 1 : p->ucStatus;
}
}
}
return 0;
}
/* =========================== Param (定值/参数) 回调 =========================== */
static uint16_t cb_param_area(uint8_t t, void *h) { (void)t; (void)h; return 1; }
static uint16_t cb_param_set_area(uint16_t a, void *h) { (void)h; return a; }
static uint8_t cb_param_add_iec(uint8_t ucAll, uint8_t ucForce, uint8_t ucType,
IECPARAM_T *p, void *pv)
{
(void)ucAll; (void)ucForce; (void)ucType; (void)pv;
return 1;
}
static uint8_t cb_param_rd_run(void *p) { (void)p; return 1; }
static uint8_t cb_param_wt_run(void *p) { (void)p; return 1; }
static uint16_t cb_param_total(void *p)
{
(void)p; auto &v = iec_cfg_ptr_get()->vec_ao;
return static_cast<uint16_t>(v.size());
}
static uint8_t cb_param_get(uint8_t t, uint16_t i, IECPARAM_T *p, void *pv)
{
(void)t; (void)pv;
auto &v = iec_cfg_ptr_get()->vec_ao;
if(i >= v.size()) return 0;
p->uiInfoAddr = v[i].inf; p->ucLen = 1;
return 1;
}
/* =========================== 文件服务回调 =========================== */
static bool cb_dir_get(tFILEDIR_RD_SET *s, tDIR_FILE_INFO *f, uint8_t t, uint16_t i, void *info)
{
(void)s; (void)t; (void)info;
/* TODO: 实现目录检索, 暂返回空 */
(void)f; (void)i; return false;
}
static int cb_dir_num(tFILEDIR_RD_SET *s) { (void)s; return 0; }
static int cb_file_read(uint32_t off, uint8_t *b, uint16_t l, void *pv)
{
(void)off; (void)b; (void)l; (void)pv;
return 0;
}
static uint8_t cb_file_wr_check(tFILE_WD_SET *s, void *p, void *u)
{ (void)s; (void)p; (void)u; return 0; }
static int cb_file_write(uint32_t off, uint8_t *b, uint16_t l, void *f, void *u)
{ (void)off; (void)b; (void)l; (void)f; (void)u; return 0; }
static int cb_file_wr_finish(uint8_t *md5, void *f, void *u)
{ (void)md5; (void)f; (void)u; return 0; }
/* =========================== 协议栈初始化 =========================== */
static void set_ptl_104_param(stru_iec *p_iec)
{
struct CS10x_AppLayerParameters a={};
a.iSzOfVSQ=1; a.iLinkAddr=1; a.iSzOfCOT=2; a.iSzOfCA=2;
a.iPublicAddr=1; a.iMaxSzOfAsduLen=249; a.iSzOfIOA=3;
CS10x_SetAppParameters(&p_iec->cs10x, &a);
struct CS104_APCIParameters api={};
api.iK=12; api.iW=8; api.iT0=10; api.iT1=12; api.iT2=8; api.iT3=15;
CS10x_SetAPCIParameters(&p_iec->cs10x, &api);
CS10x_SetYcType(&p_iec->cs10x, 2); /* 浮点遥测 */
CS10x_SetYxType(&p_iec->cs10x, 0); /* 单点遥信 */
CS10x_SetDdType(&p_iec->cs10x, 1);
CS10x_FrameGap(&p_iec->cs10x, 10);
CS10x_SetMultFrameEn(&p_iec->cs10x, 0);
CS10x_FaultEventEn(&p_iec->cs10x, 0);
CS10x_SetSendSoe(&p_iec->cs10x, 1);
CS10x_SetSendCos(&p_iec->cs10x, 1);
CS10x_SetYKEendFrame(&p_iec->cs10x, 0);
CS10x_SetReadFileVSQ(&p_iec->cs10x, 0);
CS10x_SetPresetContType(&p_iec->cs10x, 0);
}
static void set_ptl_itfc(stru_iec *p_iec)
{
CS10x *cs = &p_iec->cs10x;
CS10x_TimeRWHandler(cs, cb_get_time, cb_set_time);
CS10x_WR_ResetHandler(cs, cb_coi_read, cb_coi_write);
CS10x_S_GetYcByGroupHandler(cs, cb_yc_count, cb_yc_val);
CS10x_S_DiturbYcHandler(cs, cb_yc_disturb_get, cb_yc_disturb_num, cb_yc_disturb_upd);
CS10x_S_GetYxByGroupHandler(cs, cb_yx_count, cb_yx_val);
CS10x_S_SOEHandler(cs, cb_soe_get, cb_soe_num, cb_soe_upd);
CS10x_S_GetDdByGroupHandler(cs, cb_dd_count, cb_dd_val);
CS10x_S_DiturbDduHandler(cs, cb_dd_disturb_get, cb_dd_disturb_num, cb_dd_disturb_upd);
CS10x_S_FaultEventHandler(cs, cb_fault_num, cb_fault_get, cb_fault_upd);
CS10x_S_YkVerifyHandler(cs, cb_yk_verify);
CS10x_S_ParamAreaHandler(cs, cb_param_area, cb_param_set_area);
CS10x_S_IECParamHandler(cs, cb_param_add_iec, cb_param_rd_run, cb_param_wt_run);
CS10x_S_GetParamInfoHandler(cs, cb_param_total, cb_param_get);
CS10x_S_FileReadOprtHandler(cs, cb_dir_get, cb_dir_num, cb_file_read, NULL);
CS10x_S_FileWriteOprtHandler(cs, cb_file_write, cb_file_wr_check, cb_file_wr_finish, NULL);
CS10x_S_UpdateProcHandler(cs, NULL);
CS10x_SetYkInforHandler(cs, cb_yk_set);
}
static void protocol_config(stru_iec *p_iec)
{
CS10x_104Init(&p_iec->cs10x, iec104_data_tx, (void*)&p_iec->interface, CS104_TYPE_S);
set_ptl_104_param(p_iec);
set_ptl_itfc(p_iec);
}
/* =========================== Slot 管理 =========================== */
static stru_iec *iec104_ptr_get(uint32_t interface, CS10x_Type t)
{
uint32_t max_tm = 0; uint8_t max_i = 0; uint8_t free_i = MAX_IEC_NUM;
for(uint8_t i = 0; i < MAX_IEC_NUM; i++)
{
if(interface == g_iec[i].interface)
{
if(g_iec[i].is_busy)
{
g_iec[i].tm_out = 0;
if(t == g_iec[i].iec_type) return &g_iec[i];
g_iec[i].is_busy = 0;
}
else free_i = i;
}
if(max_tm < g_iec[i].tm_out) { max_tm = g_iec[i].tm_out; max_i = i; }
if(!g_iec[i].is_busy && free_i == MAX_IEC_NUM) free_i = i;
}
uint8_t idx = (free_i < MAX_IEC_NUM) ? free_i : max_i;
stru_iec *p = &g_iec[idx];
memset(p, 0, sizeof(*p));
p->is_busy = 1; p->interface = interface; p->iec_type = t;
p->cs10x.stRecvBuf.pBuf = p->rx; p->cs10x.stRecvBuf.usBufSize = IEC_BUF_SIZE;
p->cs10x.stSendBuf.pBuf = p->tx; p->cs10x.stSendBuf.usBufSize = IEC_BUF_SIZE;
p->cs10x.stPubBuf.pBuf = p->pub; p->cs10x.stPubBuf.usBufSize = IEC_BUF_SIZE;
p->cs10x.stSendBakBuf.pBuf = p->bak; p->cs10x.stSendBakBuf.usBufSize = IEC_BUF_SIZE;
protocol_config(p);
LOG_I("iec104: slot[%d] inited, interface=0x%X", idx, interface);
return p;
}
stru_iec *iec104_get_by_interface(uint32_t interface)
{
for(uint8_t i = 0; i < MAX_IEC_NUM; i++)
if(interface == g_iec[i].interface && g_iec[i].is_busy)
return &g_iec[i];
return NULL;
}
/* =========================== 收发桥接 =========================== */
static int iec104_data_tx(uint8_t *p_tx, uint16_t tx_len, void *arg)
{
if(tx_len + sizeof(stru_msg_head) > MSG_SIZE)
{
LOG_E("iec104: tx_len %d > MSG_SIZE", tx_len);
return -1;
}
uint32_t interface = *(uint32_t*)arg;
stru_app *p_app = app_get_ptr(ENUM_APP_IEC);
stru_app_msg_queue *p_mq;
stru_msg_head *p_head;
if(NULL == p_app) return -1;
p_head = (stru_msg_head*)p_app->rtx.tx;
p_head->interface = interface; p_head->len = tx_len;
memcpy(p_head->data, p_tx, tx_len);
p_mq = app_get_msg_queue_ptr(ENUM_MQ_IEC_TO_COM);
if(NULL == p_mq || 0 != task_msg_queue_send(p_mq->p_msg_queue, p_app->rtx.tx, p_mq->msg_size))
return -1;
stru_mq_target tgt = app_get_mq_target(ENUM_MQ_IEC_TO_COM);
stru_app *p_tgt = app_get_ptr(tgt.app_id);
if(NULL != p_tgt && NULL != p_tgt->p_event)
task_event_send(p_tgt->p_event, tgt.event);
return 0;
}
int iec104_data_rx(uint8_t *msg)
{
stru_msg_head *p_head = (stru_msg_head*)msg;
uint32_t interface = p_head->interface;
uint8_t *p_rx = p_head->data;
uint16_t len = p_head->len;
if(NULL == p_rx || len < 2) return -1;
/* 检测帧类型, 目前仅处理 104 (0x68 开头) */
if(p_rx[0] != 0x68) return -1;
stru_iec *p_iec = iec104_ptr_get(interface, CS104_TYPE_S);
if(NULL == p_iec) { LOG_E("iec104: no free slot"); return -1; }
p_iec->tm_out = 0;
CS10x_DoRecv(&p_iec->cs10x, (char*)p_rx, len);
return 0;
}
void iec104_task(uint16_t usGap)
{
for(uint8_t i = 0; i < MAX_IEC_NUM; i++)
{
if(!g_iec[i].is_busy) continue;
g_iec[i].tm_out += usGap;
if(g_iec[i].tm_out >= LINK_OVER_TIME)
{
LOG_I("iec104: slot[%d] timeout, freeing", i);
memset(&g_iec[i], 0, sizeof(stru_iec));
}
else
{
CS10x_TimerHandle(&g_iec[i].cs10x, usGap);
}
}
}
/* =========================== 测试命令 =========================== */
LOCAL void cmd_iec(int argc, char *argv[])
{
const char *str = "iec info 查看 IEC 连接状态\n"
"iec reset 清除所有连接\n";
if(argc < 2) { printf("%s", str); return; }
if(0 == strcmp(argv[1], "info"))
{
printf("===== IEC-104 Status =====\n");
for(int i = 0; i < MAX_IEC_NUM; i++)
{
printf("slot[%d]: busy=%d type=%d if=0x%X tm_out=%u\n",
i, g_iec[i].is_busy, g_iec[i].iec_type, g_iec[i].interface, g_iec[i].tm_out);
}
printf("SOE: in=%d YC-disturb: in=%d DD-disturb: in=%d Fault: in=%d\n",
g_soe_ring.in, g_yc_ring.in, g_dd_ring.in, g_fault_ring.in);
}
else if(0 == strcmp(argv[1], "reset"))
{
for(int i = 0; i < MAX_IEC_NUM; i++) memset(&g_iec[i], 0, sizeof(stru_iec));
LOG_I("iec: all slots reset");
}
else { printf("unknown: %s\n%s", argv[1], str); }
}
LOCAL void cmd_iec_complete(const char *buf, char ***c, int *n)
{
static const char *s[] = {"info", "reset"};
cmd_sub_complete(buf, c, n, s, 2);
}
CMD_REGISTER_C("iec", cmd_iec, "IEC-104 查看/重置", cmd_iec_complete);
/* =========================== 应用线程入口 =========================== */
int app_iec_init1(void *arg)
{
char dir[512] = {};
if(0 != func_proc_self_dir(dir, sizeof(dir)))
{
LOG_E("app_iec_init1: get_exe_dir failed");
return -1;
}
std::string path = std::string(dir) + "config/IEC60870/iec1014.xml";
if(0 != iec_cfg_parse(path.c_str()))
{
LOG_E("app_iec_init1: cfg parse failed: %s", path.c_str());
return -1;
}
stru_app *p = (stru_app*)arg;
dc_signal_out("iec.run_cnt", "iec.run_cnt", DATA_TYPE_U32, &p->run_cnt, "iec104");
LOG_I("app_iec_init1: ok");
return 0;
}
int app_iec_init2(void *arg)
{
(void)arg;
if(0 != iec104_point_init())
{
LOG_E("app_iec_init2: iec104_point_init failed");
return -1;
}
LOG_I("app_iec_init2: ok");
return 0;
}
void *app_iec(void *arg)
{
if(NULL == arg) { LOG_E("app_iec: null arg"); return NULL; }
stru_app *p_app = (stru_app*)arg;
uint32_t ev;
while(1)
{
task_event_recv(p_app->p_event,
EV_TIMER1 | EV_TIMER2 | EV_TIMER3 | EV_IEC_RX_COM,
TASK_EVENT_FLAG_OR | TASK_EVENT_FLAG_CLEAR,
TASK_EVENT_WAIT_FOREVER, &ev);
if(ev & EV_IEC_RX_COM)
{
stru_app_msg_queue *mq = app_get_msg_queue_ptr(ENUM_MQ_COM_TO_IEC);
if(NULL != mq)
while(0 == task_msg_queue_try_recv(mq->p_msg_queue, p_app->rtx.rx, mq->msg_size))
iec104_data_rx(p_app->rtx.rx);
}
if(ev & EV_TIMER1) iec104_task(10);
if(ev & EV_TIMER3) p_app->run_cnt++;
if(ev & EV_STOP) { LOG_I("app_iec: EV_STOP, exiting"); break; }
}
return NULL;
}

View File

@ -0,0 +1,112 @@
/**
* @file iec104_cfg.cpp
* @brief IEC-104 XML tinyxml2 iec1014.xml
*/
#include "iec104_cfg.h"
#include "myLog.h"
#include "myDatacenter.h"
#include <tinyxml2.h>
using namespace tinyxml2;
/* =========================== XML 常量 =========================== */
static const char *ele_St = "St";
static const char *ele_Mx = "Mx";
static const char *ele_Co = "Co";
static const char *ele_Dd = "Dd";
static const char *ele_Ao = "Ao";
static const char *ele_Param = "Param";
static const char *ele_Signal = "Signal";
static const char *attr_desc = "desc";
static const char *attr_link = "link";
static const char *attr_inf = "inf";
static const char *attr_type = "type";
static const char *attr_invert = "invert";
static const char *attr_factor = "factor";
static const char *attr_offset = "offset";
/* =========================== 全局配置实例 =========================== */
static stru_iec_cfg g_cfg;
typedef struct { const char *tag; std::vector<stru_iec_sig> *vec; uint8_t dt; } parse_rule;
static parse_rule g_rules[] = {
{ele_St, &g_cfg.vec_st, DATA_TYPE_U8},
{ele_Mx, &g_cfg.vec_mx, DATA_TYPE_F32},
{ele_Co, &g_cfg.vec_co, DATA_TYPE_U8},
{ele_Dd, &g_cfg.vec_dd, DATA_TYPE_F32},
{ele_Ao, &g_cfg.vec_ao, 0},
{ele_Param, &g_cfg.vec_param, 0},
};
#define RULE_COUNT (sizeof(g_rules) / sizeof(g_rules[0]))
/* =========================== 解析 =========================== */
static int parse_signals(XMLElement *root)
{
for(uint32_t r = 0; r < RULE_COUNT; r++)
{
XMLElement *parent = root->FirstChildElement(g_rules[r].tag);
if(!parent) continue;
XMLElement *sig = parent->FirstChildElement(ele_Signal);
while(sig)
{
stru_iec_sig s = {};
const char *desc = sig->Attribute(attr_desc);
const char *link = sig->Attribute(attr_link);
const char *inf = sig->Attribute(attr_inf);
const char *type = sig->Attribute(attr_type);
const char *invert = sig->Attribute(attr_invert);
const char *factor = sig->Attribute(attr_factor);
const char *offset = sig->Attribute(attr_offset);
s.desc = desc ? desc : "";
s.link = link ? link : "";
s.inf = inf ? (uint16_t)atoi(inf) : 0;
s.type = type ? dc_get_type_id_by_name(type) : 0;
s.invert = invert ? (uint8_t)atoi(invert) : 0;
s.factor = factor ? (float)atof(factor) : 0.0f;
s.offset = offset ? (float)atof(offset) : 0.0f;
/* 默认类型由规则补全 */
if(0 == s.type) s.type = g_rules[r].dt;
g_rules[r].vec->push_back(s);
sig = sig->NextSiblingElement(ele_Signal);
}
}
return 0;
}
/* =========================== 对外 API =========================== */
stru_iec_cfg *iec_cfg_ptr_get(void) { return &g_cfg; }
int iec_cfg_parse(const char *xml_path)
{
if(NULL == xml_path) { LOG_E("iec_cfg_parse: NULL path"); return -1; }
XMLDocument doc;
if(XML_SUCCESS != doc.LoadFile(xml_path))
{
LOG_E("iec_cfg_parse: LoadFile failed for %s", xml_path);
return -1;
}
XMLElement *root = doc.RootElement();
if(!root) { LOG_E("iec_cfg_parse: no root element"); return -1; }
if(0 != parse_signals(root)) return -1;
LOG_I("iec_cfg_parse: %s loaded — ST=%d MX=%d CO=%d DD=%d AO=%d Param=%d",
xml_path,
(int)g_cfg.vec_st.size(), (int)g_cfg.vec_mx.size(),
(int)g_cfg.vec_co.size(), (int)g_cfg.vec_dd.size(),
(int)g_cfg.vec_ao.size(), (int)g_cfg.vec_param.size());
return 0;
}

View File

@ -0,0 +1,64 @@
#include "iec104_cfg.h"
#include "iec104.h"
#include "myLog.h"
#include "myDatacenter.h"
#include <math.h>
#include <time.h>
#include <string.h>
#define MOD "iec104"
extern int cb_soe_write(uint16_t, uint8_t, uint32_t, uint16_t);
extern int cb_yc_disturb_write(uint16_t, float);
extern int cb_dd_disturb_write(uint16_t, float, uint32_t, uint16_t);
extern int cb_fault_write(iec_soe_info_t*, uint8_t, iec_yc_info_t*, uint8_t);
static void on_st(const char *s, uint8_t, const void *p, const void *pr)
{ if(!s||!p||!pr) return; std::string l(s); auto &v=iec_cfg_ptr_get()->vec_st;
for(uint32_t i=0;i<v.size();i++){ if(v[i].link!=l) continue;
uint8_t a=*(const uint8_t*)p,b=*(const uint8_t*)pr; if(a!=b){cb_soe_write(i,a,(uint32_t)time(NULL),0); *(uint8_t*)const_cast<void*>(pr)=a; } } }
static void on_mx(const char *s, uint8_t, const void *p, const void *pr)
{ if(!s||!p||!pr) return; std::string l(s); auto &v=iec_cfg_ptr_get()->vec_mx;
for(uint32_t i=0;i<v.size();i++){ if(v[i].link!=l) continue;
float f=*(const float*)p,lf=*(const float*)pr; if(fabsf(f-lf)>0.1f) cb_yc_disturb_write(i,f); } }
static void on_dd(const char *s, uint8_t, const void *p, const void *pr)
{ if(!s||!p||!pr) return; std::string l(s); auto &v=iec_cfg_ptr_get()->vec_dd;
for(uint32_t i=0;i<v.size();i++){ if(v[i].link!=l) continue;
float f=*(const float*)p,lf=*(const float*)pr; if(fabsf(f-lf)>0.1f){cb_dd_disturb_write(i,f,(uint32_t)time(NULL),0); *(float*)const_cast<void*>(pr)=f;} } }
static void on_yk(const char*, dc_ctrl_step_t, uint8_t, uint8_t, const void*) {}
static void on_ao(const char*, dc_ctrl_step_t, uint8_t, uint8_t, const void*) {}
static void ev_soe(void *d){ if(!d) return; dc_soe_event_t *e=(dc_soe_event_t*)d;
std::string l(e->saddr); auto &v=iec_cfg_ptr_get()->vec_st;
for(uint32_t i=0;i<v.size();i++) if(v[i].link==l){cb_soe_write(i,e->status,e->tm_sec,e->tm_ms);return;} }
static void ev_fault(void *d){ if(!d) return; dc_fault_event_t *e=(dc_fault_event_t*)d;
std::string l(e->saddr); auto &v=iec_cfg_ptr_get()->vec_st;
iec_soe_info_t so={}; for(uint32_t i=0;i<v.size();i++) if(v[i].link==l){so.info_addr=i;break;}
so.status=1;so.sec=e->tm_sec;so.ms=e->tm_ms; cb_fault_write(&so,1,NULL,0); }
static void ev_dd(void *d){ if(!d) return; dc_disturb_event_t *e=(dc_disturb_event_t*)d;
std::string l(e->saddr); auto &v=iec_cfg_ptr_get()->vec_dd;
for(uint32_t i=0;i<v.size();i++) if(v[i].link==l){cb_dd_disturb_write(i,e->f_val,e->tm_sec,e->tm_ms);return;} }
int iec104_point_init(void)
{
dc_event_register_queue_pop(ev_soe);
dc_fault_register_queue_pop(ev_fault);
dc_disturb_dd_register_queue_pop(ev_dd);
stru_iec_cfg *c=iec_cfg_ptr_get(); if(!c) return -1;
for(auto &s:c->vec_st) dc_signal_out_link_with_callback(s.link.c_str(),&s.p_data,on_st,MOD);
for(auto &s:c->vec_mx) dc_signal_out_link_with_callback(s.link.c_str(),&s.p_data,on_mx,MOD);
for(auto &s:c->vec_dd) dc_signal_out_link_with_callback(s.link.c_str(),&s.p_data,on_dd,MOD);
for(auto &s:c->vec_co) dc_signal_yk_link_with_callback(s.link.c_str(),&s.p_data,on_yk,MOD);
for(auto &s:c->vec_ao) dc_signal_ao_link_with_callback(s.link.c_str(),&s.p_data,on_ao,MOD);
void *dummy=NULL; for(auto &s:c->vec_param) dc_signal_param(s.link.c_str(),s.desc.c_str(),s.type,DC_CTRL_SBO,&dummy,1,NULL,MOD);
LOG_I("iec104_point: ST=%d MX=%d CO=%d DD=%d AO=%d Param=%d",
(int)c->vec_st.size(),(int)c->vec_mx.size(),(int)c->vec_co.size(),
(int)c->vec_dd.size(),(int)c->vec_ao.size(),(int)c->vec_param.size());
return 0;
}