# 03 - 遥控与对时回调详解 > 源码: [DCU_HOST/applications/data/iec_itfc/YkItfc.c](../../../../../../DCU_HOST/applications/data/iec_itfc/YkItfc.c) --- ## 1. 遥控回调注册 ```c // 校验回调 CS10x_S_YkVerifyHandler(pstCs10x, Cs10xYkVerifyItfs); // 遥控信息处理 CS10x_SetYkInforHandler(pstCs10x, SetYkInfor); ``` ### 1.1 Cs10xYkVerifyItfs — 遥控操作前置校验 ```c uint8_t Cs10xYkVerifyItfs(void *pvParam) { uint32_t uiItfs = *((uint32_t *)pvParam); CS10x* pstCs10x = GetEntityCs10x(uiItfs); // 1. 蓝牙不响应遥控 if((uiItfs & 0xff) == 5) return 1; // 2. 非从站不校验 if(CS101_TYPE_S != pstCs10x->eCS10x_Type && CS104_TYPE_S != pstCs10x->eCS10x_Type) return 1; // 3. 加密校验 (可选) if(*pstParam->stComCommunitParam.pucYkWithEncryEn == 1) { // ... 检查通道是否已加密 ... } // 4. ★ IP 白名单校验 (仅 104) if(CS104_TYPE_S == pstCs10x->eCS10x_Type && *pstParam->stIec104Param.pucMasterYkEnable == 1) { uiIp = GetRemoteIpByItfs(uiItfs); if(uiIp != *pstParam->stIec104Param.puiMasterIp1 && uiIp != *pstParam->stIec104Param.puiMasterIp2) return 0; // IP 不在白名单 } return 1; // 校验通过 } ``` **返回值约定**: `0` = 校验失败 (拒绝遥控), `1` = 校验通过。 ### 1.2 SetYkInfor — 遥控命令分发 ```c int SetYkInfor(Yk_Info *pstInfo, void *pvArg) { uint32_t uiArg = *(uint32_t*)pvArg; CS10x *pstSelf = GetEntityCs10x(uiArg); // 1. ★ 遥控类型转换 (CBB库 → 业务层) uint8_t ucYkOrder; if(pstInfo->ucYkType == 0) ucYkOrder = YK_CMD_EXECUTE; // 执行 else if(pstInfo->ucYkType == 1) ucYkOrder = YK_CMD_SELECT; // 选择 else if(pstInfo->ucYkType == 2) ucYkOrder = YK_CMD_CANCEL; // 撤销 // 2. 通过信息体地址查找遥控点表索引 uint16_t usPos; GetPosYkTableInform(&usPos, (uint16_t)pstInfo->uiInfoAddr); // 3. ★ 双点遥控 → 单点转换 uint8_t ucState = pstInfo->ucStatus; if(pstInfo->ucStateType == 1) ucState -= 1; // 双点 1/2 → 单点 0/1 // 4. 组装遥控命令,发送到遥控处理线程 stYK_CMD stCmd = {0}; stCmd.usOprtChannel = usPos; stCmd.ucOprtType = ucYkOrder; stCmd.ucOprtState = ucState; stCmd.pfcNotify = YkResultNofify; // ★ 结果回调 stCmd.stIecInfo.usInfoAddr = pstInfo->uiInfoAddr; stCmd.stIecInfo.ucStateType = pstInfo->ucStateType; stCmd.stIecInfo.ucState = pstInfo->ucStatus; SendYkToThread(&stCmd); // 异步处理 return 1; } ``` --- ## 2. 遥控结果通知 `YkResultNofify` 遥控执行后,DCU_HOST 通过 `SetSendTask` 主动触发 CBB 库发送确认帧: ```c void YkResultNofify(uint8_t ucResult, void *pvArg) { stYK_CMD *pstCmd = (stYK_CMD *)pvArg; CS10x *pstSelf = GetEntityCs10x(pstCmd->uiItfs); switch(ucResult) { case YK_EVT_SEL_OK: // 选择成功 → 激活确认 SetSendTask(pstSelf, TSKID_YkProcess, COT_ACTCON, ucStateType); break; case YK_EVT_SEL_ERR: // 选择失败 → 否定确认 SetSendTask(pstSelf, TSKID_YkProcess, COT_PN | COT_ACTCON, ucStateType); break; case YK_EVT_EXE_OK: // 执行成功 SetSendTask(pstSelf, TSKID_YkProcess, COT_ACTCON, ucStateType); break; case YK_EVT_EXE_ERR: // 执行失败 SetSendTask(pstSelf, TSKID_YkProcess, COT_PN | COT_ACTCON, ucStateType); break; case YK_EVT_TIMEOUT: // 超时 → 撤销确认 或 激活结束 SetSendTask(pstSelf, TSKID_YkProcess, enableYkEndFrame ? COT_ACTTERM : COT_DEACTCON, ucStateType); break; case YK_EVT_CANCEL_OK: // 撤销成功 SetSendTask(pstSelf, TSKID_YkProcess, COT_DEACTCON, ucStateType); break; case YK_EVT_CANCEL_ERR: // 撤销失败 SetSendTask(pstSelf, TSKID_YkProcess, COT_PN | COT_DEACTCON, ucStateType); break; } } ``` --- ## 3. 遥控 SBO 时序 ```mermaid sequenceDiagram participant Master as 主站 participant CBB as lib60870 participant App as 应用层 participant HW as 执行机构 Master->>CBB: C_SC_NA (Select, COT=6) CBB->>App: Cs10xYkVerifyItfs() App-->>CBB: 1 (允许) CBB->>App: SetYkInfor(YK_CMD_SELECT) App->>HW: 选择遥控对象 App->>CBB: YkResultNofify(YK_EVT_SEL_OK) CBB->>Master: C_SC_NA (ActCon, COT=7) Master->>CBB: C_SC_NA (Execute, COT=6) CBB->>App: SetYkInfor(YK_CMD_EXECUTE) App->>HW: 执行遥控动作 App->>CBB: YkResultNofify(YK_EVT_EXE_OK) CBB->>Master: C_SC_NA (ActCon, COT=7) opt 遥控结束帧使能 CBB->>Master: C_SC_NA (ActTerm, COT=10) end ``` --- ## 4. 对时回调 `CS10x_TimeRWHandler` ```c CS10x_TimeRWHandler(pstCs10x, GetTimeItfs, SetTimeItfs); ``` ### 4.1 GetTimeItfs — 获取当前时间 ```c // 参考实现: 从 RTC 获取时间并转为 CP56Time2a s32 GetTimeItfs(__CP56Time2a *pstTime) { struct tm stTm; uint16_t usMs; RTC_GetDateTime(&stTm, &usMs); // 从 RTC 获取 pstTime->ucYear = stTm.tm_year - 100; // 年 (0-99) pstTime->ucMonth = stTm.tm_mon + 1; pstTime->ucDay = stTm.tm_mday; pstTime->ucHour = stTm.tm_hour; pstTime->ucMin = stTm.tm_min; uint16_t totalMs = stTm.tm_sec * 1000 + usMs; pstTime->ucLMs = totalMs & 0xFF; pstTime->ucHMs = (totalMs >> 8) & 0xFF; return 1; } ``` ### 4.2 SetTimeItfs — 对时写入 ```c s32 SetTimeItfs(__CP56Time2a *pstTime) { struct tm stTm; uint16_t usMs; Cp562Stamp(pstTime, &stTm, &usMs); // CP56 → tm + ms RTC_SetDateTime(&stTm, usMs); // 写入 RTC return 1; } ``` ### 4.3 对时时序 ``` 主站 → C_CS_NA (时钟同步命令, COT=6) → CBB 库调用 GetTimeItfs() 获取当前时间 (用于组包) → CBB 库调用 SetTimeItfs() 设置收到的时间 → 发送 C_CS_NA 响应 (COT=7) ``` --- ## 5. RTU 适配:遥控集成 datacenter RTU 的遥控应与 `libdatacenter` 的 SBO (Select-Before-Operate) 控制机制对接: ```c // RTU 版 Cs10xYkVerifyItfs uint8_t Cs10xYkVerifyItfs(void *pvParam) { // RTU 不需要 IP 白名单 (由 TCP 连接管理保证安全性) return 1; } // RTU 版 SetYkInfor int SetYkInfor(Yk_Info *pstInfo, void *pvArg) { stru_iec_60870 *p_iec = iec60870_get_by_interface(*(uint32_t*)pvArg); if(pstInfo->ucYkType == 1) { // SELECT dc_sbo_select(p_iec, pstInfo->uiInfoAddr, &pstInfo->ucStatus, sizeof(uint8_t)); } else if(pstInfo->ucYkType == 0) { // EXECUTE dc_sbo_operate(p_iec, pstInfo->uiInfoAddr, &pstInfo->ucStatus, sizeof(uint8_t)); } else { // CANCEL dc_sbo_cancel(p_iec, pstInfo->uiInfoAddr); } return 1; } ``` ### 对时适配 ```c s32 GetTimeItfs(__CP56Time2a *pstTime) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); timespec_to_cp56(&ts, pstTime); return 1; } s32 SetTimeItfs(__CP56Time2a *pstTime) { struct timespec ts; cp56_to_timespec(pstTime, &ts); clock_settime(CLOCK_REALTIME, &ts); return 1; } ```