248 lines
7.1 KiB
Markdown
248 lines
7.1 KiB
Markdown
# 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;
|
||
}
|
||
```
|