10 KiB
libiec 修改计划
日期: 2026-06-29
状态: 阶段一二已完成 ✅ | 阶段三待定
基于: CBB 库 V2.01.059 + RTU 项目 libiec 源码分析
1. 模块概览
libiec 是 RTU 项目中 60870 协议的应用适配层,负责将 CBB 库与数据中心(datacenter)桥接。
┌──────────────────────────┐
│ libcom_decode (通信层) │
│ 原始数据 → interface路由 │
└──────────┬───────────────┘
│
┌──────────▼───────────────┐
│ libiec (本模块) │
│ ┌─────────────────────┐ │
│ │ iec.cpp │ │
│ │ • CS10x 实例管理(×3) │ │
│ │ • 101/104 自动识别 │ │
│ │ • 收发/定时调度 │ │
│ └────────┬────────────┘ │
│ │ │
│ ┌────────▼────────────┐ │
│ │ iec_method.cpp │ │
│ │ • 19个CBB回调实现 │ │
│ │ • 4个全局数据队列 │ │
│ └────────┬────────────┘ │
│ │ │
│ ┌────────▼────────────┐ │
│ │ iec_point.cpp │ │
│ │ • 信号→IEC回调绑定 │ │
│ │ • YX/YC变化检测 │ │
│ └────────┬────────────┘ │
│ │ │
│ ┌────────▼────────────┐ │
│ │ iec_cfg_parse.cpp │ │
│ │ • XML配置解析 │ │
│ └─────────────────────┘ │
└───────────────────────────┘
│
┌──────────▼───────────────┐
│ libdatacenter (数据中心) │
│ 信号池 / SOE队列 / 故障 │
└──────────────────────────┘
2. 发现的问题
2.1 🐛 Bug: GetDdValueByGroup 始终返回 0
文件: iec_method.cpp:540-577
现象: 电度总召/组召获取值时,函数计算了 usMin/usMax 但结尾直接 return 0,从未填充 pstInfo。
int32_t GetDdValueByGroup(..., TimeDd_Info *pstInfo, ...)
{
// ... 计算 usMin, usMax ...
// 缺少: 根据 usIdx 从 vec_dd 获取值并填充 pstInfo
return 0; // BUG: 永远返回 0,pstInfo 未填充
}
修复: 根据 usIdx 和计算的 usMin/usMax 范围从 vec_dd 中读取实际电度值填充 pstInfo。
2.2 🐛 Bug: YX 变化回调被禁用
文件: iec_point.cpp:22-28
现象: 遥信值变化被检测到,但既未写入 SOE 队列,也未更新 p_last_data。
if(val != last_val)
{
// UserWriteDisturbYx(i, val); // 被注释
LOG_I("...");
// *(uint8_t *)p_last_data = val; // 被注释 → 每次变化都触发回调
}
修复:
- 恢复
UserWriteSoeEvent(i, val, ...)调用,确保 YX 变化能入 SOE 队列 - 恢复
*(uint8_t *)p_last_data = val避免重复触发
2.3 🐛 Bug: SOE 事件中 ucType 未赋值
文件: iec_method.cpp:397-411
现象: UserWriteSoeEvent 写入 SOE 时未设置 ucType,后续 GetSOE 依赖此字段判断单/双点。
p_soe_info->usYxPot = usIndex;
p_soe_info->ucState = ucState;
p_soe_info->uiSeconds = uiSeconds;
p_soe_info->usMs = usMs;
// 缺失: p_soe_info->ucType = ...;
修复: 从 vec_st[idx].type 或配置获取遥信类型并赋值。
2.4 🐛 Bug: GetFaultEvent 时间双重写入
文件: iec_method.cpp:718-724
现象: 先用 StampToCp56 计算时间,紧接着 memcpy 覆盖,前者无效。
StampToCp56(&pstInfo->staSoe[i].stCTime, ..., ...); // 被覆盖
memcpy(&pstInfo->staSoe[i].stCTime, &..., sizeof(stCP56Time2a)); // 覆盖前者
修复: 删除 StampToCp56 调用,保留 memcpy(数据已在入队时计算好)。
2.5 ⚠️ 问题: GetTimeItfs/SetTimeItfs 为空桩
文件: iec_method.cpp:20-29
现象: 对时功能完全不可用。
int GetTimeItfs(__CP56Time2a *pTime) { return 0; }
int SetTimeItfs(__CP56Time2a *pTime) { return 0; }
修复: 实现 GetTimeItfs 调用系统时间,SetTimeItfs 调用 settimeofday。
2.6 ⚠️ 问题: 遥控校验始终返回通过
文件: iec_method.cpp:754-757
现象: Cs10xYkVerifyItfs 始终返回 1(允许遥控),无实际校验逻辑。
uint8_t Cs10xYkVerifyItfs(void *pvParam) { return 1; }
修复: 接入数据中心的远方/就地状态和软压板状态进行校验。
2.7 ⚠️ 问题: 硬编码起始地址与 XML inf 属性冲突
文件: iec_method.cpp:9-11
现象: YX/YC/DD 起始地址硬编码,但 XML 配置中已有 inf 属性。
#define YX_START_PORT 0x01
#define YC_START_PORT 0x4001
#define DD_START_PORT 0x6401
在 GetYcValueByGroup 中:
pstInfo->uiInfoAddr = p_mx_info->inf; // 使用了 XML 的 inf
但 GetYcCountByGroup 中:
*puiStartInf = YC_START_PORT; // 仍用硬编码
*puiEndInf = YC_START_PORT + vec_mx.size();
修复: 统一使用 XML 配置中的 inf 值,移除硬编码起始地址。对于没有配置 inf 的点,自动分配序号。
2.8 ⚠️ 问题: 参数/文件/升级回调未注册
文件: iec.cpp:117-125
现象: CBB 库支持的参数读写、文件传输、软件升级功能均已注释。
// CS10x_S_IECParamHandler(...) // 参数读写 → 未注册
// CS10x_S_GetParamInfoHandler(...) // 参数信息 → 未注册
// CS10x_S_FileReadOprtHandler(...) // 文件读取 → 未注册
// CS10x_S_FileWriteOprtHandler(...) // 文件写入 → 未注册
// CS10x_S_UpdateProcHandler(...) // 软件升级 → 未注册
修复: 根据需求逐步启用,实现对应的回调函数。
2.9 ⚠️ 问题: Dd 电度变化回调被禁用
文件: iec_point.cpp:76-79
现象: 电度值变化检测到,但未写入扰动电度队列。
if(fabs(fVal - last_fVal) > 0.1)
{
// UserWriteDisturbDd(i, fVal, 0, 0); // 被注释
// *(float *)p_last_data = fVal; // 被注释
}
修复: 恢复 UserWriteDisturbDd 调用并更新 p_last_data。
3. 执行步骤
阶段一:🐛 Bug 修复(高优先级)
| # | 步骤 | 文件 | 对应问题 |
|---|---|---|---|
| 1.1 | 修复 GetDdValueByGroup 返回值 |
iec_method.cpp |
2.1 |
| 1.2 | 恢复 YX 变化 SOE 写入 | iec_point.cpp |
2.2 |
| 1.3 | 补充 SOE ucType 赋值 | iec_method.cpp |
2.3 |
| 1.4 | 删除 FaultEvent 双重时间写入 | iec_method.cpp |
2.4 |
阶段二:⚠️ 功能完善(中优先级)
| # | 步骤 | 文件 | 对应问题 |
|---|---|---|---|
| 2.1 | 实现 GetTimeItfs/SetTimeItfs |
iec_method.cpp |
2.5 |
| 2.2 | 遥控校验接入数据中心 | iec_method.cpp |
2.6 |
| 2.3 | 恢复电度变化回调 | iec_point.cpp |
2.9 |
阶段三:🔧 架构优化(低优先级)
| # | 步骤 | 文件 | 对应问题 |
|---|---|---|---|
| 3.1 | 统一使用 XML inf 替代硬编码起始地址 |
iec_method.cpp |
2.7 |
| 3.2 | 启用参数读写回调(按需) | iec.cpp + 新增 |
2.8 |
| 3.3 | 启用文件传输回调(按需) | iec.cpp + 新增 |
2.8 |
| 3.4 | 启用软件升级回调(按需) | iec.cpp + 新增 |
2.8 |
4. 修复细节
4.1 GetDdValueByGroup 修复
int32_t GetDdValueByGroup(uint16_t usGroupID, uint16_t usIdx,
TimeDd_Info *pstInfo, void *pvParam, uint8_t ucTimeEn)
{
std::vector<stru_cfg_base> &vec_dd = iec_cfg_ptr_get()->vec_dd;
uint32_t dd_num = vec_dd.size();
if (usIdx >= dd_num) return 0;
// 确定索引范围
uint16_t usMin = 0, usMax = dd_num;
if (usGroupID != 0) {
if (ucTimeEn == 0) { usMin = 0; usMax = 8; }
else { usMin = 8; usMax = dd_num; }
}
if (usIdx >= usMax - usMin) return 0;
uint16_t idx = usMin + usIdx;
stru_cfg_base *p_dd_info = &vec_dd[idx];
pstInfo->uiInfoAddr = p_dd_info->inf;
pstInfo->fVal = p_dd_info->p_data ? *(float *)p_dd_info->p_data : 0.0f;
memset(&pstInfo->stCTime, 0, sizeof(__CP56Time2a));
return 1;
}
4.2 YX 变化恢复
if (val != last_val) {
uint32_t ts_sec = time(NULL);
uint16_t ts_ms = 0;
UserWriteSoeEvent(i, val, ts_sec, ts_ms);
*(uint8_t *)p_last_data = val;
}
4.3 SOE ucType 补充
p_soe_info->usYxPot = usIndex;
p_soe_info->ucState = ucState;
p_soe_info->ucType = vec_st[usIndex].type; // 新增
p_soe_info->uiSeconds = uiSeconds;
p_soe_info->usMs = usMs;
5. 预期影响
| 修复项 | 影响 |
|---|---|
| 2.1 电度 | 电度总召/组召恢复正常 |
| 2.2 YX变化 | 遥信变位能正常产生 SOE 事件 |
| 2.3 SOE ucType | SOE 事件中单/双点遥信正确区分 |
| 2.4 FaultEvent | 消除无效代码,时间戳正确 |
| 2.5 对时 | CBB 库对时功能可用 |
| 2.6 遥控 | 遥控操作接入安全校验 |
| 2.9 电度变化 | 电度扰动数据正常上送 |