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

214 lines
6.2 KiB
Markdown
Raw Permalink 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.

# 02 - SOE 与扰动数据回调详解
> 源码: [DCU_HOST/applications/data/iec_itfc/YxItfc.c](../../../../../../DCU_HOST/applications/data/iec_itfc/YxItfc.c)
---
## 1. SOE 回调注册
```c
CS10x_S_SOEHandler(pstCs10x, GetSOE, GetSOESendNum, UpdateSoePtrOut);
```
三个回调函数分工:
| 回调 | 作用 | 库调用时机 |
|------|------|-----------|
| `GetSOESendNum(pvArg)` | 返回待发送 SOE 条数 | 调度时检查 |
| `GetSOE(pstInfo, pvParam, uiPos)` | 填充第 uiPos 条 SOE 数据 | 有 SOE 待发时 |
| `UpdateSoePtrOut(pvParam, uiPos)` | 主站确认 n 条后更新指针 | 收到确认帧后 |
---
## 2. GetSOESendNum — 获取待发送数量
```c
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 适配
```c
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 数据
```c
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 格式转换
```c
// 参考实现: 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 — 更新已确认指针
```c
s32 UpdateSoePtrOut(void *pvParam, u32 uiPos) {
u8 ucYxInforNum = GetSOEOutNum(pvParam);
return UserUpdateSoeOut(ucYxInforNum, uiPos);
// → 数据池中将 SOE 队列的读指针向后移动 uiPos 条
}
```
**调用时机**: 当主站对 SOE 帧回复确认 (COT=7) 后CBB 库自动调用此回调告知应用层"前 uiPos 条 SOE 已被主站确认,可以移除了"。
---
## 5. SOE 数据流全景
```mermaid
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. 扰动遥测回调
```c
CS10x_S_DiturbYcHandler(pstCs10x, GetDiturbYc, GetDiturbYcSendNum, UpdateDiturbYcPtrOut);
```
扰动遥测的交互模式与 SOE **完全一致**
- `GetDiturbYcSendNum` → 待发送扰动遥测数量
- `GetDiturbYc(Yc_Info *pstInfo, void *pvParam, u32 uiPos)` → 填充遥测值
- `UpdateDiturbYcPtrOut` → 更新已发送指针
```c
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 那样管理多通道数据出口:
```c
// 简化版: 直接从 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 编码逻辑自动设置,**应用层通常无需手动处理品质位**。仅在需要标记无效数据时:
```c
// 如果数据无效,可在 ucStatus 中设置 IV 位
pstInfo->ucStatus = (evt->valid ? 0x00 : 0x80) | actual_value;
```