RTU/mimo/工程/DCU_HOST/03-遥控与对时.md

248 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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;
}
```