RTU/mimo/工程/libmms_s模块分析.md

284 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# libmms_s 模块分析
**日期**: 2026-06-12
**基于源码**: `src/protocol/libmms_s/`16个文件约7500行
---
## 1. 模块定位
`libmms_s` 是 RTU 的 IEC 61850 MMS 服务端库,位于协议层。它解析 ICDIED Capability DescriptionXML 文件构建完整的数据模型,创建 `IedServer` 对外提供 MMS 服务,实现控制执行、定值组管理、文件服务等功能。
**与上下层关系**:
- **下层**: 依赖 libiec61850 v1.5.x 的 IedServer/IedModel API
- **上层**: 被 `iec61850s`(系统层封装)调用初始化,通过回调机制将控制/定值变更通知上层
- **数据方向**: 接收远方客户端的 MMS 读写请求,通过回调将控制指令传递给 `iec61850s``datacenter`
## 2. ICD 解析流程(`mms_s_icd.cpp`
ICD 文件是 SCLSubstation 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`, `fc`
- `stru_icd`: 顶层结构,包含 `ied` 对象和 `map_lnode_type`, `map_do_type`, `map_da_type`, `map_enum_type`
- `stru_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 下创建 SGSetting Group和 SESetting Group EditSE 是 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`
SPSet Point类 DAfc=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. 关键日志宏
```cpp
#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__)
```
带颜色、时间戳、文件名和行号。