47 KiB
libiec61850-1.5.3 MMS 客户端 API 开发手册
本文档基于 libiec61850 v1.5.3 源码,覆盖 MMS 客户端全部公开 C API,达到开发者脱离源码即可进行项目开发的标准。
目录
- 架构概述
- 核心数据类型
- 连接管理
- 数据读写
- 报告服务(Report)
- 控制服务(Control)
- 数据集服务(DataSet)
- 文件服务(File)
- 日志服务(Log)
- 模型发现服务
- SV/GOOSE 控制块处理
- MSO 传输层访问
- 错误码完整参考
- 运行模式:线程模式 vs 非线程模式
- 与 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 主要不透明句柄
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 连接状态
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 控制模型
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 质量
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
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 回调函数类型速查
// 通用服务回调(写操作完成等)
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:
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;
构造(仅在需要主动创建值时使用,读取操作由库自动创建):
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);
取值:
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);
赋值:
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);
生命周期:
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 完整连接生命周期
// 步骤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 连接状态查询
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 获取底层连接
MmsConnection IedConnection_getMmsConnection(IedConnection self);
返回底层 MmsConnection 句柄,可用于直接调用低层 MMS API。
4. 数据读写
4.1 通用读写(通过 FCDA 引用 + FC)
// 同步读:返回 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 便捷类型读写(推荐用于简单类型)
读:
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 指针则复用。
写:
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 时指定要写入哪些字段)
#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)
#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)— 决定报告包含哪些信息
#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 完整报告配置与接收流程
// 步骤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 包含原因枚举
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 完整列表
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 完整列表
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 控制对象生命周期
// 创建(同步版本:会请求服务端获取 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 同步控制操作
// === 直控-普通安全 (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 异步控制操作
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 命令终止回调(增强安全模式)
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
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):
#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 读取数据集
// 同步
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 创建/删除数据集
// 创建
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 获取数据集目录
// 同步:返回 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 写入数据集
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
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 获取文件目录
// 获取根目录
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)
// 同步
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)/ 删除文件
// 上传
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)
// 按时间范围查询日志
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 获取完整设备模型
IedConnection_getDeviceModelFromServer(conn, &err);
// 此调用会缓存模型,后续的查询基于缓存,不再产生网络请求
10.2 目录查询
// 获取逻辑设备列表
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 类别枚举
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 异步模型发现
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 控制块
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 控制块
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 元素掩码:
#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:
// 获取底层连接
MmsConnection mms = IedConnection_getMmsConnection(conn);
// 低层API包含:
// - 直接域名(domain)和变量名(item)的读写
// - 命名变量列表操作
// - 文件服务(底层的FileOpen/Read/Close等)
// - 日志/Journal读取
一般开发不需要使用低层 API,只有在高层 API 不支持的特殊场景(如非标准服务器)才需要。
13. 错误码完整参考
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 非线程模式
线程模式(默认)
IedConnection conn = IedConnection_create();
// 库内部创建后台线程自动处理 MMS 消息
// 可以使用所有同步/异步 API
IedConnection_connect(conn, &err, host, port);
IedConnection_destroy(conn);
非线程模式
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 代码):
// 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。