6.2 KiB
6.2 KiB
02 - SOE 与扰动数据回调详解
1. SOE 回调注册
CS10x_S_SOEHandler(pstCs10x, GetSOE, GetSOESendNum, UpdateSoePtrOut);
三个回调函数分工:
| 回调 | 作用 | 库调用时机 |
|---|---|---|
GetSOESendNum(pvArg) |
返回待发送 SOE 条数 | 调度时检查 |
GetSOE(pstInfo, pvParam, uiPos) |
填充第 uiPos 条 SOE 数据 | 有 SOE 待发时 |
UpdateSoePtrOut(pvParam, uiPos) |
主站确认 n 条后更新指针 | 收到确认帧后 |
2. GetSOESendNum — 获取待发送数量
s32 GetSOESendNum(void *pvArg) {
u8 ucYxInforNum = GetSOEOutNum(pvArg); // ★ 获取 SOE 数据出口序号
if(ucYxInforNum >= ALL_INFOR_CHANNEL_NUM) return 0;
return UserGetSoeSendNum(ucYxInforNum); // 从数据池查询待发送 SOE 数量
}
GetSOEOutNum 的作用: DCU_HOST 中有多条通信通道 (4G/以太网/蓝牙等),每个 CS10x 实例绑定了不同通道。GetSOEOutNum 通过 pvArg (即 uiItfs 接口号) 查找对应的 SOE 数据出口序号,确保每个通道的 SOE 互不干扰。
RTU 适配
s32 GetSOESendNum(void *pvArg) {
stru_iec_60870 *p_iec = iec60870_get_by_interface(*(uint32_t*)pvArg);
if(!p_iec) return 0;
return p_iec->soe_queue.count; // 或 dc_get_soe_pending_count()
}
3. GetSOE — 填充单条 SOE 数据
s32 GetSOE(SOE_Info *pstInfo, void *pvParam, u32 uiPos) {
u8 ucYxInforNum = GetSOEOutNum(pvParam);
return UserGetSoe(ucYxInforNum, pstInfo, uiPos, pvParam);
}
// UserGetSoe 的真正实现
s32 UserGetSoe(u8 ucCh, SOE_Info *pstInfo, u32 uiPos, void *pvArg) {
u16 usTabIdx;
// 1. 从数据池获取第 uiPos 条 SOE 原始数据
stYX_CHANGE_INFO stYxInfo;
UserGetSoeEvent(ucCh, &stYxInfo, (u16)uiPos);
// 2. 填充 SOE_Info 结构体
pstInfo->uiInfoAddr = stYxInfo.usYxPot; // 点号
pstInfo->ucStatus = stYxInfo.ucState; // 状态值
pstInfo->ucStateType = stYxInfo.ucType; // 类型
// 3. 双点遥信特殊处理
stLIB60870ENTITY *pstLibEntity = GetEntityNum(*(u32*)pvArg);
if(pstLibEntity->stCs10x.usYxType) {
pstInfo->ucStateType = 1;
pstInfo->ucStatus += 1; // 单点 0/1 → 双点 1/2
} else {
pstInfo->ucStateType = 1;
}
// 4. ★ 时间戳转换: Unix时间戳 → CP56Time2a
Stamp2Cp56(&pstInfo->stCTime, stYxInfo.uiSeconds, stYxInfo.usMs);
return 1;
}
关键:CP56Time2a 格式转换
// 参考实现: Unix time (秒+毫秒) → CP56Time2a (7字节)
void Stamp2Cp56(__CP56Time2a *pCp56, uint32_t uiSeconds, uint16_t usMs) {
struct tm *ptm = gmtime((time_t*)&uiSeconds);
uint16_t uiTotalMs = ptm->tm_sec * 1000 + usMs;
pCp56->ucLMs = uiTotalMs & 0xFF; // 毫秒低字节
pCp56->ucHMs = (uiTotalMs >> 8) & 0xFF; // 毫秒高字节
pCp56->ucMin = ptm->tm_min;
pCp56->ucHour = ptm->tm_hour;
pCp56->ucDay = ptm->tm_mday;
pCp56->ucMonth = ptm->tm_mon + 1;
pCp56->ucYear = ptm->tm_year - 100; // 年 (0-99)
}
CP56Time2a 关键点:
ucHMs第 7 位是 IV 标志 (0=有效, 1=无效)ucHour第 7 位是 SU 标志 (夏令时)- 毫秒范围 0-59999 (分钟1000 + 秒1000 + 毫秒)
4. UpdateSoePtrOut — 更新已确认指针
s32 UpdateSoePtrOut(void *pvParam, u32 uiPos) {
u8 ucYxInforNum = GetSOEOutNum(pvParam);
return UserUpdateSoeOut(ucYxInforNum, uiPos);
// → 数据池中将 SOE 队列的读指针向后移动 uiPos 条
}
调用时机: 当主站对 SOE 帧回复确认 (COT=7) 后,CBB 库自动调用此回调告知应用层"前 uiPos 条 SOE 已被主站确认,可以移除了"。
5. SOE 数据流全景
sequenceDiagram
participant HW as 硬件采集板
participant YX as 遥信数据池
participant CBB as lib60870
participant Master as 主站
HW->>YX: 遥信变位事件 (点号+状态+时标)
YX->>YX: 写入 SOE 队列
loop 10ms 定时调度
CBB->>YX: GetSOESendNum() → N 条待发
alt N > 0
CBB->>YX: GetSOE(uiPos=0)
YX-->>CBB: SOE_Info {点号, 状态, CP56Time}
CBB->>YX: GetSOE(uiPos=1...)
CBB->>Master: M_SP_TB / M_DP_TB (带时标 SOE 帧)
end
end
Master->>CBB: 确认帧 (COT=7)
CBB->>YX: UpdateSoePtrOut(已确认条数)
YX->>YX: 释放已确认 SOE 记录
6. 扰动遥测回调
CS10x_S_DiturbYcHandler(pstCs10x, GetDiturbYc, GetDiturbYcSendNum, UpdateDiturbYcPtrOut);
扰动遥测的交互模式与 SOE 完全一致:
GetDiturbYcSendNum→ 待发送扰动遥测数量GetDiturbYc(Yc_Info *pstInfo, void *pvParam, u32 uiPos)→ 填充遥测值UpdateDiturbYcPtrOut→ 更新已发送指针
s32 GetDiturbYc(Yc_Info *pstInfo, void *pvParam, u32 uiPos) {
stYC_RECORD stYcInfo;
UserGetDisturbYc(ucInforNum, &stYcInfo, (u16)uiPos);
pstInfo->uiInfoAddr = stYcInfo.usIndex;
pstInfo->fVal = stYcInfo.fVal;
return 1;
}
7. RTU 适配建议
SOE 数据来源
RTU 中 SOE 来自 datacenter 的事件队列,无需像 DCU_HOST 那样管理多通道数据出口:
// 简化版: 直接从 datacenter 获取
s32 GetSOESendNum(void *pvArg) {
return dc_get_event_count(EVENT_SOE);
}
s32 GetSOE(SOE_Info *pstInfo, void *pvParam, u32 uiPos) {
dc_event *evt = dc_get_event(EVENT_SOE, uiPos);
if(!evt) return 0;
pstInfo->uiInfoAddr = evt->info_addr;
pstInfo->ucStatus = evt->value.u8;
pstInfo->ucStateType = 0; // 单点
// 时间转换: struct timespec → CP56Time2a
dc_timespec_to_cp56(&evt->timestamp, &pstInfo->stCTime);
return 1;
}
s32 UpdateSoePtrOut(void *pvParam, u32 uiPos) {
dc_ack_events(EVENT_SOE, uiPos);
return 1;
}
品质位设置
SOE 帧的品质位 (SIQ/DIQ) 由 CBB 库内部的 ASDU 编码逻辑自动设置,应用层通常无需手动处理品质位。仅在需要标记无效数据时:
// 如果数据无效,可在 ucStatus 中设置 IV 位
pstInfo->ucStatus = (evt->valid ? 0x00 : 0x80) | actual_value;