10 KiB
10 KiB
libmms_s 模块分析
日期: 2026-06-12
基于源码: src/protocol/libmms_s/(16个文件,约7500行)
1. 模块定位
libmms_s 是 RTU 的 IEC 61850 MMS 服务端库,位于协议层。它解析 ICD(IED Capability Description)XML 文件构建完整的数据模型,创建 IedServer 对外提供 MMS 服务,实现控制执行、定值组管理、文件服务等功能。
与上下层关系:
- 下层: 依赖 libiec61850 v1.5.x 的 IedServer/IedModel API
- 上层: 被
iec61850s(系统层封装)调用初始化,通过回调机制将控制/定值变更通知上层 - 数据方向: 接收远方客户端的 MMS 读写请求,通过回调将控制指令传递给
iec61850s→datacenter
2. ICD 解析流程(mms_s_icd.cpp)
ICD 文件是 SCL(Substation Configuration Language)格式的 XML。解析流程自顶向下:
XML 加载 (tinyxml2)
→ SCL 根元素
→ DataTypeTemplates 区域:
- 遍历 LNodeType(逻辑节点类型)
- DO → 引用 DOType
- 遍历 DOType(数据对象类型)
- SDO → 递归引用 DOType
- DA → 属性定义(bType, fc, dchg, qchg)
- fc/dchg/qchg 从父级 BDA 向下传播
- 遍历 DAType(数据属性类型)
- BDA → fc/dchg/qchg 从 BDA 向下传播到子 BDA
- 遍历 EnumType(枚举类型)
- EnumVal → 枚举值映射
→ IED 区域:
- AccessPoint → Server
- Server → LDevice
- LN0 (LLN0):
- DOI/SDI/DAI → 运行时值覆盖
- DataSet → FCDA 成员
- ReportControl → TrgOps, OptFields
- SettingControl → numOfSGs, actSG
- LN(逻辑节点)→ DOI/SDI/DAI
→ 校验:
- LN/LN0 的 lnType 在 DataTypeTemplates 中存在
- DataSet 的 FCDA 引用的 DO/SDO/DA 在模型中存在
核心数据结构:
stru_all_DO: 包含map_all_sdo(子 SDO)和map_all_da(子 DA),每个 DA 记录sAddr,bType,fcstru_icd: 顶层结构,包含ied对象和map_lnode_type,map_do_type,map_da_type,map_enum_typestru_icd::ied: 包含map_ldevice(逻辑设备),vec_all_DO(所有 DO),vec_control_do(控制 DO),vec_saddr(信号地址列表),map_saddr_point(sAddr→模型节点映射)
3. 动态模型创建(mms_s_model.cpp)
将 ICD 解析的 SCL 结构映射为 libiec61850 的 IedModel 对象树。
3.1 类型映射
| ICD bType 字符串 | libiec61850 DataAttributeType |
|---|---|
BOOLEAN |
IEC61850_BOOLEAN |
INT8 / Struct |
IEC61850_INT8 |
INT16 |
IEC61850_INT16 |
INT32 |
IEC61850_INT32 |
INT64 |
IEC61850_INT64 |
INT128 |
IEC61850_INT128 |
INT8U |
IEC61850_INT8U |
INT16U |
IEC61850_INT16U |
INT24U |
IEC61850_INT24U |
INT32U |
IEC61850_INT32U |
FLOAT32 |
IEC61850_FLOAT32 |
FLOAT64 |
IEC61850_FLOAT64 |
VisString64 等 |
IEC61850_VISIBLE_STRING64 |
Unicode255 等 |
IEC61850_UNICODE_STRING255 |
Timestamp |
IEC61850_TIMESTAMP |
Dbpos |
IEC61850_DBPOS |
Quality |
IEC61850_QUALITY |
Check |
IEC61850_CHECK |
Tcmd |
IEC61850_TCMD |
OptFlds |
IEC61850_OPTFLDS |
TrgOps |
IEC61850_TRGOPS |
EntryID |
IEC61850_ENTRYID |
EntryTime |
IEC61850_ENTRYTIME |
ObjRef |
IEC61850_OBJREF |
PhyComAddr |
IEC61850_PHYCOMADDR |
Octet64 |
IEC61850_OCTET_STRING_64 |
Currency |
IEC61850_CURRENCY |
AnalogueValue |
IEC61850_ANALOGUE_VALUE |
Unit |
IEC61850_UNIT |
Vector |
IEC61850_VECTOR |
ValWithTrans |
IEC61850_VAL_WITH_TRANS |
CUG |
IEC61850_CUG |
3.2 创建流程
model_init(icd):
IedModel_create(ied_name) // 创建根模型
→ model_ldevice_init(model, icd):
遍历每个 LDevice:
LogicalDevice_create(name, model)
→ model_init_lnodes(ld, icd):
遍历每个 LN/LN0:
LogicalNode_create(name, ld)
→ model_init_do(ln, icd):
遍历 DO/SDO/DA 递归创建 DataObject 和 DataAttribute
记录 sAddr → ModelNode 映射
→ 对 LN0:
model_init_datasets → 创建 DataSet + FCDA
model_init_rcb → 创建 URCB/BRCB + TrgOps/OptFields
model_init_sgcb → 创建 SGCB + SG/SE 镜像
→ model_sync_ied_default_values: ICD 默认值同步到模型节点
→ model_search_control_DataObjects: 搜索所有含 ctlModel 的 DO
→ 设置 model.initializer = mms_s_values_init
3.3 SG/SE 定值组镜像
每个 LN0 的 SGCB 下创建 SG(Setting Group)和 SE(Setting Group Edit),SE 是 SG 的镜像:
- SG 的 sAddr = 原始 sAddr(如
PROT/LLN0$SG$sg1$StrVal) - SE 的 sAddr =
SE_前缀版本 mms_s_param.cpp只操作 SE,协议栈负责 SG↔SE 同步
3.4 sAddr 映射
sAddr 是信号地址,IEC 61850 标准字段。解析时保存 sAddr → (fc, node) 映射到 map_saddr_point,供上层 iec61850s 按 sAddr 注册信号回调。
4. MMS 服务器启动(mms_s.cpp)
mms_s_init(icd_path, stack_size):
→ 解析 ICD → 创建 IedModel → model_init
→ IedServerConfig 配置:
- 设置 selectLlN0ForUnmatchedCommands = true
- 设置 maxMmsConnections = default
- 设置 reportBufferSize = 8
→ IedServer_create(&model) // 创建服务器
→ mms_s_control_init(server) // 安装控制回调
→ mms_s_param_init(server) // 安装定值组回调
→ mms_s_file_init(server) // 安装文件服务
→ mms_s_setting_init(server) // 安装 SP 定值回调
→ IedServer_start(server, port)
→ pthread_create → server_run_thread:
while(1) IedServer_processEvent(server, timeout)
5. 控制服务(mms_s_control.cpp)
5.1 控制模型
支持三种控制模型(由 ICD 中 DO 的 ctlModel 决定):
status-only(0): 仅状态,不参与控制direct-with-normal-security(1): 直接执行sbo-with-normal-security(2): 选择-执行(Select Before Operate)
5.2 回调链
远方客户端发送控制命令
→ IedServer 触发 control_handler(control, ctlVal, oper, interlockCheck):
- oper == OPERATE: 执行操作
stVal 从 ctlVal 中提取
若是 SBO: control.oper() → 更新 t, origin, ctlNum
若是 Direct: 直接更新 stVal + t
调用 p_control_callback(state) → 通知 iec61850s
- oper == CANCEL: 取消操作
→ 同时触发 check_handler(control, ctlVal, oper, interlockCheck):
- interlockCheck == true: 检查连锁条件
- 返回 false 阻止操作执行
5.3 控制注册
mms_s_control_register(controls, count, callback):
遍历所有控制 DO(由 model_search_control_DataObjects 在 ICD 解析阶段收集)
→ 通过 sAddr 在 map_saddr_point 中查找对应的 ModelNode
→ IedServer_setControlHandlerEx → 安装 control_handler + check_handler
→ 保存 callback 到全局列表
6. 定值组管理(mms_s_param.cpp)
6.1 SG/SE 编辑流程
远方客户端编辑定值:
→ SGCB.SelectEditSG(zone): 切换到编辑区
→ 加载当前激活 SG 的值到 SE
→ 设置 selectEditSG_callback → 通知上层定值区切换
→ SE 各 DA 写入新值:
→ writeAccessHandler: 校验 min/max/step
→ 通过才允许写入
→ SGCB.ConfirmEditSG(zone):
→ confirmEditSG_callback:
读取 SE 所有值 → 校验范围/步长
→ 调用 p_param_callback(zone, data) → 通知 iec61850s
返回 true 确认 / false 拒绝
6.2 值校验
每种数据类型对应独立的校验函数:
- BOOLEAN: 直接通过
- INT8/16/32:
minVal/maxVal/stepSize从 min/max/stepSize DA 读取 - INT8U/16U/32U: 同上(无符号)
- FLOAT32/64: 校验 + epsilon 容差(
fabs(val - min) > 0.000001) - VisString/UnicodeString: 长度校验
- Enum: 枚举值范围校验
6.3 定值区信号绑定
mms_s_bind_param_zone_signal(saddr, callback):
→ 在 LN0 的 SGCB 中查找 ActSG DA
→ IedServer_handleWriteAccess: 安装 writeAccessHandler
→ 当 ActSG 被写入时:
读取新 actSG 值
调用 callback(saddr, new_act_sg)
→ iec61850s 通过 datacenter 更新相关信号
7. 固定定值管理(mms_s_setting.cpp)
SP(Set Point)类 DA(fc=SP)的读写管理:
mms_s_setting_register(settings, count, callback):
→ 对每个 SP DA:
- IedServer_setWriteAccessPolicy(deny) // 全局拒绝 SP 写入
- IedServer_handleWriteAccess 安装 writeAccessHandler:
→ 校验 min/max/step
→ 允许后调用 p_setting_callback(saddr, data)
- sgcb 定值区变化时同步 SP 值
8. 文件服务(mms_s_file.cpp)
file_init(server):
→ IedServer_setFilestoreBasepath(path)
→ IedServer_setFileServiceHandler:
- rename: 一律拒绝
- read/write/delete: 允许
- IEDSERVER.BIN 文件保护: 拒绝任何操作
→ IedServer_installConnectionHandler(connectionHandler):
记录连接建立/断开日志
9. DA 值初始化(mms_s_value.cpp)
mms_s_values_init(param, model):
// 作为 model->initializer 在 IedServer_create 时被调用
→ 遍历 LD → LN → DO → SDO → DA 树
→ 按 bType 初始化 MmsValue:
BOOLEAN → MmsValue_newBoolean(false)
INT32 → MmsValue_newInteger(0)
INT32U → MmsValue_newUnsigned(0)
FLOAT32 → MmsValue_newFloat(0.0)
VisString* → MmsValue_newVisibleString("")
Unicode* → MmsValue_newUnicodeString("")
Timestamp → MmsValue_newUtcTime(0)
Dbpos → MmsValue_newDbpos()
Enum → 按 OrderedEnum 表现(有 ord → INTEGER, 无 → 第一枚举值)
→ MmsValue_setDeletable(value) // 标记为模型可管理生命周期
mms_s_value_update(saddr, value_str):
→ 按 bType 从字符串解析值 → 更新 MmsValue
→ IedServer_updateAttributeValue(server, node, value) // 触发 RCB 报告
→ 自动更新 t(时标)和 q(品质)字段
10. 关键日志宏
#define MMS_S_LOG_E(fmt, ...) LOG_E("[MMS_S][%s:%d]" fmt, __FILENAME__, __LINE__, ##__VA_ARGS__)
#define MMS_S_LOG_W(fmt, ...) LOG_W("[MMS_S][%s:%d]" fmt, __FILENAME__, __LINE__, ##__VA_ARGS__)
#define MMS_S_LOG_I(fmt, ...) LOG_I("[MMS_S][%s:%d]" fmt, __FILENAME__, __LINE__, ##__VA_ARGS__)
带颜色、时间戳、文件名和行号。