RTU/mimo/工程/DCU_HOST/02-SOE与扰动数据.md

6.2 KiB
Raw Permalink Blame History

02 - SOE 与扰动数据回调详解

源码: DCU_HOST/applications/data/iec_itfc/YxItfc.c


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;