693 lines
27 KiB
Markdown
693 lines
27 KiB
Markdown
# lib60870 CBB 库详细解析
|
||
|
||
> **库版本**: IECVer2.01.059_260202 (2026-02-02)
|
||
> **厂家**: 科大智能电气有限公司 (2013-2026)
|
||
> **用途**: IEC 60870-5-101 / 60870-5-104 / 60870-5-103 电力远动规约协议栈
|
||
> **源码路径**: [src/protocol/lib60870/](../../src/protocol/lib60870/)
|
||
> **编译兼容头**: [cbb_compat.h](../../release/inc/cbb_compat.h) (通过 gcc `-include` 注入,不修改库文件)
|
||
|
||
---
|
||
|
||
## 1. 模块总览
|
||
|
||
### 1.1 文件清单
|
||
|
||
```
|
||
src/protocol/lib60870/
|
||
├── ChangeList.md # 版本变更记录 (V2.01.035 → V2.01.059)
|
||
├── 规约参数说明.md # 参数配置字典 (必读)
|
||
├── 规约库默认参数.md # 初始化的默认值
|
||
├── inc/ # 头文件目录
|
||
│ ├── lib60870_inc.h # 基础包含 (stdint/stdio) + 字节序宏
|
||
│ ├── lib60870_common.h # ★ 核心头文件: CS10x 结构体 + 全部 API 声明 + 常量
|
||
│ ├── lib60870_public.h # 公共工具: TaskFlag/checksum/CP56Time 编解码
|
||
│ ├── lib60870_process.h # 公共流程: SOE/故障/扰动检查与调度
|
||
│ ├── Asdu.h # ASDU 数据域解析: 品质描述词 + 发送/解码函数
|
||
│ ├── gb101.h # ★ IEC 101 链路层: 帧编解码/重发/链路状态
|
||
│ ├── gb104.h # ★ IEC 104 APCI 层: I/S/U帧/计时器
|
||
│ ├── gb103.h # IEC 103 规约: 保护通信/通用分类服务
|
||
│ └── md5.h # MD5 校验
|
||
└── src/ # 源文件目录
|
||
├── lib60870_common.c # CS10x 公共接口实现 + 回调注册
|
||
├── lib60870_public.c # 工具函数实现
|
||
├── lib60870_process.c # 公共流程实现
|
||
├── Asdu.c # ASDU 编解码 + 业务数据发送
|
||
├── gb101.c # 101 链路层实现
|
||
├── gb104.c # 104 APCI 层实现
|
||
├── gb103.c # 103 规约实现
|
||
└── md5.c # MD5 实现
|
||
```
|
||
|
||
### 1.2 头文件依赖关系
|
||
|
||
```mermaid
|
||
graph TD
|
||
A[lib60870_inc.h] --> B[lib60870_common.h]
|
||
B --> C[lib60870_public.h]
|
||
B --> D[lib60870_process.h]
|
||
B --> E[Asdu.h]
|
||
B --> F[gb101.h]
|
||
B --> G[gb104.h]
|
||
B --> H[gb103.h]
|
||
C --> E
|
||
D --> E
|
||
E --> F
|
||
E --> G
|
||
```
|
||
|
||
`lib60870_common.h` 是所有模块的**单一依赖入口**。外部只需 `#include "lib60870_common.h"` 即可获得全部类型和 API。
|
||
|
||
---
|
||
|
||
## 2. 核心数据结构
|
||
|
||
### 2.1 协议类型枚举 `CS10x_Type`
|
||
|
||
```c
|
||
typedef enum {
|
||
CS101_TYPE_S = 0, // CS101 从站 (被控站/终端)
|
||
CS101_TYPE_M, // CS101 主站
|
||
CS104_TYPE_S, // CS104 从站 (被控站/终端)
|
||
CS104_TYPE_M, // CS104 主站
|
||
} CS10x_Type;
|
||
```
|
||
|
||
**理解要点**:
|
||
- `_S` = slave/从站 = 终端侧 = **本项目 RTU 使用的模式**
|
||
- `_M` = master/主站 = 调度侧
|
||
- 101 和 104 共用同一个 `CS10x` 结构体,通过类型枚举区分行为
|
||
|
||
### 2.2 核心结构体 `CS10x`(约 300 行,[源码](src/protocol/lib60870/inc/lib60870_common.h#L1300-L1599))
|
||
|
||
```c
|
||
struct CS10x_t { ... } CS10x; // 结构体定义 + typedef
|
||
typedef struct CS10x_t *CS10xHandle_t; // 句柄指针
|
||
```
|
||
|
||
**关键成员分类**:
|
||
|
||
| 分类 | 成员 | 说明 |
|
||
|------|------|------|
|
||
| **协议标识** | `CS10x_Type eCS10x_Type` | 当前协议类型 (101/104 + 主/从) |
|
||
| **101 链路** | `ucAddr`, `ucReSend_Num`, `usReSend_Gap` | 链路地址、重发次数、重发间隔 |
|
||
| | `uc101InitFlag`, `uc101LinkState` | 101 链路初始化状态 |
|
||
| | `CONTROL101 stControl101Up/Down` | 101 控制域位域 |
|
||
| **104 链路** | `usSendNum/usRecvNum`, `usAckSendNum/usAckRecvNum` | 104 发送/接收序号 |
|
||
| | `VIec104Timer stM_vTimer[4]` | T0/T1/T2/T3 定时器 |
|
||
| **数据缓冲** | `VCommBuf stRecvBuf/SendBuf` | 接收/发送环形缓冲区 |
|
||
| **ASDU 参数** | `CS10x_AppLayerParameters stParam` | ASDU 字节长度配置 |
|
||
| | `CS104_APCIParameters st104Param` | 104 K/W/T 参数 |
|
||
| **任务标志** | `VFLAGS stTaskFlags` | 128 位任务位图 |
|
||
| **回调函数** | `pfnSend`, `pfnGetTime`, `pfnSetTime` | ★ 核心回调接口(见 3.1) |
|
||
| | `pfnGetYcCountByGroup` / `pfnGetYcValueByGroup` | 遥测回调 |
|
||
| | `pfnGetYxCountByGroup` / `pfnGetYxStatusByGroup` | 遥信回调 |
|
||
| | `pfnGetSOE` / `pfnUpdata_SOE_Pout` / `pfnGetSOE_SendNum` | SOE 回调 |
|
||
| | `pfnGetFaultEvent` / `pfnUpdata_FaultEvent_Pout` | 故障事件回调 |
|
||
| | `pfnFcYkVerify` | 遥控校验回调 |
|
||
| | `pfnGetDdCountByGroup` / `pfnGetDdValueByGroup` | 电度回调 |
|
||
| | `pfnGetDiturbYc` / `pfnGetTimeDdu` | 扰动遥测/电度时标回调 |
|
||
| **参数读写** | `pfnGetParamArea` / `pfnSetParamArea` | 定值区切换 |
|
||
| | `pfnAddIECParam` / `pfnRunReadIECParam` / `pfnRunWriteIECParam` | 参数读写 |
|
||
| | `pfnGetParamCount` / `pfnGetParamInfoByIndx` | 参数信息查询 |
|
||
| **文件传输** | `pfnGetProDirFile` / `pfnReadFile` / `pfnWrite` 等 | 文件服务回调(*已废弃旧接口,新接口为 `CS10x_S_FileReadOprtHandler`*) |
|
||
| **软件升级** | `pfnOnlineProg` / `pfnUpdateOprt` | 在线升级回调 |
|
||
| **配置参数** | `usYcType(0/1/2)` / `usYxType(0/1)` / `usDdType(0/1)` | 数据类型选择 |
|
||
| | `ucSOEEn`, `ucCOSEn`, `ucYkEndFrameEn` | SOE/COS/遥控结束帧使能 |
|
||
| | `ucMutilFrameEn` | 104 多帧发送使能 |
|
||
| | `ulSummon_Gap`, `ulHeart_Gap`, `ulSyn_Gap` | 定时器间隔(ms) |
|
||
| | `ucInitType`, `ucFileAckType` | 初始化类型/文件确认类型 |
|
||
|
||
### 2.3 ASDU 信息体数据结构
|
||
|
||
```c
|
||
// 遥信—库发出给应用层填充数据
|
||
typedef struct {
|
||
unsigned int uiInfoAddr; // 信息体地址 (点号)
|
||
unsigned char ucStatus; // 状态 (0=分/1=合)
|
||
unsigned char ucStateType; // 0=单点 / 1=双点
|
||
} Yx_Info;
|
||
|
||
// 遥测—库发出给应用层填充数据
|
||
typedef struct {
|
||
unsigned int uiInfoAddr; // 信息体地址
|
||
float fVal; // 遥测值 (浮点)
|
||
unsigned char ucQds; // 品质描述词
|
||
} Yc_Info;
|
||
|
||
// SOE—事件顺序记录
|
||
typedef struct {
|
||
unsigned int uiInfoAddr; // 信息体地址
|
||
unsigned char ucStatus; // 状态
|
||
unsigned char ucStateType; // 单点/双点
|
||
__CP56Time2a stCTime; // 时标 (7字节 CP56 格式)
|
||
} SOE_Info;
|
||
|
||
// 遥控—库传递给应用层校验
|
||
typedef struct {
|
||
unsigned int uiInfoAddr; // 信息体地址
|
||
unsigned char ucStatus; // 命令值 (0=分/1=合)
|
||
unsigned char ucStateType; // 单点遥控/双点遥控
|
||
unsigned char ucYkType; // 选择/执行
|
||
} Yk_Info;
|
||
|
||
// 电度
|
||
typedef struct { unsigned int uiInfoAddr; float fVal; } Dd_Info;
|
||
|
||
// 带时标电度
|
||
typedef struct { unsigned int uiInfoAddr; float fVal; __CP56Time2a stCTime; } TimeDd_Info;
|
||
|
||
// 故障事件
|
||
typedef struct {
|
||
unsigned short usSoeNum;
|
||
SOE_Info staSoe[MAX_FAULT_EVENT_SOE_SIZE];
|
||
unsigned short usYcNum;
|
||
Yc_Info staYc[MAX_FAULT_EVENT_YC_SIZE];
|
||
} FAULT_EVENT_Info;
|
||
|
||
// 参数/定值
|
||
typedef struct {
|
||
unsigned char ucType; // TAG_TYPE_xxx
|
||
unsigned char ucLen; // 数据长度
|
||
union { ... } unVal; // 值 (支持多种类型)
|
||
unsigned int uiInfoAddr; // 信息体地址
|
||
} IECPARAM_T;
|
||
```
|
||
|
||
### 2.4 CP56Time2a 时间格式
|
||
|
||
```c
|
||
typedef struct {
|
||
unsigned char ucLMs; // 毫秒低位
|
||
unsigned char ucHMs; // 毫秒高位 (含 IV 标志位)
|
||
unsigned char ucMin; // 分钟 (含 IV 标志)
|
||
unsigned char ucHour; // 小时 (含 SU 夏令时标志)
|
||
unsigned char ucWeek; // 星期 (0=未用)
|
||
unsigned char ucMonth; // 月
|
||
unsigned char ucYear; // 年 (0-99)
|
||
} __CP56Time2a;
|
||
|
||
// 编解码工具函数
|
||
unsigned char CP56Time2a_Pack2Buf(unsigned char *out, const __CP56Time2a *cp56);
|
||
unsigned char CP56Time2a_GetFromBuf(const unsigned char *in, __CP56Time2a *cp56);
|
||
```
|
||
|
||
### 2.5 帧格式定义
|
||
|
||
```c
|
||
// 104 帧
|
||
typedef struct {
|
||
unsigned char byStartCode; // 0x68
|
||
unsigned char byAPDULen; // APDU 长度
|
||
unsigned char byControl1..4; // 控制域 (4字节)
|
||
unsigned char byASDU[249]; // ASDU 数据
|
||
} VIec104Frame;
|
||
|
||
// 101 帧—联合体:短帧(单字节地址/双字节地址) / 长帧
|
||
typedef union {
|
||
VFrame10 stFrame10; // 双字节地址短帧
|
||
VFrame10_S stFrame10_S; // 单字节地址短帧
|
||
VFrame68 stFrame68; // 长帧
|
||
} VIec101Frame;
|
||
```
|
||
|
||
---
|
||
|
||
## 3. API 体系
|
||
|
||
### 3.1 生命周期 API(必须调用)
|
||
|
||
| 函数 | 说明 |
|
||
|------|------|
|
||
| **`CS10x_101Init(pstSelf, cbSend, arg, CS101_TYPE_S/M)`** | ★ 101 从站初始化 |
|
||
| **`CS10x_104Init(pstSelf, cbSend, arg, CS104_TYPE_S/M)`** | ★ 104 从站初始化 |
|
||
| **`CS10x_DoRecv(pstSelf, pBuf, usLen)`** | ★ 接收数据入口 (每收到一包调用一次) |
|
||
| **`CS10x_TimerHandle(pstSelf, usGap)`** | ★ 定时器驱动入口 (每 usGap ms 调用一次) |
|
||
|
||
> `cbSend` 类型: `int (*)(unsigned char *buf, unsigned short len, void *arg)`
|
||
> 库通过此回调将编码好的报文交给应用层发送。
|
||
|
||
### 3.2 参数配置 API(初始化后、运行前调用)
|
||
|
||
```c
|
||
// ★ 核心 ASDU 参数
|
||
void CS10x_SetAppParameters(CS10xHandle_t pstSelf, CS10x_AppLayerParameters *param);
|
||
// → 链路地址字节数(1-2)、传输原因字节数(1-2)、公共地址字节数(1-2)、
|
||
// 信息体地址字节数(2-3)、链路地址、公共地址
|
||
|
||
// ★ 104 APCI 参数
|
||
void CS10x_SetAPCIParameters(CS10xHandle_t pstSelf, CS104_APCIParameters *param);
|
||
// → K(0-600)、W(1-60)、T0/T1/T2/T3(2-120s)
|
||
|
||
// 数据类型
|
||
void CS10x_SetYcType(pstSelf, usType); // 0=整形 1=标度化 2=浮点
|
||
void CS10x_SetYxType(pstSelf, usType); // 0=单点 1=双点
|
||
void CS10x_SetDdType(pstSelf, usType); // 0=不带时标 1=带时标
|
||
|
||
// 时间间隔 (ms)
|
||
void CS10x_SetSummonGap(pstSelf, uiGap); // 总召间隔 (默认300s)
|
||
void CS10x_SetESummonGap(pstSelf, uiGap); // 电度总召间隔 (默认60s)
|
||
void CS10x_SetHeartTimeGap(pstSelf, uiGap); // 心跳间隔 (默认90s)
|
||
void CS10x_FrameGap(pstSelf, uiGap); // 发送帧间隔 (默认100ms)
|
||
|
||
// 101 特殊
|
||
void CS10x_ResendGap(pstSelf, uiGap); // 重发超时 (默认5s)
|
||
void CS10x_ResendCnt(pstSelf, ucCnt); // 重发次数 (默认5)
|
||
void CS10x_SetInitType(pstSelf, bType); // 简单初始化投退 (1=投入)
|
||
void CS10x_SetFileAckType(pstSelf, ucType); // 文件需确认帧 (0=需要, 1=不需要)
|
||
|
||
// 功能使能
|
||
void CS10x_SetMultFrameEn(pstSelf, ucEn); // 104 多帧发送 (0=关闭 1=开启)
|
||
void CS10x_FaultEventEn(pstSelf, ucEn); // 故障事件 (0=关, 1/2/3=开)
|
||
void CS10x_SetSendSoe(pstSelf, ucEn); // SOE 发送 (0/1)
|
||
void CS10x_SetSendCos(pstSelf, ucEn); // COS 发送 (0/1)
|
||
void CS10x_SetYKEendFrame(pstSelf, ucEn); // 遥控结束帧 (0/1)
|
||
void CS10x_SetReadFileVSQ(pstSelf, ucEn); // 文件传输VSQ状态值
|
||
void CS10x_SetPresetContType(pstSelf, ucEn); // 参数预置忽略后续帧 (淮北主站=1)
|
||
```
|
||
|
||
### 3.3 回调注册 API(按业务域分组)
|
||
|
||
> **约定**: 以 `CS10x_S_` 为前缀的新接口是**推荐使用**的从站回调注册 API。旧的无前缀接口(如 `CS10x_GetYcValueByGroupHandler`)已标记为"待删除"。
|
||
|
||
```c
|
||
// ===== 遥测 (YC) =====
|
||
void CS10x_S_GetYcByGroupHandler(pstSelf,
|
||
CS10x_GetYcCountByGroup cbGetYcCount, // → 返回该组遥测数量
|
||
CS10x_GetYcValueByGroup cbGetYcValue); // → 填充特定遥测值 (Yc_Info*)
|
||
|
||
// ===== 遥信 (YX) =====
|
||
void CS10x_S_GetYxByGroupHandler(pstSelf,
|
||
CS10x_GetYxCountByGroup cbGetYxCount, // → 返回该组遥信数量
|
||
CS10x_GetYxStatusByGroup cbGetYxStatus); // → 填充特定遥信状态 (Yx_Info*)
|
||
|
||
// ===== SOE (事件顺序记录) =====
|
||
void CS10x_S_SOEHandler(pstSelf,
|
||
CS10x_GetSOE cbGetSOE, // → 获取SOE数据
|
||
CS10x_GET_SOE_SendNum cbSendNum, // → 返回待发送SOE条数
|
||
CS10x_Updata_SOE_Pout cbUpdataSOEPout); // → 更新已发送指针
|
||
|
||
// ===== 遥控 (YK) =====
|
||
void CS10x_S_YkVerifyHandler(pstSelf,
|
||
CS10x_YkVerify cbYkVerify); // → 校验遥控命令合法性
|
||
|
||
// ===== 电度 (DD) =====
|
||
void CS10x_S_GetDdByGroupHandler(pstSelf,
|
||
CS10x_GetDdCountByGroup cbGetDdCount,
|
||
CS10x_GetDdValueByGroup cbGetDdValue);
|
||
|
||
// ===== 扰动遥测 (Disturb YC) =====
|
||
void CS10x_S_DiturbYcHandler(pstSelf,
|
||
CS10x_GetDiturbYc cbGetDiturbYc,
|
||
CS10x_GET_DiturbYc_SendNum cbSendNum,
|
||
CS10x_Updata_DiturbYc_Pout cbUpdataPout);
|
||
|
||
// ===== 时标电度 =====
|
||
void CS10x_S_DiturbDduHandler(pstSelf,
|
||
CS10x_GetTimeDdu cbGetTimeDdu,
|
||
CS10x_GetTimeDdu_SendNum cbSendNum,
|
||
CS10x_Updata_TimeDdu_Pout cbUpdataPout);
|
||
|
||
// ===== 故障事件 =====
|
||
void CS10x_S_FaultEventHandler(pstSelf,
|
||
CS10x_GET_Event_SendNum cbSendNum,
|
||
CS10x_GetFaultEvent cbGetFaultEvent,
|
||
CS10x_Updata_FaultEvent_Pout cbUpdataPout);
|
||
|
||
// ===== 参数/定值 =====
|
||
void CS10x_S_ParamAreaHandler(pstSelf,
|
||
CS10x_GetParamArea cbGetParamArea, // 读取当前定值区号
|
||
CS10x_SetParamArea cbSetParamArea); // 切换定值区
|
||
|
||
void CS10x_S_IECParamHandler(pstSelf,
|
||
CS10x_AddIECParam cbAddIECParam, // 添加参数项
|
||
CS10x_RunReadIECParam cbReadIECParam, // 读参数执行
|
||
CS10x_RunWriteIECParam cbWriteIECParam); // 写参数执行
|
||
|
||
void CS10x_S_GetParamInfoHandler(pstSelf,
|
||
CS10x_GetParamCount cbGetParamCount, // 参数总数
|
||
CS10x_GetParamInfoByIndx cbGetParamInfo); // 按索引获取参数信息
|
||
|
||
// ===== 时间 (对时) =====
|
||
void CS10x_TimeRWHandler(pstSelf,
|
||
CS10x_GetTime cbGetTime, // → 获取当前时间 (填充 __CP56Time2a)
|
||
CS10x_SetTime cbSetTime); // → 设置时间 (解析 __CP56Time2a)
|
||
|
||
// ===== 文件服务 =====
|
||
void CS10x_S_FileReadOprtHandler(pstSelf, cbGetPro, cbGetNum, cbRead, pvParam);
|
||
void CS10x_S_FileWriteOprtHandler(pstSelf, cbWrite, cbCheck, cbFinish, pvParam);
|
||
void CS10x_S_UpdateProcHandler(pstSelf, cbUpdateProc); // 软件升级
|
||
```
|
||
|
||
### 3.4 主动发送 API(应用层调用,库提供)
|
||
|
||
```c
|
||
// 任务类
|
||
unsigned char CS10x_SendTask(pstSelf); // 调度所有待发送任务
|
||
|
||
// 遥控主动下发(主站模式使用)
|
||
void CS10x_SetYkTask(pstSelf, usInfoAddr, ucSE, ucCmd, ucDcs, ucQU, bEncrypt);
|
||
```
|
||
|
||
### 3.5 公共工具函数
|
||
|
||
```c
|
||
// TaskFlag 操作
|
||
void SetTaskFlag(VFLAGS *flags, unsigned int flagNo);
|
||
unsigned int GetTaskFlag(VFLAGS *flags, unsigned int flagNo);
|
||
void ClearTaskFlag(VFLAGS *flags, unsigned int flagNo);
|
||
|
||
// 字节序编解码
|
||
unsigned char IntBytes_Pack2Buf(unsigned char *out, unsigned int val, unsigned char size);
|
||
unsigned int IntBytes_GetFromBuf(const unsigned char *in, unsigned char size);
|
||
unsigned char Float_Pack2Buf(unsigned char *out, float val);
|
||
float Float_GetFromBuf(const unsigned char *in);
|
||
unsigned char U16_Pack2Buf(unsigned char *out, unsigned short val);
|
||
unsigned short U16_GetFromBuf(const unsigned char *in);
|
||
|
||
// 校验
|
||
unsigned char checksum_8(unsigned char *buf, unsigned char len);
|
||
```
|
||
|
||
|
||
## 4. 典型使用流程
|
||
|
||
### 4.1 IEC 104 从站完整流程
|
||
|
||
```c
|
||
// ============ 步骤 1: 创建规约实例并初始化 ============
|
||
CS10x cs10x;
|
||
memset(&cs10x, 0, sizeof(CS10x));
|
||
CS10x_104Init(&cs10x, MySendCallback, myAppData, CS104_TYPE_S);
|
||
|
||
// ============ 步骤 2: 配置 ASDU 参数 ============
|
||
CS10x_AppLayerParameters appParam = {
|
||
.iSzOfLinkAddr = 0, // 104 无链路地址
|
||
.iSzOfTypeId = 1,
|
||
.iSzOfVSQ = 1,
|
||
.iSzOfCOT = 2, // 传输原因 2 字节
|
||
.iSzOfCA = 2, // 公共地址 2 字节
|
||
.iSzOfIOA = 3, // 信息体地址 3 字节
|
||
.iLinkAddr = 0,
|
||
.iPublicAddr = 1,
|
||
.iMaxSzOfAsduLen = 249
|
||
};
|
||
CS10x_SetAppParameters(&cs10x, &appParam);
|
||
|
||
// ============ 步骤 3: 配置 104 APCI 参数 ============
|
||
CS104_APCIParameters apciParam = {
|
||
.iK = 12,
|
||
.iW = 8,
|
||
.iT0 = 10, // 连接建立超时 10s
|
||
.iT1 = 12, // 发送超时 12s
|
||
.iT2 = 8, // 确认超时 8s
|
||
.iT3 = 15 // 空闲测试超时 15s
|
||
};
|
||
CS10x_SetAPCIParameters(&cs10x, &apciParam);
|
||
|
||
// ============ 步骤 4: 配置数据类型 ============
|
||
CS10x_SetYcType(&cs10x, 2); // 浮点遥测
|
||
CS10x_SetYxType(&cs10x, 0); // 单点遥信
|
||
CS10x_SetDdType(&cs10x, 1); // 带时标电度
|
||
CS10x_FrameGap(&cs10x, 10); // 10ms 帧间隔
|
||
CS10x_SetSummonGap(&cs10x, 300000); // 300s 总召
|
||
|
||
// ============ 步骤 5: 注册回调 ============
|
||
CS10x_S_GetYcByGroupHandler(&cs10x, MyGetYcCount, MyGetYcValue);
|
||
CS10x_S_GetYxByGroupHandler(&cs10x, MyGetYxCount, MyGetYxStatus);
|
||
CS10x_S_SOEHandler(&cs10x, MyGetSOE, MySOESendNum, MyUpdateSOEPout);
|
||
CS10x_S_YkVerifyHandler(&cs10x, MyYkVerify);
|
||
CS10x_TimeRWHandler(&cs10x, MyGetTime, MySetTime);
|
||
|
||
// ============ 步骤 6: 主循环驱动 ============
|
||
while(running) {
|
||
// 接收数据
|
||
int len = recv(sock, buf, sizeof(buf), 0);
|
||
if(len > 0) {
|
||
CS10x_DoRecv(&cs10x, buf, len); // ★ 喂给规约栈
|
||
}
|
||
|
||
// 定时器驱动 (每 10ms 调用一次)
|
||
CS10x_TimerHandle(&cs10x, 10); // ★ 驱动所有定时任务
|
||
}
|
||
```
|
||
|
||
### 4.2 回调函数签名参考
|
||
|
||
```c
|
||
// 发送回调 → 库将编码好的帧传给此函数
|
||
int MySendCallback(unsigned char *buf, unsigned short len, void *arg) {
|
||
return send(mySock, buf, len, 0);
|
||
}
|
||
|
||
// 获取遥测数量 (uiParam=组号)
|
||
int MyGetYcCount(void *param, unsigned int uiParam) {
|
||
return (uiParam == 1) ? g_ycCount : 0;
|
||
}
|
||
|
||
// 获取特定遥测值 (usPos=组内偏移)
|
||
int MyGetYcValue(Yc_Info *info, void *param, unsigned int usPos) {
|
||
info->uiInfoAddr = g_ycTable[usPos].addr; // 信息体地址
|
||
info->fVal = g_ycTable[usPos].value; // 遥测值
|
||
info->ucQds = 0x00; // 品质: 有效
|
||
return 0;
|
||
}
|
||
|
||
// 获取时间 (对时使用)
|
||
int MyGetTime(__CP56Time2a *pTime) {
|
||
struct timespec ts;
|
||
clock_gettime(CLOCK_REALTIME, &ts);
|
||
struct tm *t = gmtime(&ts.tv_sec);
|
||
pTime->ucYear = t->tm_year - 100;
|
||
pTime->ucMonth = t->tm_mon + 1;
|
||
pTime->ucDay = t->tm_mday;
|
||
pTime->ucHour = t->tm_hour;
|
||
pTime->ucMin = t->tm_min;
|
||
int ms = ts.tv_nsec / 1000000 + t->tm_sec % 60 * 1000;
|
||
pTime->ucLMs = ms & 0xFF;
|
||
pTime->ucHMs = (ms >> 8) & 0xFF;
|
||
return 0;
|
||
}
|
||
|
||
// 遥控校验
|
||
int MyYkVerify(Yk_Info *info, void *param) {
|
||
// 校验成功后执行遥控,失败返回 -1
|
||
return executeYk(info->uiInfoAddr, info->ucStatus, info->ucYkType) ? 0 : -1;
|
||
}
|
||
```
|
||
|
||
|
||
## 5. ASDU 类型标识码映射表
|
||
|
||
### 5.1 监视方向(终端 → 主站)
|
||
|
||
| TypeID | 宏 | 说明 | 品质描述词 |
|
||
|--------|-----|------|------------|
|
||
| 1 | `M_SP_NA` | 单点信息 | SIQ |
|
||
| 3 | `M_DP_NA` | 双点信息 | DIQ |
|
||
| 5 | `M_ST_NA` | 步位置信息 | VTI |
|
||
| 7 | `M_BO_NA` | 32位位串 | QDS |
|
||
| 9 | `M_ME_NA` | 测量值(归一化) | QDS |
|
||
| 11 | `M_ME_NB` | 测量值(标度化) | QDS |
|
||
| 13 | `M_ME_NC` | 测量值(短浮点) | QDS |
|
||
| 15 | `M_IT_NA` | 电能脉冲计数量 | - |
|
||
| 20 | `M_PS_NA` | 带状态变位检出的成组单点 | SIQ |
|
||
| 21 | `M_ME_ND` | 不带品质的测量值 | - |
|
||
| 30 | `M_SP_TB` | 带长时标单点 | SIQ |
|
||
| 31 | `M_DP_TB` | 带长时标双点 | DIQ |
|
||
| 34 | `M_ME_TD` | 带长时标测量值 | QDS |
|
||
| 42 | `M_FT_NA` | 故障事件 | QDP |
|
||
| 70 | `M_EI_NA` | 初始化结束 | COI |
|
||
| 206 | `M_IT_NB` | 不带时标累计量(浮点) | - |
|
||
| 207 | `M_IT_TC` | 带时标累计量(浮点) | - |
|
||
|
||
### 5.2 控制方向(主站 → 终端)
|
||
|
||
| TypeID | 宏 | 说明 |
|
||
|--------|-----|------|
|
||
| 45 | `C_SC_NA` | 单点遥控命令 |
|
||
| 46 | `C_DC_NA` | 双点遥控命令 |
|
||
| 47 | `C_RC_NA` | 升降命令 |
|
||
| 48 | `C_SE_NA` | 设定命令 |
|
||
| 55 | `C_SP_NA` | 参数预置(南网) |
|
||
| 100 | `C_IC_NA` | 总召唤命令 |
|
||
| 101 | `C_CI_NA` | 电度召唤命令 |
|
||
| 102 | `C_RD_NA` | 读数据命令 |
|
||
| 103 | `C_CS_NA` | 时钟同步命令 |
|
||
| 104 | `C_TS_NA` | 测试命令 |
|
||
| 105 | `C_RP_NA` | 复位进程命令 |
|
||
| 106 | `C_CD_NA` | 延时获得命令(南网) |
|
||
| 107 | `C_TS_TA` | 带时标测试命令 |
|
||
| 108 | `C_RS_NA_NANWANG` | 读参数命令(南网) |
|
||
| 112 | `P_ME_NC` | 参数预置 |
|
||
| 113 | `P_AC_NA` | 参数固化 |
|
||
| 200 | `C_SR_NA` | 切换定值区 |
|
||
| 201 | `C_RR_NA` | 读定值区号 |
|
||
| 202 | `C_RS_NA` | 读参数和定值 |
|
||
| 203 | `C_WS_NA` | 写参数和定值 |
|
||
| 210 | `F_FR_NA` | 文件升级 |
|
||
| 211 | `F_SR_NA` | 软件升级 |
|
||
|
||
|
||
## 6. 传送原因 (COT) 速查
|
||
|
||
| COT | 宏 | 说明 |
|
||
|------|-----|------|
|
||
| 1 | `COT_PERCYC` | 周期/循环 |
|
||
| 2 | `COT_BACK` | 背景扫描 |
|
||
| 3 | `COT_SPONT` | 突发(变位) |
|
||
| 4 | `COT_INIT` | 初始化 |
|
||
| 5 | `COT_REQ` | 请求或被请求 |
|
||
| 6 | `COT_ACT` | 激活 |
|
||
| 7 | `COT_ACTCON` | 激活确认 |
|
||
| 8 | `COT_DEACT` | 停止激活 |
|
||
| 9 | `COT_DEACTCON` | 停止激活确认 |
|
||
| 10 | `COT_ACTTERM` | 激活结束 |
|
||
| 20 | `COT_INTROGEN` | 响应总召唤 |
|
||
| 37 | `COT_REQCOGCN` | 响应计数量总召唤 |
|
||
| 44 | `COT_E_TYPE` | 未知类型标识 |
|
||
| 45 | `COT_E_REASON` | 未知传送原因 |
|
||
| 46 | `COT_E_CADDR` | 未知公共地址 |
|
||
| 47 | `COT_E_IADDR` | 未知信息体地址 |
|
||
|
||
|
||
## 7. 定时器与任务调度
|
||
|
||
### 7.1 104 定时器
|
||
|
||
| 定时器 | 默认值 | 说明 |
|
||
|--------|--------|------|
|
||
| T0 | 10s | 连接建立超时—发送 STARTDT 后等待确认 |
|
||
| T1 | 12s | 发送 I 帧后等待确认超时 |
|
||
| T2 | 8s | 接收 I 帧后延迟确认 ACK |
|
||
| T3 | 15s | 空闲测试超时—发送 TESTFR |
|
||
|
||
**定时器处理流程**:
|
||
1. `CS10x_TimerHandle()` 被调用 → 遍历 stM_vTimer[4]
|
||
2. T0 超时 → 重试连接 / T1 超时 → 断开链路 / T3 超时 → 发送测试帧
|
||
|
||
### 7.2 任务标志系统
|
||
|
||
库内部使用 128 位位图 (`VFLAGS`) 管理所有待处理任务:
|
||
|
||
| 任务 ID | 枚举值 | 说明 |
|
||
|---------|--------|------|
|
||
| `TSKID_SummonYx` | - | 总招遥信响应 |
|
||
| `TSKID_SummonYc` | - | 总招遥测响应 |
|
||
| `TSKID_SendCOS` | - | 发送变位遥信 |
|
||
| `TSKID_SendSOE` | - | 发送 SOE |
|
||
| `TSKID_TimeSyn` | - | 时钟同步 |
|
||
| `TSKID_HeartBeat` | - | 心跳 |
|
||
| `TSKID_DdSummon` | - | 总招电能量 |
|
||
| `TSKID_FaultSoe` | - | 故障事件 |
|
||
| `TSKID_YcDisturb` | - | 扰动数据 |
|
||
| `TSKID_ChgConst` | - | 修改定值区 |
|
||
| `TSKID_ReadConst` | - | 读取定值区 |
|
||
|
||
**任务调度流程**: `CS10x_TimerHandle` → 累计各定时计数 → 超时时 `SetSendTask` 置位 → `CS10x_SendTask` → 遍历任务标志 → 调用对应的 `Do_*` 函数。
|
||
|
||
|
||
## 8. 品质描述词 (Quality Descriptor)
|
||
|
||
库提供了 5 种品质描述词的位域定义,封装在 `ASDU_CMD_T` union 中:
|
||
|
||
| 类型 | 字段 | 位 | 说明 |
|
||
|------|------|-----|------|
|
||
| **SIQ** (单点) | SPI | 0 | 0=开, 1=合 |
|
||
| | BL | 4 | 0=未锁定, 1=锁定 |
|
||
| | SB | 5 | 0=未取代, 1=取代 |
|
||
| | NT | 6 | 0=当前值, 1=非当前 |
|
||
| | IV | 7 | 0=有效, 1=无效 |
|
||
| **DIQ** (双点) | DPI | 0-1 | 0/3=不确定, 1=开, 2=合 |
|
||
| **QDS** (测量值) | OV | 0 | 0=未溢出, 1=溢出 |
|
||
| **QDP** (保护事件) | EI | 4 | 0=动作时间有效, 1=无效 |
|
||
| **SCO** (单命令) | SCS | 0 | 0=开, 1=合 |
|
||
| | QU | 1-5 | 0=无定义, 1=短脉冲, 2=长脉冲, 3=持续 |
|
||
| | SE | 6 | 0=执行, 1=选择 |
|
||
| **DCO** (双命令) | DCS | 0-1 | 1=开, 2=合 |
|
||
| | SE | 6 | 0=执行, 1=选择 |
|
||
| **QOS** (设定命令) | QL | 0-6 | 0=缺省 |
|
||
| | SE | 6 | 0=执行, 1=选择 |
|
||
| **COI** (初始化) | UI7 | 0-6 | 0=电源合上, 1=手动复位, 2=远方复位 |
|
||
|
||
|
||
## 9. 101 与 104 的关键差异
|
||
|
||
| 特性 | IEC 101 | IEC 104 |
|
||
|------|---------|---------|
|
||
| **传输层** | 串口 (异步字节流) | TCP/IP 网络 |
|
||
| **链路层帧** | `0x10` 短帧 / `0x68` 长帧 | APCI 6字节头 (I/S/U帧) |
|
||
| **地址** | 链路地址 (1-2 byte) | 无链路地址 |
|
||
| **帧同步** | 起始+结束字符 (0x68/0x10+0x16) | APDU 长度字段 |
|
||
| **重传机制** | FCB/FCV + 超时重发 | 发送序号 + 接收序号 + T1 超时 |
|
||
| **链路管理** | REQUEST_LINK / RESET_LINK 帧 | STARTDT/STOPDT/TESTFR U帧 |
|
||
| **初始化** | 复位链路 → 简单/完整初始化 (可选) | TCP 连接 → STARTDT 激活 |
|
||
| **初始化函数** | `CS10x_101Init(..., CS101_TYPE_S)` | `CS10x_104Init(..., CS104_TYPE_S)` |
|
||
| **缓存** | 固定 255 字节帧 | APCI 6 + ASDU max 249 |
|
||
| **多帧** | 101 连帧 (FCB 位机制) | 104 I 帧序号连续发送 |
|
||
|
||
|
||
## 10. 文件服务与升级流程
|
||
|
||
库内建了基于 IEC 60870-5 文件传输服务的文件操作能力:
|
||
|
||
| 操作 | TypeID / 说明 |
|
||
|------|---------------|
|
||
| 目录召唤 | 读目录 → 返回文件列表 |
|
||
| 读文件 | 激活 → 分段传输数据 → 传输确认 |
|
||
| 写文件 | 激活 → 分段写数据 → 写确认 |
|
||
| 软件升级 | 211 `F_SR_NA` → `CS10x_S_UpdateProcHandler` |
|
||
|
||
**注意**: 旧文件服务接口 (`CS10x_FileReadOprtHandler` 不带 `S_` 前缀) 在库中标记为"待删除",应使用 `CS10x_S_FileReadOprtHandler` / `CS10x_S_FileWriteOprtHandler`。
|
||
|
||
|
||
## 11. 注意事项与踩坑指南
|
||
|
||
### 11.1 类型兼容
|
||
- CBB 库使用 `u8/u16/u32/s16/s32/f32/BOOL/TRUE/FALSE` 自定义类型
|
||
- **不能修改库文件** — 通过 `release/inc/cbb_compat.h` + gcc `-include` 注入解决
|
||
- 本项目已在 [makefile](../../release/src/protocol/lib60870/makefile) 中配置
|
||
|
||
### 11.2 回调约定
|
||
- 所有回调返回 `0` = 成功,非 `0` = 失败
|
||
- `cbSend` 回调中不要阻塞
|
||
- 库内部是**同步调用**,`CS10x_DoRecv` 内部会立即调用回调获取数据
|
||
|
||
### 11.3 线程安全
|
||
- 库本身**不是线程安全**的,所有 API 调用建议在同一线程
|
||
- 典型架构:通信线程收数据 → CS10x_DoRecv → 定时器线程驱动 CS10x_TimerHandle
|
||
|
||
### 11.4 多实例支持
|
||
- 每个通信链路创建一个 `CS10x` 实例
|
||
- 调用方通过 `void *arg` (初始化参数) 和 `void *pParam` (回调参数) 传递业务上下文
|
||
|
||
### 11.5 从站 vs 主站
|
||
- **此项目 RTU 作为从站** (`_S` 类型) — 回调负责**提供数据**
|
||
- 如果作为主站 (`_M` 类型) — 回调负责**处理收到的数据**
|
||
|
||
### 11.6 101 序列号与链路状态
|
||
- FCB 位每发送一帧翻转
|
||
- 重发帧 FCB 位不变
|
||
- 链路重置时 FCB 复位
|
||
|
||
### 11.7 104 序号机制
|
||
- 发送序号 `usSendNum` 与接收序号 `usRecvNum` 各自独立递增
|
||
- 当 `usSendNum - usAckRecvNum >= K` 时停止发送 (流量控制)
|
||
- T1 超时断开链路,清初始化完成标识
|
||
|
||
|
||
## 12. 版本关键变更摘要
|
||
|
||
| 版本 | 日期 | 关键修改 |
|
||
|------|------|----------|
|
||
| V2.01.059 | 2026-02-02 | 增加 YC 上传品质描述词字段 |
|
||
| V2.01.058 | 2026-01-31 | 增加确认帧通知回调接口(透传给下行设备) |
|
||
| V2.01.057 | 2025-12-25 | T0-T3 最大范围 120→200 |
|
||
| V2.01.056 | 2025-12-09 | 添加关闭链路接口 |
|
||
| V2.01.055 | 2025-10-30 | 优化序号匹配,>=K 时继续闭锁 |
|
||
| V2.01.054 | 2025-10-27 | 修复总召不上送、序号不匹配问题 |
|
||
| V2.01.044 | 2025-09-03 | 参数预置忽略后续帧 + 修复 T1/T3 超时 |
|
||
| V2.01.043 | 2025-08-11 | 添加普洱/贵州参数读写流程 |
|
||
| V2.01.042 | 2025-07-17 | 南网规约参数读写和延时获得命令 |
|
||
| V2.01.039 | 2025-04-21 | 101 错误帧继续解析后续报文 |
|
||
| V2.01.038 | 2025-03-19 | 101 回复长帧 FCV 默认置 1 |
|