1208 lines
47 KiB
Markdown
1208 lines
47 KiB
Markdown
# libiec61850-1.5.3 MMS 客户端 API 开发手册
|
||
|
||
> 本文档基于 libiec61850 v1.5.3 源码,覆盖 MMS 客户端全部公开 C API,达到开发者脱离源码即可进行项目开发的标准。
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [架构概述](#1-架构概述)
|
||
2. [核心数据类型](#2-核心数据类型)
|
||
3. [连接管理](#3-连接管理)
|
||
4. [数据读写](#4-数据读写)
|
||
5. [报告服务(Report)](#5-报告服务report)
|
||
6. [控制服务(Control)](#6-控制服务control)
|
||
7. [数据集服务(DataSet)](#7-数据集服务dataset)
|
||
8. [文件服务(File)](#8-文件服务file)
|
||
9. [日志服务(Log)](#9-日志服务log)
|
||
10. [模型发现服务](#10-模型发现服务)
|
||
11. [SV/GOOSE 控制块处理](#11-svgoose-控制块处理)
|
||
12. [MSO 传输层访问](#12-mms低层传输层访问)
|
||
13. [错误码完整参考](#13-错误码完整参考)
|
||
14. [运行模式:线程模式 vs 非线程模式](#14-运行模式线程模式-vs-非线程模式)
|
||
15. [与 RTU 项目对照](#15-与-rtu-项目对照)
|
||
|
||
---
|
||
|
||
## 1. 架构概述
|
||
|
||
libiec61850 客户端 API 提供两个抽象层级:
|
||
|
||
| 层级 | API 头文件 | 描述 |
|
||
|------|-----------|------|
|
||
| **高层 IEC 61850** | `iec61850_client.h` | 封装 IEC 61850 语义(FC, LD/LN/DO/DA),推荐使用 |
|
||
| **底层 MMS** | `mms_client_connection.h` | 直接操作 MMS 协议 domain/item,一般不需要直接使用 |
|
||
|
||
客户端核心不透明句柄为 `IedConnection`,所有操作围绕它展开。
|
||
|
||
### 协议栈层次
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ 高层 IEC 61850 API (iec61850_client.h) │
|
||
│ IedConnection / ClientReport / ControlObjectClient │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ 底层 MMS API (mms_client_connection.h) │
|
||
│ MmsConnection / MmsValue │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ ISO 传输层 (ACSE + Presentation + Session) │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ ASN.1 BER 编解码 + TCP/IP │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 两类运行模式
|
||
|
||
| 模式 | 创建方式 | 消息处理 | 适用场景 |
|
||
|------|---------|---------|---------|
|
||
| **线程模式**(默认) | `IedConnection_create()` | 库内部后台线程自动处理 | 大多数场景 |
|
||
| **非线程模式** | `IedConnection_createEx(tlsConfig, false)` | 用户周期性调用 `IedConnection_tick()` | 需要精确控制线程的嵌入式系统 |
|
||
|
||
在非线程模式下,禁止使用同步(阻塞)API,必须使用 `*Async` 异步版本。
|
||
|
||
---
|
||
|
||
## 2. 核心数据类型
|
||
|
||
### 2.1 主要不透明句柄
|
||
|
||
```c
|
||
typedef struct sIedConnection* IedConnection; // 客户端连接
|
||
typedef struct sClientReportControlBlock* ClientReportControlBlock; // RCB 本地对象
|
||
typedef struct sClientReport* ClientReport; // 接收到的报告
|
||
typedef struct sClientDataSet* ClientDataSet; // 数据集本地对象
|
||
typedef struct sControlObjectClient* ControlObjectClient; // 控制对象
|
||
typedef struct sClientSVControlBlock* ClientSVControlBlock; // SV控制块
|
||
typedef struct sClientGooseControlBlock* ClientGooseControlBlock; // GOOSE控制块
|
||
```
|
||
|
||
### 2.2 连接状态
|
||
|
||
```c
|
||
typedef enum {
|
||
IED_STATE_CLOSED = 0, // 空闲/关闭
|
||
IED_STATE_CONNECTING = 1, // 连接中
|
||
IED_STATE_CONNECTED = 2, // 已连接
|
||
IED_STATE_CLOSING = 3 // 关闭中
|
||
} IedConnectionState;
|
||
```
|
||
|
||
### 2.3 功能约束(FunctionalConstraint)
|
||
|
||
客户端读写数据时,**必须指定 FC**:
|
||
|
||
| FC 枚举 | 值 | 含义 | 典型使用 |
|
||
|---------|----|------|---------|
|
||
| `IEC61850_FC_ST` | 0 | 状态信息 | 读遥信状态 |
|
||
| `IEC61850_FC_MX` | 1 | 测量值 | 读遥测值 |
|
||
| `IEC61850_FC_SP` | 2 | 设定值 | 读设定点 |
|
||
| `IEC61850_FC_SV` | 3 | 替代值 | 读替代值 |
|
||
| `IEC61850_FC_CF` | 4 | 配置 | 读配置 |
|
||
| `IEC61850_FC_DC` | 5 | 描述 | 读描述 |
|
||
| `IEC61850_FC_SG` | 6 | 定值组 | 读定值组活跃值 |
|
||
| `IEC61850_FC_SE` | 7 | 可编辑定值组 | 读/写编辑定值组 |
|
||
| `IEC61850_FC_CO` | 12 | 控制 | 写控制命令 |
|
||
| `IEC61850_FC_RP` | 15 | 非缓存报告 | RCB 操作 |
|
||
| `IEC61850_FC_BR` | 16 | 缓存报告 | BRCB 操作 |
|
||
|
||
**引用格式**:`"LD名/LN名.DO名.DA名"`,如 `"simpleIOGenericIO/GGIO1.ST.Ind1.stVal"`。
|
||
|
||
### 2.4 控制模型
|
||
|
||
```c
|
||
typedef enum {
|
||
CONTROL_MODEL_STATUS_ONLY = 0, // 只读,不支持控制
|
||
CONTROL_MODEL_DIRECT_NORMAL = 1, // 直控-普通安全
|
||
CONTROL_MODEL_SBO_NORMAL = 2, // 选控-普通安全(需Select再Operate)
|
||
CONTROL_MODEL_DIRECT_ENHANCED = 3,// 直控-增强安全(含CommandTermination)
|
||
CONTROL_MODEL_SBO_ENHANCED = 4 // 选控-增强安全(SelectWithValue再Operate)
|
||
} ControlModel;
|
||
```
|
||
|
||
### 2.5 Quality 质量
|
||
|
||
```c
|
||
typedef uint16_t Quality;
|
||
|
||
// 有效性(低2位)
|
||
#define QUALITY_VALIDITY_GOOD 0
|
||
#define QUALITY_VALIDITY_INVALID 2
|
||
#define QUALITY_VALIDITY_QUESTIONABLE 3
|
||
|
||
// 详情标志
|
||
#define QUALITY_DETAIL_OVERFLOW 4 // 溢出
|
||
#define QUALITY_DETAIL_OUT_OF_RANGE 8 // 超量程
|
||
#define QUALITY_DETAIL_BAD_REFERENCE 16 // 坏基准值
|
||
#define QUALITY_DETAIL_OSCILLATORY 32 // 振荡
|
||
#define QUALITY_DETAIL_FAILURE 64 // 故障
|
||
#define QUALITY_DETAIL_OLD_DATA 128 // 旧数据
|
||
#define QUALITY_DETAIL_INCONSISTENT 256 // 不一致
|
||
#define QUALITY_DETAIL_INACCURATE 512 // 不精确
|
||
#define QUALITY_SOURCE_SUBSTITUTED 1024 // 替代值
|
||
#define QUALITY_TEST 2048 // 测试
|
||
#define QUALITY_OPERATOR_BLOCKED 4096 // 操作员闭锁
|
||
|
||
// Quality 操作函数
|
||
Validity Quality_getValidity(Quality* self);
|
||
void Quality_setValidity(Quality* self, Validity validity);
|
||
void Quality_setFlag(Quality* self, int flag);
|
||
void Quality_unsetFlag(Quality* self, int flag);
|
||
bool Quality_isFlagSet(Quality* self, int flag);
|
||
Quality Quality_fromMmsValue(const MmsValue* mmsValue);
|
||
MmsValue* Quality_toMmsValue(Quality* self, MmsValue* mmsValue);
|
||
```
|
||
|
||
### 2.6 Timestamp
|
||
|
||
```c
|
||
typedef union {
|
||
uint8_t val[8];
|
||
} Timestamp;
|
||
|
||
Timestamp* Timestamp_create(void);
|
||
Timestamp* Timestamp_createFromByteArray(const uint8_t* byteArray);
|
||
void Timestamp_destroy(Timestamp* self);
|
||
void Timestamp_clearFlags(Timestamp* self);
|
||
uint32_t Timestamp_getTimeInSeconds(Timestamp* self); // 秒级时间戳
|
||
uint64_t Timestamp_getTimeInMs(Timestamp* self); // 毫秒级时间戳
|
||
uint64_t Timestamp_getTimeInNs(Timestamp* self); // 纳秒级时间戳
|
||
bool Timestamp_isLeapSecondKnown(Timestamp* self);
|
||
void Timestamp_setLeapSecondKnown(Timestamp* self, bool value);
|
||
bool Timestamp_hasClockFailure(Timestamp* self);
|
||
void Timestamp_setClockFailure(Timestamp* self, bool value);
|
||
bool Timestamp_isClockNotSynchronized(Timestamp* self);
|
||
void Timestamp_setClockNotSynchronized(Timestamp* self, bool value);
|
||
int Timestamp_getSubsecondPrecision(Timestamp* self);
|
||
void Timestamp_setSubsecondPrecision(Timestamp* self, int subsecondPrecision);
|
||
void Timestamp_setTimeInSeconds(Timestamp* self, uint32_t secondsSinceEpoch);
|
||
void Timestamp_setTimeInMilliseconds(Timestamp* self, uint64_t msTime);
|
||
void Timestamp_setTimeInNanoseconds(Timestamp* self, uint64_t nsTime);
|
||
void Timestamp_setByMmsUtcTime(Timestamp* self, const MmsValue* mmsValue);
|
||
MmsValue* Timestamp_toMmsValue(Timestamp* self, MmsValue* mmsValue);
|
||
Timestamp* Timestamp_fromMmsValue(Timestamp* self, MmsValue* mmsValue);
|
||
```
|
||
|
||
### 2.7 回调函数类型速查
|
||
|
||
```c
|
||
// 通用服务回调(写操作完成等)
|
||
typedef void (*IedConnection_GenericServiceHandler)(uint32_t invokeId, void* parameter, IedClientError err);
|
||
|
||
// 连接状态变化回调
|
||
typedef void (*IedConnection_StateChangedHandler)(void* parameter, IedConnection connection, IedConnectionState newState);
|
||
|
||
// 读对象回调
|
||
typedef void (*IedConnection_ReadObjectHandler)(uint32_t invokeId, void* parameter, IedClientError err, MmsValue* value);
|
||
|
||
// 报告接收回调
|
||
typedef void (*ReportCallbackFunction)(void* parameter, ClientReport report);
|
||
|
||
// 控制操作完成回调
|
||
typedef void (*ControlObjectClient_ControlActionHandler)(uint32_t invokeId, void* parameter, IedClientError err, ControlActionType type, bool success);
|
||
|
||
// 命令终止回调
|
||
typedef void (*CommandTerminationHandler)(void* parameter, ControlObjectClient controlClient);
|
||
|
||
// 文件下载数据回调
|
||
typedef bool (*IedClientGetFileHandler)(void* parameter, uint8_t* buffer, uint32_t bytesRead);
|
||
```
|
||
|
||
### 2.8 MmsValue 基础操作
|
||
|
||
`MmsValue` 是所有客户端数据交换的统一容器类型。每个 `MmsValue` 有一个 `MmsType`:
|
||
|
||
```c
|
||
typedef enum {
|
||
MMS_ARRAY = 0, // 数组
|
||
MMS_STRUCTURE = 1, // 结构体
|
||
MMS_BOOLEAN = 2, // 布尔
|
||
MMS_BIT_STRING = 3, // 位串
|
||
MMS_INTEGER = 4, // 有符号整数
|
||
MMS_UNSIGNED = 5, // 无符号整数
|
||
MMS_FLOAT = 6, // 浮点数
|
||
MMS_OCTET_STRING = 7, // 字节串
|
||
MMS_VISIBLE_STRING = 8, // 可见字符串
|
||
MMS_STRING = 9, // MMS 字符串
|
||
MMS_UTC_TIME = 10, // UTC 时间
|
||
MMS_DATA_ACCESS_ERROR = 11,
|
||
} MmsType;
|
||
```
|
||
|
||
**构造**(仅在需要主动创建值时使用,读取操作由库自动创建):
|
||
|
||
```c
|
||
MmsValue* MmsValue_newBoolean(bool boolean);
|
||
MmsValue* MmsValue_newFloat(float value);
|
||
MmsValue* MmsValue_newDouble(double value);
|
||
MmsValue* MmsValue_newIntegerFromInt32(int32_t integer);
|
||
MmsValue* MmsValue_newIntegerFromInt64(int64_t integer);
|
||
MmsValue* MmsValue_newUnsignedFromUint32(uint32_t integer);
|
||
MmsValue* MmsValue_newVisibleString(const char* string);
|
||
MmsValue* MmsValue_newUtcTime(uint32_t timeval); // 秒级时间戳
|
||
MmsValue* MmsValue_newUtcTimeByMsTime(uint64_t timeval); // 毫秒级时间戳
|
||
MmsValue* MmsValue_newOctetString(int size, int maxSize);
|
||
MmsValue* MmsValue_newBitString(int bitSize);
|
||
MmsValue* MmsValue_newStructure(const MmsVariableSpecification* typeSpec);
|
||
MmsValue* MmsValue_createEmptyStructure(int size);
|
||
MmsValue* MmsValue_createEmptyArray(int size);
|
||
```
|
||
|
||
**取值**:
|
||
|
||
```c
|
||
bool MmsValue_getBoolean(const MmsValue* value);
|
||
float MmsValue_toFloat(const MmsValue* self);
|
||
double MmsValue_toDouble(const MmsValue* self);
|
||
int32_t MmsValue_toInt32(const MmsValue* value);
|
||
int64_t MmsValue_toInt64(const MmsValue* self);
|
||
uint32_t MmsValue_toUint32(const MmsValue* value);
|
||
uint64_t MmsValue_getUtcTimeInMs(const MmsValue* value);
|
||
uint64_t MmsValue_getUtcTimeInMsWithUs(const MmsValue* self, uint32_t* usec);
|
||
uint32_t MmsValue_toUnixTimestamp(const MmsValue* self);
|
||
const char* MmsValue_toString(MmsValue* self); // 对 VISIBLE_STRING/STRING 类型
|
||
|
||
// 数组/结构体
|
||
MmsValue* MmsValue_getElement(const MmsValue* array, int index);
|
||
uint32_t MmsValue_getArraySize(const MmsValue* self);
|
||
|
||
// 位串
|
||
bool MmsValue_getBitStringBit(const MmsValue* self, int bitPos);
|
||
int MmsValue_getBitStringSize(const MmsValue* self);
|
||
uint32_t MmsValue_getBitStringAsInteger(const MmsValue* self);
|
||
uint32_t MmsValue_getBitStringAsIntegerBigEndian(const MmsValue* self);
|
||
|
||
// 字节串
|
||
uint16_t MmsValue_getOctetStringSize(const MmsValue* self);
|
||
uint8_t* MmsValue_getOctetStringBuffer(MmsValue* self);
|
||
uint8_t MmsValue_getOctetStringOctet(MmsValue* self, int octetPos);
|
||
```
|
||
|
||
**赋值**:
|
||
|
||
```c
|
||
void MmsValue_setBoolean(MmsValue* value, bool boolValue);
|
||
void MmsValue_setFloat(MmsValue* self, float newFloatValue);
|
||
void MmsValue_setDouble(MmsValue* self, double newFloatValue);
|
||
void MmsValue_setInt32(MmsValue* self, int32_t integer);
|
||
void MmsValue_setInt64(MmsValue* value, int64_t integer);
|
||
void MmsValue_setUint32(MmsValue* value, uint32_t integer);
|
||
void MmsValue_setVisibleString(MmsValue* self, const char* string);
|
||
MmsValue* MmsValue_setUtcTime(MmsValue* self, uint32_t timeval); // 秒
|
||
MmsValue* MmsValue_setUtcTimeMs(MmsValue* self, uint64_t timeval); // 毫秒
|
||
void MmsValue_setOctetString(MmsValue* self, const uint8_t* buf, int size);
|
||
void MmsValue_setBitStringBit(MmsValue* self, int bitPos, bool value);
|
||
void MmsValue_setElement(MmsValue* complexValue, int index, MmsValue* elementValue);
|
||
```
|
||
|
||
**生命周期**:
|
||
|
||
```c
|
||
MmsValue* MmsValue_clone(const MmsValue* self); // 深拷贝,调用者负责释放
|
||
void MmsValue_delete(MmsValue* self); // 递归删除所有子元素
|
||
bool MmsValue_update(MmsValue* self, const MmsValue* source);
|
||
bool MmsValue_equals(const MmsValue* self, const MmsValue* otherValue);
|
||
MmsType MmsValue_getType(const MmsValue* self);
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 连接管理
|
||
|
||
### 3.1 完整连接生命周期
|
||
|
||
```c
|
||
// 步骤1:创建连接
|
||
IedConnection conn = IedConnection_create();
|
||
// 或高级版本:IedConnection_createEx(tlsConfig, useThreads);
|
||
|
||
// 步骤2(可选):设置参数
|
||
IedConnection_setLocalAddress(conn, "0.0.0.0", 0); // 本地绑定地址和端口,0=自动分配
|
||
IedConnection_setConnectTimeout(conn, 5000); // 连接超时(ms),须在connect前调用
|
||
IedConnection_setRequestTimeout(conn, 3000); // 请求超时(ms),可随时调用
|
||
IedConnection_setTimeQuality(conn, true, false, false, 10); // 时间品质
|
||
|
||
// 步骤3:连接服务器(阻塞版本)
|
||
IedClientError err;
|
||
IedConnection_connect(conn, &err, "192.168.1.100", 102);
|
||
if (err != IED_ERROR_OK) { /* 处理错误 */ }
|
||
|
||
// 步骤3':连接服务器(异步版本)
|
||
IedConnection_connectAsync(conn, &err, "192.168.1.100", 102);
|
||
// 轮询 IedConnection_getState(conn) 或安装状态回调来等待连接完成
|
||
|
||
// 步骤4:安装连接状态回调(可选)
|
||
void stateHandler(void* param, IedConnection connection, IedConnectionState newState) {
|
||
switch (newState) {
|
||
case IED_STATE_CONNECTED: /* 连接成功 */ break;
|
||
case IED_STATE_CLOSED: /* 连接关闭 */ break;
|
||
// ...
|
||
}
|
||
}
|
||
IedConnection_installStateChangedHandler(conn, stateHandler, NULL);
|
||
|
||
// 步骤5:进行业务操作...
|
||
|
||
// 步骤6:关闭连接(三选一)
|
||
IedConnection_close(conn); // 直接关闭 TCP(最常用)
|
||
IedConnection_abort(conn, &err); // 发送 ACSE Abort 后关闭
|
||
IedConnection_release(conn, &err); // 发送 MMS Conclude(优雅关闭)
|
||
|
||
// 步骤7:释放资源
|
||
IedConnection_destroy(conn);
|
||
```
|
||
|
||
### 3.2 连接状态查询
|
||
|
||
```c
|
||
IedConnectionState IedConnection_getState(IedConnection self);
|
||
|
||
// 状态流转:
|
||
// CLOSED → connect() → CONNECTING → 连接成功 → CONNECTED
|
||
// CONNECTED → close()/abort() → CLOSING → TCP断开 → CLOSED
|
||
```
|
||
|
||
### 3.3 连接参数
|
||
|
||
| API | 说明 |
|
||
|-----|------|
|
||
| `IedConnection_setLocalAddress(conn, ip, port)` | 绑定本地地址(可选,不调用由OS自动分配) |
|
||
| `IedConnection_setConnectTimeout(conn, ms)` | 连接超时,须在connect前调用 |
|
||
| `IedConnection_setRequestTimeout(conn, ms)` | 请求超时,可随时调整 |
|
||
| `IedConnection_getRequestTimeout(conn)` | 获取当前请求超时值 |
|
||
| `IedConnection_setTimeQuality(conn, a,b,c,d)` | 设置本连接生成的所有时间戳的品质 |
|
||
|
||
### 3.4 获取底层连接
|
||
|
||
```c
|
||
MmsConnection IedConnection_getMmsConnection(IedConnection self);
|
||
```
|
||
返回底层 `MmsConnection` 句柄,可用于直接调用低层 MMS API。
|
||
|
||
---
|
||
|
||
## 4. 数据读写
|
||
|
||
### 4.1 通用读写(通过 FCDA 引用 + FC)
|
||
|
||
```c
|
||
// 同步读:返回 MmsValue*,失败返回 NULL
|
||
MmsValue* val = IedConnection_readObject(conn, &err,
|
||
"simpleIOGenericIO/GGIO1.ST.Ind1.stVal", IEC61850_FC_ST);
|
||
if (val) {
|
||
float f = MmsValue_toFloat(val);
|
||
// 注意:不要手动 delete val,生命周期由库管理
|
||
}
|
||
|
||
// 同步写
|
||
MmsValue* writeVal = MmsValue_newBoolean(true);
|
||
IedConnection_writeObject(conn, &err,
|
||
"simpleIOGenericIO/GGIO1.SP.Pos1.ctlVal", IEC61850_FC_CO, writeVal);
|
||
MmsValue_delete(writeVal);
|
||
|
||
// 异步读
|
||
void readHandler(uint32_t invokeId, void* param, IedClientError err, MmsValue* value) {
|
||
if (err == IED_ERROR_OK) { /* 使用 value */ }
|
||
}
|
||
uint32_t id = IedConnection_readObjectAsync(conn, &err, "ref", IEC61850_FC_ST,
|
||
readHandler, myParam);
|
||
|
||
// 异步写
|
||
uint32_t id = IedConnection_writeObjectAsync(conn, &err, "ref", IEC61850_FC_CO,
|
||
value, genericHandler, myParam);
|
||
```
|
||
|
||
### 4.2 便捷类型读写(推荐用于简单类型)
|
||
|
||
**读**:
|
||
|
||
```c
|
||
bool v = IedConnection_readBooleanValue(conn, &err, "ref", IEC61850_FC_ST);
|
||
float v = IedConnection_readFloatValue(conn, &err, "ref", IEC61850_FC_MX);
|
||
int32_t v = IedConnection_readInt32Value(conn, &err, "ref", IEC61850_FC_ST);
|
||
int64_t v = IedConnection_readInt64Value(conn, &err, "ref", IEC61850_FC_ST);
|
||
uint32_t v = IedConnection_readUnsigned32Value(conn, &err, "ref", IEC61850_FC_ST);
|
||
char* v = IedConnection_readStringValue(conn, &err, "ref", IEC61850_FC_CF); // 需手动 free!
|
||
Quality v = IedConnection_readQualityValue(conn, &err, "ref", IEC61850_FC_ST);
|
||
Timestamp* v = IedConnection_readTimestampValue(conn, &err, "ref", IEC61850_FC_ST, NULL); // 需手动 free!
|
||
```
|
||
|
||
**注意**:`readStringValue` 返回的 `char*` 由库动态分配,调用者必须手动 `free()`。
|
||
|
||
`readTimestampValue` 如果传入 `NULL` 则库分配新 Timestamp 对象,调用者负责 `Timestamp_destroy()`;如果传入已有的 Timestamp 指针则复用。
|
||
|
||
**写**:
|
||
|
||
```c
|
||
IedConnection_writeBooleanValue(conn, &err, "ref", IEC61850_FC_CO, true);
|
||
IedConnection_writeFloatValue(conn, &err, "ref", IEC61850_FC_CO, 1.5f);
|
||
IedConnection_writeInt32Value(conn, &err, "ref", IEC61850_FC_CO, 42);
|
||
IedConnection_writeUnsigned32Value(conn, &err, "ref", IEC61850_FC_CO, 100);
|
||
IedConnection_writeVisibleStringValue(conn, &err, "ref", IEC61850_FC_CF, "hello");
|
||
IedConnection_writeOctetString(conn, &err, "ref", IEC61850_FC_CF, data, len);
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 报告服务(Report)
|
||
|
||
报告是 IEC 61850 客户端接收服务端主动上送数据的机制。
|
||
|
||
### 5.1 RCB 类型
|
||
|
||
| 类型 | 引用名特征 | 行为 |
|
||
|------|-----------|------|
|
||
| **URCB** (Unbuffered) | 含 `RP`(如 `LLN0.RP.EventsRCB01`) | 不缓存,断连后丢失 |
|
||
| **BRCB** (Buffered) | 含 `BR`(如 `LLN0.BR.EventsBRCB01`) | 缓存报告,断连后可重传 |
|
||
|
||
### 5.2 RCB 元素掩码(setRCBValues 时指定要写入哪些字段)
|
||
|
||
```c
|
||
#define RCB_ELEMENT_RPT_ID 1 // 报告ID
|
||
#define RCB_ELEMENT_RPT_ENA 2 // 报告使能
|
||
#define RCB_ELEMENT_RESV 4 // 预留(仅URCB)
|
||
#define RCB_ELEMENT_DATSET 8 // 数据集
|
||
#define RCB_ELEMENT_CONF_REV 16 // 配置版本
|
||
#define RCB_ELEMENT_OPT_FLDS 32 // 选项字段
|
||
#define RCB_ELEMENT_BUF_TM 64 // 缓冲时间
|
||
#define RCB_ELEMENT_SQ_NUM 128 // 序列号
|
||
#define RCB_ELEMENT_TRG_OPS 256 // 触发选项
|
||
#define RCB_ELEMENT_INTG_PD 512 // 完整性周期
|
||
#define RCB_ELEMENT_GI 1024 // 总召
|
||
#define RCB_ELEMENT_PURGE_BUF 2048 // 清除缓冲区(仅BRCB)
|
||
#define RCB_ELEMENT_ENTRY_ID 4096 // 条目ID(仅BRCB)
|
||
#define RCB_ELEMENT_TIME_OF_ENTRY 8192 // 条目时间(仅BRCB)
|
||
#define RCB_ELEMENT_RESV_TMS 16384 // 预留时间(仅BRCB)
|
||
#define RCB_ELEMENT_OWNER 32768 // 所有者
|
||
```
|
||
|
||
### 5.3 触发选项(TrgOps)
|
||
|
||
```c
|
||
#define TRG_OPT_DATA_CHANGED 1 // 数据变化触发
|
||
#define TRG_OPT_QUALITY_CHANGED 2 // 品质变化触发
|
||
#define TRG_OPT_DATA_UPDATE 4 // 数据更新触发
|
||
#define TRG_OPT_INTEGRITY 8 // 周期性触发
|
||
#define TRG_OPT_GI 16 // 总召触发
|
||
#define TRG_OPT_TRANSIENT 128 // 仅上升沿触发(瞬态)
|
||
```
|
||
|
||
### 5.4 报告选项字段(OptFlds)— 决定报告包含哪些信息
|
||
|
||
```c
|
||
#define RPT_OPT_SEQ_NUM 1 // 序列号
|
||
#define RPT_OPT_TIME_STAMP 2 // 时标
|
||
#define RPT_OPT_REASON_FOR_INCLUSION 4 // 包含原因
|
||
#define RPT_OPT_DATA_SET 8 // 数据集名
|
||
#define RPT_OPT_DATA_REFERENCE 16 // 数据引用
|
||
#define RPT_OPT_BUFFER_OVERFLOW 32 // 缓冲区溢出标志
|
||
#define RPT_OPT_ENTRY_ID 64 // 条目ID
|
||
#define RPT_OPT_CONF_REV 128 // 配置版本
|
||
```
|
||
|
||
### 5.5 完整报告配置与接收流程
|
||
|
||
```c
|
||
// 步骤1:获取 RCB(读取服务端当前RCB值)
|
||
ClientReportControlBlock rcb = IedConnection_getRCBValues(conn, &err,
|
||
"simpleIOGenericIO/LLN0.RP.EventsRCB01", NULL);
|
||
if (!rcb || err != IED_ERROR_OK) { /* 错误处理 */ }
|
||
|
||
// 或者异步获取
|
||
uint32_t id = IedConnection_getRCBValuesAsync(conn, &err, rcbRef, NULL, handler, param);
|
||
|
||
// 步骤2:配置 RCB 参数
|
||
ClientReportControlBlock_setRptEna(rcb, true); // 使能报告
|
||
ClientReportControlBlock_setDataSetReference(rcb,
|
||
"simpleIOGenericIO/LLN0$Events"); // 设置数据集
|
||
ClientReportControlBlock_setOptFlds(rcb,
|
||
RPT_OPT_SEQ_NUM | RPT_OPT_TIME_STAMP |
|
||
RPT_OPT_REASON_FOR_INCLUSION | RPT_OPT_DATA_SET |
|
||
RPT_OPT_DATA_REFERENCE | RPT_OPT_BUF_OVERFLOW | RPT_OPT_CONF_REV);
|
||
ClientReportControlBlock_setTrgOps(rcb,
|
||
TRG_OPT_DATA_CHANGED | TRG_OPT_INTEGRITY | TRG_OPT_GI);
|
||
ClientReportControlBlock_setBufTm(rcb, 100); // 缓冲时间 100ms
|
||
ClientReportControlBlock_setIntgPd(rcb, 60000); // 完整性周期 60s
|
||
|
||
// 步骤3:将配置写入服务端
|
||
IedConnection_setRCBValues(conn, &err, rcb,
|
||
RCB_ELEMENT_RPT_ENA | RCB_ELEMENT_OPT_FLDS | RCB_ELEMENT_TRG_OPS |
|
||
RCB_ELEMENT_DATSET | RCB_ELEMENT_BUF_TM | RCB_ELEMENT_INTG_PD,
|
||
false); // false=多变量合并单次MMS写请求(通常建议false)
|
||
|
||
// 异步版本
|
||
IedConnection_setRCBValuesAsync(conn, &err, rcb, mask, false, handler, param);
|
||
|
||
// 步骤4:安装报告回调
|
||
IedConnection_installReportHandler(conn,
|
||
"simpleIOGenericIO/LLN0.RP.EventsRCB01", // RCB引用
|
||
"EventsRpt", // 报告标识符(RptID)
|
||
rcbHandler, // 回调函数
|
||
myParam); // 用户参数
|
||
|
||
// 步骤5:报告回调处理
|
||
void rcbHandler(void* parameter, ClientReport report) {
|
||
// 获取数据集值(MmsValue 数组)
|
||
MmsValue* values = ClientReport_getDataSetValues(report);
|
||
int size = MmsValue_getArraySize(values);
|
||
|
||
// 遍历数据集成员
|
||
for (int i = 0; i < size; i++) {
|
||
ReasonForInclusion reason = ClientReport_getReasonForInclusion(report, i);
|
||
const char* ref = ClientReport_getDataReference(report, i);
|
||
MmsValue* element = MmsValue_getElement(values, i);
|
||
|
||
// 根据类型处理值...
|
||
}
|
||
|
||
// 报告元数据
|
||
if (ClientReport_hasTimestamp(report))
|
||
uint64_t ts = ClientReport_getTimestamp(report);
|
||
if (ClientReport_hasSeqNum(report))
|
||
uint16_t seq = ClientReport_getSeqNum(report);
|
||
if (ClientReport_hasBufOvfl(report))
|
||
bool ovfl = ClientReport_getBufOvfl(report);
|
||
if (ClientReport_hasConfRev(report))
|
||
uint32_t cr = ClientReport_getConfRev(report);
|
||
if (ClientReport_hasSubSeqNum(report)) { // 分段报告
|
||
uint16_t subSeq = ClientReport_getSubSeqNum(report);
|
||
bool more = ClientReport_getMoreSegmentsFollow(report);
|
||
}
|
||
}
|
||
|
||
// 步骤6:触发总召(可选)
|
||
ClientReportControlBlock_setGI(rcb, true);
|
||
IedConnection_setRCBValues(conn, &err, rcb, RCB_ELEMENT_GI, false);
|
||
|
||
// 步骤7:注销报告
|
||
IedConnection_uninstallReportHandler(conn,
|
||
"simpleIOGenericIO/LLN0.RP.EventsRCB01");
|
||
|
||
// 步骤8:释放 RCB 对象
|
||
ClientReportControlBlock_destroy(rcb);
|
||
```
|
||
|
||
### 5.6 包含原因枚举
|
||
|
||
```c
|
||
typedef int ReasonForInclusion;
|
||
#define IEC61850_REASON_NOT_INCLUDED 0 // 未包含
|
||
#define IEC61850_REASON_DATA_CHANGE 1 // 数据变化
|
||
#define IEC61850_REASON_QUALITY_CHANGE 2 // 品质变化
|
||
#define IEC61850_REASON_DATA_UPDATE 4 // 数据更新
|
||
#define IEC61850_REASON_INTEGRITY 8 // 周期性上送
|
||
#define IEC61850_REASON_GI 16 // 总召
|
||
#define IEC61850_REASON_UNKNOWN 32 // 原因未知
|
||
```
|
||
|
||
### 5.7 ClientReport API 完整列表
|
||
|
||
```c
|
||
const char* ClientReport_getDataSetName(ClientReport self);
|
||
MmsValue* ClientReport_getDataSetValues(ClientReport self); // 仅回调内有效
|
||
char* ClientReport_getRcbReference(ClientReport self);
|
||
char* ClientReport_getRptId(ClientReport self);
|
||
ReasonForInclusion ClientReport_getReasonForInclusion(ClientReport self, int elementIndex);
|
||
MmsValue* ClientReport_getEntryId(ClientReport self);
|
||
bool ClientReport_hasTimestamp(ClientReport self);
|
||
uint64_t ClientReport_getTimestamp(ClientReport self);
|
||
bool ClientReport_hasSeqNum(ClientReport self);
|
||
uint16_t ClientReport_getSeqNum(ClientReport self);
|
||
bool ClientReport_hasDataSetName(ClientReport self);
|
||
bool ClientReport_hasReasonForInclusion(ClientReport self);
|
||
bool ClientReport_hasConfRev(ClientReport self);
|
||
uint32_t ClientReport_getConfRev(ClientReport self);
|
||
bool ClientReport_hasBufOvfl(ClientReport self);
|
||
bool ClientReport_getBufOvfl(ClientReport self);
|
||
bool ClientReport_hasDataReference(ClientReport self);
|
||
const char* ClientReport_getDataReference(ClientReport self, int elementIndex);
|
||
bool ClientReport_hasSubSeqNum(ClientReport self); // 分段报告
|
||
uint16_t ClientReport_getSubSeqNum(ClientReport self);
|
||
bool ClientReport_getMoreSegmentsFollow(ClientReport self);
|
||
char* ReasonForInclusion_getValueAsString(ReasonForInclusion reasonCode);
|
||
```
|
||
|
||
### 5.8 ClientReportControlBlock API 完整列表
|
||
|
||
```c
|
||
ClientReportControlBlock ClientReportControlBlock_create(const char* rcbReference);
|
||
void ClientReportControlBlock_destroy(ClientReportControlBlock self);
|
||
char* ClientReportControlBlock_getObjectReference(ClientReportControlBlock self);
|
||
bool ClientReportControlBlock_isBuffered(ClientReportControlBlock self);
|
||
|
||
// Getter/Setter
|
||
const char* ClientReportControlBlock_getRptId(ClientReportControlBlock self);
|
||
void ClientReportControlBlock_setRptId(ClientReportControlBlock self, const char* rptId);
|
||
bool ClientReportControlBlock_getRptEna(ClientReportControlBlock self);
|
||
void ClientReportControlBlock_setRptEna(ClientReportControlBlock self, bool rptEna);
|
||
bool ClientReportControlBlock_getResv(ClientReportControlBlock self);
|
||
void ClientReportControlBlock_setResv(ClientReportControlBlock self, bool resv);
|
||
const char* ClientReportControlBlock_getDataSetReference(ClientReportControlBlock self);
|
||
void ClientReportControlBlock_setDataSetReference(ClientReportControlBlock self, const char* dataSetReference);
|
||
uint32_t ClientReportControlBlock_getConfRev(ClientReportControlBlock self);
|
||
int ClientReportControlBlock_getOptFlds(ClientReportControlBlock self);
|
||
void ClientReportControlBlock_setOptFlds(ClientReportControlBlock self, int optFlds);
|
||
uint32_t ClientReportControlBlock_getBufTm(ClientReportControlBlock self);
|
||
void ClientReportControlBlock_setBufTm(ClientReportControlBlock self, uint32_t bufTm);
|
||
uint16_t ClientReportControlBlock_getSqNum(ClientReportControlBlock self);
|
||
int ClientReportControlBlock_getTrgOps(ClientReportControlBlock self);
|
||
void ClientReportControlBlock_setTrgOps(ClientReportControlBlock self, int trgOps);
|
||
uint32_t ClientReportControlBlock_getIntgPd(ClientReportControlBlock self);
|
||
void ClientReportControlBlock_setIntgPd(ClientReportControlBlock self, uint32_t intgPd);
|
||
bool ClientReportControlBlock_getGI(ClientReportControlBlock self);
|
||
void ClientReportControlBlock_setGI(ClientReportControlBlock self, bool gi);
|
||
bool ClientReportControlBlock_getPurgeBuf(ClientReportControlBlock self);
|
||
void ClientReportControlBlock_setPurgeBuf(ClientReportControlBlock self, bool purgeBuf);
|
||
bool ClientReportControlBlock_hasResvTms(ClientReportControlBlock self);
|
||
int16_t ClientReportControlBlock_getResvTms(ClientReportControlBlock self);
|
||
void ClientReportControlBlock_setResvTms(ClientReportControlBlock self, int16_t resvTms);
|
||
MmsValue* ClientReportControlBlock_getEntryId(ClientReportControlBlock self);
|
||
void ClientReportControlBlock_setEntryId(ClientReportControlBlock self, MmsValue* entryId);
|
||
uint64_t ClientReportControlBlock_getEntryTime(ClientReportControlBlock self);
|
||
MmsValue* ClientReportControlBlock_getOwner(ClientReportControlBlock self);
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 控制服务(Control)
|
||
|
||
客户端控制功能用于向服务端发送遥控命令。支持直控(Direct)和选控(SBO,Select Before Operate)。
|
||
|
||
### 6.1 控制对象生命周期
|
||
|
||
```c
|
||
// 创建(同步版本:会请求服务端获取 ctlModel 等信息,可能阻塞)
|
||
ControlObjectClient ctl = ControlObjectClient_create(
|
||
"simpleIOGenericIO/GGIO1.SP.Pos1", conn);
|
||
|
||
// 创建(扩展版本:不阻塞,需要已知 ctlModel 和 controlObjectSpec)
|
||
ControlObjectClient ctl = ControlObjectClient_createEx(
|
||
objRef, conn, CONTROL_MODEL_SBO_NORMAL, controlObjectSpec);
|
||
|
||
// 销毁
|
||
ControlObjectClient_destroy(ctl);
|
||
```
|
||
|
||
### 6.2 同步控制操作
|
||
|
||
```c
|
||
// === 直控-普通安全 (DIRECT_NORMAL) ===
|
||
// 直接发送 operate
|
||
MmsValue* ctlVal = MmsValue_newBoolean(true);
|
||
bool success = ControlObjectClient_operate(ctl, ctlVal, 0); // operTime=0 立即执行
|
||
MmsValue_delete(ctlVal);
|
||
|
||
// === 选控-普通安全 (SBO_NORMAL) ===
|
||
// 第一步:select
|
||
bool ok = ControlObjectClient_select(ctl);
|
||
// 第二步:operate
|
||
bool ok = ControlObjectClient_operate(ctl, ctlVal, 0);
|
||
|
||
// === 选控-增强安全 (SBO_ENHANCED) ===
|
||
// 第一步:select with value
|
||
bool ok = ControlObjectClient_selectWithValue(ctl, ctlVal);
|
||
// 第二步:operate
|
||
bool ok = ControlObjectClient_operate(ctl, ctlVal, 0);
|
||
|
||
// === 取消操作 ===
|
||
bool ok = ControlObjectClient_cancel(ctl);
|
||
```
|
||
|
||
### 6.3 异步控制操作
|
||
|
||
```c
|
||
void actionHandler(uint32_t invokeId, void* param, IedClientError err,
|
||
ControlActionType type, bool success) {
|
||
switch (type) {
|
||
case CONTROL_ACTION_TYPE_SELECT: /* select 结果 */ break;
|
||
case CONTROL_ACTION_TYPE_OPERATE: /* operate 结果 */ break;
|
||
case CONTROL_ACTION_TYPE_CANCEL: /* cancel 结果 */ break;
|
||
}
|
||
}
|
||
|
||
uint32_t id;
|
||
id = ControlObjectClient_operateAsync(ctl, &err, ctlVal, 0, actionHandler, myParam);
|
||
id = ControlObjectClient_selectAsync(ctl, &err, actionHandler, myParam);
|
||
id = ControlObjectClient_selectWithValueAsync(ctl, &err, ctlVal, actionHandler, myParam);
|
||
id = ControlObjectClient_cancelAsync(ctl, &err, actionHandler, myParam);
|
||
```
|
||
|
||
### 6.4 命令终止回调(增强安全模式)
|
||
|
||
```c
|
||
void terminationHandler(void* parameter, ControlObjectClient controlClient) {
|
||
LastApplError lastErr = ControlObjectClient_getLastApplError(controlClient);
|
||
if (lastErr.error == CONTROL_ERROR_NO_ERROR) {
|
||
// CommandTermination+ (成功)
|
||
} else {
|
||
// CommandTermination- (失败)
|
||
}
|
||
}
|
||
ControlObjectClient_setCommandTerminationHandler(ctl, terminationHandler, param);
|
||
```
|
||
|
||
### 6.5 控制对象其他 API
|
||
|
||
```c
|
||
const char* ControlObjectClient_getObjectReference(ControlObjectClient self);
|
||
ControlModel ControlObjectClient_getControlModel(ControlObjectClient self);
|
||
void ControlObjectClient_setControlModel(ControlObjectClient self, ControlModel ctlModel);
|
||
void ControlObjectClient_changeServerControlModel(ControlObjectClient self, ControlModel ctlModel);
|
||
MmsType ControlObjectClient_getCtlValType(ControlObjectClient self);
|
||
IedClientError ControlObjectClient_getLastError(ControlObjectClient self);
|
||
LastApplError ControlObjectClient_getLastApplError(ControlObjectClient self);
|
||
|
||
// 控制参数设置
|
||
void ControlObjectClient_setTestMode(ControlObjectClient self, bool value);
|
||
void ControlObjectClient_setOrigin(ControlObjectClient self, const char* orIdent, int orCat);
|
||
void ControlObjectClient_setInterlockCheck(ControlObjectClient self, bool value);
|
||
void ControlObjectClient_setSynchroCheck(ControlObjectClient self, bool value);
|
||
void ControlObjectClient_useConstantT(ControlObjectClient self, bool useConstantT);
|
||
```
|
||
|
||
**Originator 类别(orCat)**:
|
||
|
||
```c
|
||
#define CONTROL_ORCAT_NOT_SUPPORTED 0
|
||
#define CONTROL_ORCAT_BAY_CONTROL 1 // 间隔层操作员
|
||
#define CONTROL_ORCAT_STATION_CONTROL 2 // 站控层操作员
|
||
#define CONTROL_ORCAT_REMOTE_CONTROL 3 // 远方控制
|
||
#define CONTROL_ORCAT_AUTOMATIC_BAY 4 // 间隔层自动
|
||
#define CONTROL_ORCAT_AUTOMATIC_STATION 5 // 站控层自动
|
||
#define CONTROL_ORCAT_AUTOMATIC_REMOTE 6 // 远方自动
|
||
#define CONTROL_ORCAT_MAINTENANCE 7 // 维护工具
|
||
#define CONTROL_ORCAT_PROCESS 8 // 过程层
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 数据集服务(DataSet)
|
||
|
||
数据集是一组 FCD/FCDA 引用的集合,用于报告、日志等。
|
||
|
||
### 7.1 读取数据集
|
||
|
||
```c
|
||
// 同步
|
||
ClientDataSet ds = IedConnection_readDataSetValues(conn, &err,
|
||
"simpleIOGenericIO/LLN0$Events", NULL); // NULL=创建新实例
|
||
if (ds) {
|
||
MmsValue* values = ClientDataSet_getValues(ds); // MMS_ARRAY
|
||
int size = ClientDataSet_getDataSetSize(ds);
|
||
char* ref = ClientDataSet_getReference(ds);
|
||
ClientDataSet_destroy(ds);
|
||
}
|
||
|
||
// 异步
|
||
IedConnection_readDataSetValuesAsync(conn, &err, dsRef, NULL, handler, param);
|
||
// 回调: void handler(uint32_t invokeId, void* param, IedClientError err, ClientDataSet dataSet)
|
||
```
|
||
|
||
### 7.2 创建/删除数据集
|
||
|
||
```c
|
||
// 创建
|
||
LinkedList members = LinkedList_create();
|
||
LinkedList_add(members, strdup("simpleIOGenericIO/GGIO1.ST.Ind1.stVal[ST]"));
|
||
LinkedList_add(members, strdup("simpleIOGenericIO/GGIO1.ST.Ind2.stVal[ST]"));
|
||
IedConnection_createDataSet(conn, &err, "simpleIOGenericIO/LLN0.MyDS", members);
|
||
LinkedList_destroyDeep(members, free);
|
||
|
||
// 异步创建
|
||
IedConnection_createDataSetAsync(conn, &err, ref, members, handler, param);
|
||
|
||
// 删除
|
||
bool deleted = IedConnection_deleteDataSet(conn, &err, "simpleIOGenericIO/LLN0.MyDS");
|
||
|
||
// 异步删除
|
||
IedConnection_deleteDataSetAsync(conn, &err, ref, handler, param);
|
||
```
|
||
|
||
### 7.3 获取数据集目录
|
||
|
||
```c
|
||
// 同步:返回 LinkedList<char*> 所有成员引用
|
||
bool isDeletable;
|
||
LinkedList elements = IedConnection_getDataSetDirectory(conn, &err, ref, &isDeletable);
|
||
|
||
// 异步
|
||
IedConnection_getDataSetDirectoryAsync(conn, &err, ref, handler, param);
|
||
// 回调: void handler(uint32_t id, void* param, IedClientError err, LinkedList<char*> dir, bool isDeletable)
|
||
```
|
||
|
||
### 7.4 写入数据集
|
||
|
||
```c
|
||
LinkedList values = LinkedList_create();
|
||
LinkedList_add(values, MmsValue_newFloat(1.5f));
|
||
LinkedList_add(values, MmsValue_newBoolean(true));
|
||
LinkedList accessResults = NULL;
|
||
IedConnection_writeDataSetValues(conn, &err, ref, values, &accessResults);
|
||
|
||
// 异步
|
||
IedConnection_writeDataSetValuesAsync(conn, &err, ref, values, handler, param);
|
||
// 回调: void handler(uint32_t id, void* param, IedClientError err, LinkedList accessResults)
|
||
```
|
||
|
||
### 7.5 ClientDataSet API
|
||
|
||
```c
|
||
void ClientDataSet_destroy(ClientDataSet self);
|
||
MmsValue* ClientDataSet_getValues(ClientDataSet self); // MMS_ARRAY
|
||
char* ClientDataSet_getReference(ClientDataSet self);
|
||
int ClientDataSet_getDataSetSize(ClientDataSet self); // 成员数
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 文件服务(File)
|
||
|
||
### 8.1 获取文件目录
|
||
|
||
```c
|
||
// 获取根目录
|
||
LinkedList /*<FileDirectoryEntry>*/ dir = IedConnection_getFileDirectory(conn, &err, NULL);
|
||
if (dir) {
|
||
// 遍历
|
||
LinkedList element = LinkedList_getNext(dir);
|
||
while (element) {
|
||
FileDirectoryEntry entry = (FileDirectoryEntry) element->data;
|
||
const char* name = FileDirectoryEntry_getFileName(entry);
|
||
uint32_t size = FileDirectoryEntry_getFileSize(entry);
|
||
uint64_t mtime = FileDirectoryEntry_getLastModified(entry);
|
||
element = LinkedList_getNext(element);
|
||
}
|
||
LinkedList_destroyDeep(dir, (LinkedListValueDeleteFunction)FileDirectoryEntry_destroy);
|
||
}
|
||
|
||
// 扩展版本(支持分页)
|
||
bool moreFollows;
|
||
LinkedList dir = IedConnection_getFileDirectoryEx(conn, &err, NULL, NULL, &moreFollows);
|
||
// 如果 moreFollows=true,用最后一个文件名做 continueAfter 继续获取
|
||
|
||
// 异步版本
|
||
IedConnection_getFileDirectoryAsyncEx(conn, &err, dirName, continueAfter, handler, param);
|
||
```
|
||
|
||
### 8.2 下载文件(GetFile)
|
||
|
||
```c
|
||
// 同步
|
||
bool fileHandler(void* parameter, uint8_t* buffer, uint32_t bytesRead) {
|
||
// 将 buffer 中的数据写入本地文件
|
||
fwrite(buffer, 1, bytesRead, (FILE*)parameter);
|
||
return true; // 返回 true 继续下载
|
||
}
|
||
uint32_t totalBytes = IedConnection_getFile(conn, &err, "remoteFile.txt", fileHandler, fp);
|
||
|
||
// 异步
|
||
bool asyncFileHandler(uint32_t invokeId, void* parameter, IedClientError err,
|
||
uint32_t originalInvokeId, uint8_t* buffer, uint32_t bytesRead, bool moreFollows) {
|
||
if (err != IED_ERROR_OK) { /* 错误处理 */ return false; }
|
||
fwrite(buffer, 1, bytesRead, (FILE*)parameter);
|
||
return moreFollows; // 还有更多数据
|
||
}
|
||
uint32_t id = IedConnection_getFileAsync(conn, &err, "remoteFile.txt", asyncFileHandler, fp);
|
||
```
|
||
|
||
### 8.3 上传文件(SetFile)/ 删除文件
|
||
|
||
```c
|
||
// 上传
|
||
IedConnection_setFile(conn, &err, "localFile.txt", "remoteFile.txt");
|
||
// 异步
|
||
IedConnection_setFileAsync(conn, &err, src, dst, handler, param);
|
||
|
||
// 删除
|
||
IedConnection_deleteFile(conn, &err, "remoteFile.txt");
|
||
// 异步
|
||
IedConnection_deleteFileAsync(conn, &err, filename, handler, param);
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 日志服务(Log)
|
||
|
||
```c
|
||
// 按时间范围查询日志
|
||
bool moreFollows;
|
||
LinkedList /* <MmsJournalEntry> */ entries = IedConnection_queryLogByTime(
|
||
conn, &err, "LDName/LNName$LogName", startTime, endTime, &moreFollows);
|
||
|
||
// 按条目ID查询后续日志
|
||
LinkedList entries = IedConnection_queryLogAfter(
|
||
conn, &err, "LDName/LNName$LogName", entryID, timeStamp, &moreFollows);
|
||
|
||
// 异步版本
|
||
IedConnection_queryLogByTimeAsync(conn, &err, ref, start, end, handler, param);
|
||
IedConnection_queryLogAfterAsync(conn, &err, ref, entryID, ts, handler, param);
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 模型发现服务
|
||
|
||
用于动态发现服务器端设备模型结构。
|
||
|
||
### 10.1 获取完整设备模型
|
||
|
||
```c
|
||
IedConnection_getDeviceModelFromServer(conn, &err);
|
||
// 此调用会缓存模型,后续的查询基于缓存,不再产生网络请求
|
||
```
|
||
|
||
### 10.2 目录查询
|
||
|
||
```c
|
||
// 获取逻辑设备列表
|
||
LinkedList /* <char*> */ lds = IedConnection_getLogicalDeviceList(conn, &err);
|
||
// 或:IedConnection_getServerDirectory(conn, &err, false);
|
||
|
||
// 获取逻辑节点列表
|
||
LinkedList lns = IedConnection_getLogicalDeviceDirectory(conn, &err, "LDName");
|
||
|
||
// 获取逻辑节点目录(按ACSI类别过滤)
|
||
LinkedList items = IedConnection_getLogicalNodeDirectory(conn, &err,
|
||
"LDName/LLN0", ACSI_CLASS_DATA_SET);
|
||
|
||
// 获取数据对象(DO)的子元素
|
||
LinkedList das = IedConnection_getDataDirectory(conn, &err, "LDName/GGIO1.ST.Ind1");
|
||
LinkedList das = IedConnection_getDataDirectoryFC(conn, &err, "LDName/GGIO1.ST.Ind1"); // 带 FC 后缀
|
||
LinkedList das = IedConnection_getDataDirectoryByFC(conn, &err, "LDName/GGIO1.ST", IEC61850_FC_ST);
|
||
|
||
// 获取变量规格
|
||
MmsVariableSpecification* spec = IedConnection_getVariableSpecification(conn, &err, ref, fc);
|
||
|
||
// 获取逻辑设备所有MMS变量名
|
||
LinkedList vars = IedConnection_getLogicalDeviceVariables(conn, &err, "LDName");
|
||
// 获取逻辑设备所有数据集名
|
||
LinkedList dss = IedConnection_getLogicalDeviceDataSets(conn, &err, "LDName");
|
||
```
|
||
|
||
### 10.3 ACSI 类别枚举
|
||
|
||
```c
|
||
typedef enum {
|
||
ACSI_CLASS_DATA_OBJECT,
|
||
ACSI_CLASS_DATA_SET,
|
||
ACSI_CLASS_BRCB,
|
||
ACSI_CLASS_URCB,
|
||
ACSI_CLASS_LCB,
|
||
ACSI_CLASS_LOG,
|
||
ACSI_CLASS_SGCB,
|
||
ACSI_CLASS_GoCB,
|
||
ACSI_CLASS_GsCB,
|
||
ACSI_CLASS_MSVCB,
|
||
ACSI_CLASS_USVCB
|
||
} ACSIClass;
|
||
```
|
||
|
||
### 10.4 异步模型发现
|
||
|
||
```c
|
||
IedConnection_getServerDirectoryAsync(conn, &err, NULL, NULL, handler, param);
|
||
IedConnection_getLogicalDeviceVariablesAsync(conn, &err, ld, NULL, NULL, handler, param);
|
||
IedConnection_getLogicalDeviceDataSetsAsync(conn, &err, ld, NULL, NULL, handler, param);
|
||
IedConnection_getVariableSpecificationAsync(conn, &err, ref, fc, handler, param);
|
||
```
|
||
|
||
---
|
||
|
||
## 11. SV/GOOSE 控制块处理
|
||
|
||
### 11.1 SV 控制块
|
||
|
||
```c
|
||
ClientSVControlBlock svcb = ClientSVControlBlock_create(conn, "ref");
|
||
bool ena = ClientSVControlBlock_getSvEna(svcb);
|
||
ClientSVControlBlock_setSvEna(svcb, true);
|
||
char* msvID = ClientSVControlBlock_getMsvID(svcb);
|
||
char* ds = ClientSVControlBlock_getDatSet(svcb); // 需手动 free
|
||
uint32_t confRev = ClientSVControlBlock_getConfRev(svcb);
|
||
uint16_t smpRate = ClientSVControlBlock_getSmpRate(svcb);
|
||
uint8_t smpMod = ClientSVControlBlock_getSmpMod(svcb);
|
||
int noASDU = ClientSVControlBlock_getNoASDU(svcb);
|
||
PhyComAddress addr = ClientSVControlBlock_getDstAddress(svcb);
|
||
int optFlds = ClientSVControlBlock_getOptFlds(svcb);
|
||
bool mcast = ClientSVControlBlock_isMulticast(svcb);
|
||
IedClientError err = ClientSVControlBlock_getLastComError(svcb);
|
||
ClientSVControlBlock_destroy(svcb);
|
||
```
|
||
|
||
### 11.2 GOOSE 控制块
|
||
|
||
```c
|
||
ClientGooseControlBlock gcb = ClientGooseControlBlock_create("ref");
|
||
|
||
// 读取服务端GoCB
|
||
IedConnection_getGoCBValues(conn, &err, "simpleIOGenericIO/LLN0.gcbEvents", gcb);
|
||
// 或传入 NULL 创建新实例:gcb = IedConnection_getGoCBValues(conn, &err, ref, NULL);
|
||
|
||
// 读写属性
|
||
bool ena = ClientGooseControlBlock_getGoEna(gcb);
|
||
ClientGooseControlBlock_setGoEna(gcb, true);
|
||
const char* goID = ClientGooseControlBlock_getGoID(gcb);
|
||
ClientGooseControlBlock_setGoID(gcb, "newID");
|
||
const char* ds = ClientGooseControlBlock_getDatSet(gcb);
|
||
ClientGooseControlBlock_setDatSet(gcb, "LD/LLN0$ds1");
|
||
uint32_t confRev = ClientGooseControlBlock_getConfRev(gcb);
|
||
bool ndsComm = ClientGooseControlBlock_getNdsComm(gcb);
|
||
uint32_t minTime = ClientGooseControlBlock_getMinTime(gcb);
|
||
uint32_t maxTime = ClientGooseControlBlock_getMaxTime(gcb);
|
||
bool fixedOffs = ClientGooseControlBlock_getFixedOffs(gcb);
|
||
PhyComAddress addr = ClientGooseControlBlock_getDstAddress(gcb);
|
||
ClientGooseControlBlock_setDstAddress(gcb, value);
|
||
|
||
// 写入服务端
|
||
IedConnection_setGoCBValues(conn, &err, gcb,
|
||
GOCB_ELEMENT_GO_ENA | GOCB_ELEMENT_DATSET, false);
|
||
|
||
ClientGooseControlBlock_destroy(gcb);
|
||
```
|
||
|
||
**GoCB 元素掩码**:
|
||
|
||
```c
|
||
#define GOCB_ELEMENT_GO_ENA 1
|
||
#define GOCB_ELEMENT_GO_ID 2
|
||
#define GOCB_ELEMENT_DATSET 4
|
||
#define GOCB_ELEMENT_CONF_REV 8
|
||
#define GOCB_ELEMENT_NDS_COMM 16
|
||
#define GOCB_ELEMENT_DST_ADDRESS 32
|
||
#define GOCB_ELEMENT_MIN_TIME 64
|
||
#define GOCB_ELEMENT_MAX_TIME 128
|
||
#define GOCB_ELEMENT_FIXED_OFFS 256
|
||
#define GOCB_ELEMENT_ALL 511
|
||
```
|
||
|
||
---
|
||
|
||
## 12. MMS低层传输层访问
|
||
|
||
当高层 API 不够用时,可通过 `IedConnection_getMmsConnection()` 获取底层 `MmsConnection`,直接使用 `mms_client_connection.h` 中的低层 API:
|
||
|
||
```c
|
||
// 获取底层连接
|
||
MmsConnection mms = IedConnection_getMmsConnection(conn);
|
||
|
||
// 低层API包含:
|
||
// - 直接域名(domain)和变量名(item)的读写
|
||
// - 命名变量列表操作
|
||
// - 文件服务(底层的FileOpen/Read/Close等)
|
||
// - 日志/Journal读取
|
||
```
|
||
|
||
一般开发不需要使用低层 API,只有在高层 API 不支持的特殊场景(如非标准服务器)才需要。
|
||
|
||
---
|
||
|
||
## 13. 错误码完整参考
|
||
|
||
```c
|
||
typedef enum {
|
||
IED_ERROR_OK = 0, // 成功
|
||
IED_ERROR_NOT_CONNECTED = 1, // 未连接
|
||
IED_ERROR_ALREADY_CONNECTED = 2, // 已连接(重复连接)
|
||
IED_ERROR_CONNECTION_LOST = 3, // 连接丢失
|
||
IED_ERROR_SERVICE_NOT_SUPPORTED = 4, // 服务不支持
|
||
IED_ERROR_CONNECTION_REJECTED = 5, // 连接被拒绝
|
||
IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED = 6, // 未完成调用达上限
|
||
IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT = 10,// 无效参数
|
||
IED_ERROR_ENABLE_REPORT_FAILED_DATASET_MISMATCH = 11, // 报告使能失败(数据集不匹配)
|
||
IED_ERROR_OBJECT_REFERENCE_INVALID = 12, // 对象引用无效
|
||
IED_ERROR_UNEXPECTED_VALUE_RECEIVED = 13, // 收到意外类型值
|
||
IED_ERROR_TIMEOUT = 20, // 超时
|
||
IED_ERROR_ACCESS_DENIED = 21, // 访问被拒绝
|
||
IED_ERROR_OBJECT_DOES_NOT_EXIST = 22, // 对象不存在
|
||
IED_ERROR_OBJECT_EXISTS = 23, // 对象已存在
|
||
IED_ERROR_OBJECT_ACCESS_UNSUPPORTED = 24, // 访问方式不支持
|
||
IED_ERROR_TYPE_INCONSISTENT = 25, // 类型不一致
|
||
IED_ERROR_TEMPORARILY_UNAVAILABLE = 26, // 临时不可用
|
||
IED_ERROR_OBJECT_UNDEFINED = 27, // 对象未定义
|
||
IED_ERROR_INVALID_ADDRESS = 28, // 无效地址
|
||
IED_ERROR_HARDWARE_FAULT = 29, // 硬件故障
|
||
IED_ERROR_TYPE_UNSUPPORTED = 30, // 类型不支持
|
||
IED_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT = 31, // 属性不一致
|
||
IED_ERROR_OBJECT_VALUE_INVALID = 32, // 对象值无效
|
||
IED_ERROR_OBJECT_INVALIDATED = 33, // 对象已失效
|
||
IED_ERROR_MALFORMED_MESSAGE = 34, // 畸形消息
|
||
IED_ERROR_SERVICE_NOT_IMPLEMENTED = 98, // 服务未实现
|
||
IED_ERROR_UNKNOWN = 99 // 未知错误
|
||
} IedClientError;
|
||
```
|
||
|
||
---
|
||
|
||
## 14. 运行模式:线程模式 vs 非线程模式
|
||
|
||
### 线程模式(默认)
|
||
|
||
```c
|
||
IedConnection conn = IedConnection_create();
|
||
// 库内部创建后台线程自动处理 MMS 消息
|
||
// 可以使用所有同步/异步 API
|
||
IedConnection_connect(conn, &err, host, port);
|
||
IedConnection_destroy(conn);
|
||
```
|
||
|
||
### 非线程模式
|
||
|
||
```c
|
||
IedConnection conn = IedConnection_createEx(NULL, false); // useThreads=false
|
||
|
||
// 必须使用异步 API
|
||
IedConnection_connectAsync(conn, &err, host, port);
|
||
|
||
// 周期性轮询
|
||
while (running) {
|
||
bool canPause = IedConnection_tick(conn);
|
||
// canPause=true: 当前无活跃请求,调用线程可以挂起
|
||
// canPause=false: 忙,应尽快再次调用 tick
|
||
|
||
if (canPause) {
|
||
usleep(10000); // 10ms
|
||
}
|
||
}
|
||
|
||
// 警告:不要使用同步 API!它会永远阻塞(因为没有后台线程驱动)
|
||
```
|
||
|
||
---
|
||
|
||
## 15. 与 RTU 项目对照
|
||
|
||
RTU 项目中,`src/protocol/libmms_m/` 封装了客户端 API,核心数据结构为 `stru_mms_m_obj`:
|
||
|
||
| RTU 封装层 | libiec61850 API |
|
||
|-----------|----------------|
|
||
| `stru_mms_m_obj.run.con` | `IedConnection` |
|
||
| `stru_mms_m_obj.run.con_state` | `IedConnection_getState()` |
|
||
| `stru_ln_rpt.rcb` | `ClientReportControlBlock` |
|
||
| `stru_ld_dataset.ln_datasets[]` | `IedConnection_readDataSetValues()` |
|
||
| `stru_mms_m_obj.run.out_status_cb` | `IedConnection_installStateChangedHandler()` |
|
||
| `mms_m_sg.cpp` (定值组读写) | `IedConnection_readObject`/`writeObject` with FC=SG/SE |
|
||
| `mms_m_file.cpp` (文件传输) | `IedConnection_getFile`/`setFile` |
|
||
|
||
**线程模型**:RTU 客户端使用独立线程(`pthread_task`),通过 `sem` 信号量进行同步。运行间隔为 `MMS_M_THREAD_RUN_TM = 300ms`。
|
||
|
||
**关键使用模式**(参考 RTU 代码):
|
||
|
||
```cpp
|
||
// RTU 中的典型初始化流程
|
||
stru_mms_m_obj* obj = new stru_mms_m_obj;
|
||
obj->run.con = IedConnection_create();
|
||
IedConnection_setConnectTimeout(obj->run.con, obj->connectionTimeout);
|
||
|
||
// 连接
|
||
IedClientError err;
|
||
IedConnection_connect(obj->run.con, &err, obj->run.ip.c_str(), obj->run.port);
|
||
obj->run.con_state = IedConnection_getState(obj->run.con);
|
||
|
||
// 安装报告回调
|
||
IedConnection_installReportHandler(obj->run.con,
|
||
rcbRef.c_str(), rptId.c_str(), reportCallback, obj);
|
||
```
|
||
|
||
---
|
||
|
||
> 本文档基于 `libiec61850-1.5.3/src/iec61850/inc/iec61850_client.h` (3091行) 和 `src/mms/inc/mms_value.h` (1062行) 完整归纳,覆盖所有公开 C API。
|