17 KiB
libmms_s 模块分析
日期:2026-06-10
1. 模块概览
libmms_s 是 IEC 61850 MMS 服务端的封装库,基于 libiec61850 v1.5.3 的服务端 API,提供 ICD 文件解析、动态数据模型创建、IedServer 生命周期管理,以及控制/定值/参数/文件等子系统的初始化。
目录结构
src/protocol/libmms_s/
├── inc/
│ ├── mms_s.h # 核心头文件:日志宏、全局访问接口、类型转换
│ ├── mms_s_icd.h # ICD 解析数据结构(完整 SCL 类型体系)
│ ├── mms_s_model.h # 动态模型创建接口
│ ├── mms_s_value.h # DA 初始值设定
│ ├── mms_s_control.h # 控制功能接口
│ ├── mms_s_param.h # 定值组参数接口
│ ├── mms_s_setting.h # 定值(SP)接口
│ └── mms_s_file.h # 文件传输服务接口
└── src/
├── mms_s.cpp # 核心:init/run/task/类型转换
├── mms_s_icd.cpp # ICD XML 解析器(tinyxml2)
├── mms_s_model.cpp # 动态 IedModel 创建
├── mms_s_control.cpp # 控制(SBO/直控)处理
├── mms_s_param.cpp # 定值组(SG/SE)管理
├── mms_s_setting.cpp # 定值(SP)管理
├── mms_s_value.cpp # 数据属性初始值设定
└── mms_s_file.cpp # MMS 文件传输服务
2. 核心架构
2.1 单例全局状态
LOCAL IedServer gp_iedServer = NULL; // 全局 IED 服务器(单例)
LOCAL stru_icd *gp_icd = NULL; // 全局 ICD 数据(单例)
LOCAL int g_running = 0; // 线程运行标志
LOCAL bool g_dbg_switch = false; // 调试输出开关
整个库采用单例模式,全局只有一个 IedServer 和一个 ICD 数据结构。
2.2 模块分层
┌──────────────────────────────────────────┐
│ myMms_s.h │ ← 公共 API + 类型定义
├──────────────────────────────────────────┤
│ mms_s.cpp (init / run / 类型转换) │ ← 库入口
├──────────────────────────────────────────┤
│ mms_s_icd.cpp │ mms_s_model.cpp │ ← ICD 解析 → 模型构建
├──────────────────────────────────────────┤
│ mms_s_value.cpp (DA 初始值) │ ← 模型回调
├──────────────────────────────────────────┤
│ mms_s_control.cpp │ mms_s_param.cpp │ ← 功能模块
│ mms_s_setting.cpp │ mms_s_file.cpp │
├──────────────────────────────────────────┤
│ libiec61850 (libiec61850.a) │ ← 底层协议栈
└──────────────────────────────────────────┘
3. 完整初始化流程
mms_s_init() 的执行顺序(mms_s.cpp:261):
1. icd_parse(icd_path) → 解析 ICD 文件 → stru_icd
2. model_init(*gp_icd) → 创建动态数据模型 → IedModel
3. IedServerConfig_create() → 创建服务器配置
4. IedServer_createWithConfig() → 创建 IedServer 实例
5. control_init() → 安装控制回调
6. param_init() → 安装定值组回调
7. file_init() → 安装文件服务
8. IedServer_setRCBEventHandler() → 注册 RCB 事件监听
9. IedServer_start(port) → 启动服务器(默认端口 102)
10. setting_init() → 初始化定值
11. IedServer_isRunning() → 检查运行状态
12. Thread_create(mms_s_run_task) → 启动后台线程
4. ICD 解析子系统
4.1 数据结构体系(mms_s_icd.h)
对应 IEC 61850-6 SCL 标准的完整类型体系:
数据类型模板层(DataTypeTemplates):
| 结构 | 说明 | 关键字段 |
|---|---|---|
stru_LNodeType |
逻辑节点类型 | lnClass, vec_do, map_do(key:name) |
stru_DO |
数据对象定义 | desc, type(→ DOType id) |
stru_DOType |
数据对象类型 | cdc, vec_da, map_da, vec_sdo, map_sdo |
stru_DA |
数据属性定义 | bType, type, dchg, qchg, fc, text |
stru_DAType |
数据属性类型 | vec_bda, map_bda(key:name) |
stru_BDA |
基本数据属性 | bType, type, fc, dchg, qchg |
stru_EnumType |
枚举类型 | vec_ord, map_enumVal(key:ord) |
实例化模型层(IED/Server):
| 结构 | 说明 |
|---|---|
stru_ied |
IED 顶层模型(含 model, name, 各逻辑设备) |
stru_LDevice |
逻辑设备(含 Ldevice 指针, sgcb, ln0, vec_ln) |
stru_LN0 |
LLN0(含 数据集, 报告控制, 定值控制) |
stru_LN |
普通逻辑节点 |
stru_DOI |
数据对象实例(含 SDI, DAI) |
stru_SDI |
子数据对象实例(可递归嵌套) |
stru_DAI |
数据属性实例(含 sAddr, val) |
stru_DataSet |
数据集(含 FCDA 列表) |
stru_FCDA |
功能约束数据属性(ldInst/lnClass/doName/daName/fc) |
stru_ReportControl |
报告控制块(含 TrgOps, OptFields, RptEnabled_max) |
stru_SettingControl |
定值控制块(actSG, numOfSGs) |
运行时辅助结构:
| 结构 | 说明 |
|---|---|
stru_all_DO |
DO 运行时树(含 libiec61850 DataObject 指针, map_all_sdo, map_all_da) |
stru_all_DA |
DA 运行时树(含 libiec61850 DataAttribute 指针, map_all_da 子节点) |
stru_contorl_DO |
控制 DO 定位信息(ldevice_inst/ln_class/ln_inst/do_name/p_all_do) |
stru_saddr_point |
sAddr 到模型节点的映射(node, da_t, da_q) |
stru_setting_info |
定值信息(DA 指针, 类型, 值) |
4.2 解析流程(mms_s_icd.cpp)
icd_parse(icd_file)
├── XMLDocument.LoadFile()
├── parse_ied() → 解析 IED 实例
│ ├── parse_LDevice() → 每个逻辑设备
│ │ ├── parse_LN0() → LLN0(数据集/报告控制/定值控制/DOI)
│ │ │ ├── parse_DataSet()
│ │ │ ├── parse_ReportControl()
│ │ │ │ ├── parse_ReportControl_TrgOps()
│ │ │ │ ├── parse_ReportControl_OptFields()
│ │ │ │ └── parse_ReportControl_RptEnable() → RptEnabled_max
│ │ │ ├── parse_DOI() → parse_SDI() / parse_DAI()
│ │ │ └── parse_SettingControl()
│ │ └── parse_LN() → 普通 LN + DOI
│ └── (沿 SCL > IED > AccessPoint > Server > LDevice 路径)
├── parse_dataTypeTemplates() → 解析模板
│ ├── parse_LNodeType()
│ ├── parse_DOType()
│ ├── parse_DAType()
│ ├── parse_EnumType()
│ └── parse_BDA_fc() ×2 → 传播 FC/dchg/qchg 到嵌套 BDA
└── check_ied_dataTypeTemplates() → 一致性校验
└── 递归验证每个实例节点都能在模板中找到定义
4.3 BDA 属性传播机制
关键设计:当 DA 的 type 指向 DAType(Struct 类型)时,parse_BDA_fc() 将父级 DA 的 fc/dchg/qchg 属性向下传播到 DAType 中所有 BDA,确保嵌套 Struct 的底层 BDA 也能继承正确的 FC 约束。
5. 动态模型创建子系统
5.1 model_init() 主流程(mms_s_model.cpp:1319)
1. IedModel_create(name)
2. model_ldevice_init()
├── LogicalDevice_create()
├── model_ln0_init()
│ ├── LogicalNode_create("LLN0")
│ ├── model_dataobject_init() → 创建所有 DO/SDO/DA
│ ├── model_DataSet_init() → 创建数据集+条目
│ ├── model_ReportControlBlock_init() → 创建 RCB
│ └── SettingGroupControlBlock_create()
└── model_LNodes_init() → 创建普通 LN
3. model_sync_ied_default_values() → 同步 ICD DAI 中的 sAddr/val
4. model_search_control_DataObjects() → 搜索 ctlModel→vec_control_do
5. icd.ied.model->initializer = mms_s_values_init → 注册回调
5.2 类型映射表
bType → DataAttributeType(mms_s_model.cpp:43):
BOOLEAN → IEC61850_BOOLEAN, INT32 → IEC61850_INT32, FLOAT32 → IEC61850_FLOAT32, Struct → IEC61850_CONSTRUCTED, Quality → IEC61850_QUALITY, Timestamp → IEC61850_TIMESTAMP, Dbpos → IEC61850_GENERIC_BITSTRING 等,共约 25 种映射。
FC → FunctionalConstraint(mms_s_model.cpp:86):
ST/MX/SP/SV/CF/DC/SG/SE/SR/OR/BL/EX/CO/US/MS/RP/BR/LG/GO → 对应 IEC61850 枚举。
5.3 SG/SE 双数据属性设计
当 DA 的 FC=SG 时,除了创建 FC=SG 的 DA 外,还会额外创建一个 FC=SE 的同名 DA(key 加 _SE 后缀)。反之 FC=SE 同理。sAddr 映射时也会对应添加 _SG / _SE 后缀区分。
这是一个独创设计——在同一个 DO 下同时暴露 SG(当前激活值)和 SE(编辑缓冲区值),使得外部系统可以同时访问定值的"当前生效值"和"正在编辑中的值"。
5.4 sAddr 映射机制
ICD 文件中 DAI 元素的 sAddr 属性定义了该数据点与外部数据源的绑定关系。model_sync_ied_default_values() 将 sAddr 写入模型节点的 sAddr 字段,同时建立:
icd.ied.vec_saddr— sAddr 列表icd.ied.map_saddr_point— sAddr → ModelNode 映射
对于 FC=SG 的点,sAddr 后缀 _SG;FC=SE 的点后缀 _SE。
6. 控制子系统(mms_s_control.cpp)
6.1 双回调模式
| 回调 | 阶段 | 职能 |
|---|---|---|
check_handler() |
Select / Interlock | 权限校验,匹配合法 DO 则返回 CONTROL_ACCEPTED |
control_handler() |
Operate | 执行控制命令,更新 t(时间戳)+ stVal,触发外部回调 |
6.2 控制执行流程
客户端 → Select Request → check_handler() → CONTROL_ACCEPTED
客户端 → Operate Request → check_handler() (interlock check) → control_handler()
├── IedServer_updateUTCTimeAttributeValue(t)
├── 匹配 sAddr → cb_control() → 通知应用层
└── 根据 stVal 类型:
├── BOOLEAN → IedServer_updateAttributeValue()
└── Dbpos → IedServer_updateDbposValue()
6.3 控制 DO 发现
model_search_control_DataObjects() 遍历模型树,找到所有包含 ctlModel DA 的 DO,存入 icd.ied.vec_control_do。control_init() 遍历该列表,为每个安装 control_handler 和 check_handler。
7. 定值组子系统(mms_s_param.cpp)
7.1 SG vs SE
| FC | 含义 | mmsValue 加载时机 |
|---|---|---|
| SG | Setting Group — 当前激活定值区的实际值 | 激活定值组切换时加载 |
| SE | Setting Editable — 编辑缓冲区值 | 编辑定值组切换时加载 |
7.2 回调链
激活定值组切换 → param_active_sg_changed_handler() → param_load_active_sg_values()
编辑定值组切换 → param_edit_sg_changed_handler() → param_load_edit_sg_values()
编辑确认 → edit_sg_confirmation_handler() → 回读 SE 值 → cb_param()
7.3 值的加载与校验
- 加载:根据 sAddr+"_SG"/"_SE" 在
map_saddr_point中查找节点,按 MMS 类型分发更新函数 - 回读:
edit_sg_confirmation_handler()读取 SE DA 当前值,做 min/max/step 校验,通过后触发外部回调g_param_cb - 类型分发表:
g_param_update_funcs[](写)和g_param_get_funcs[](读),覆盖 BOOLEAN/INT/UINT/FLOAT/STRING
8. 定值子系统(mms_s_setting.cpp)
8.1 与参数的差异
定值(FC=SP)不参与定值组切换,是单一设置值。
8.2 写保护机制
- 全局访问策略:
IedServer_setWriteAccessPolicy(IEC61850_FC_SP, ACCESS_POLICY_DENY) - 精确放行:通过
IedServer_handleWriteAccess()为已注册 sAddr 安装writeAccessHandler writeAccessHandler做值校验(范围+步长),通过后触发外部回调cb_setting
8.3 校验流程
客户端写 SP 值 → writeAccessHandler()
├── 匹配 sAddr
├── 类型分发 (setting_get_BOOLEAN/INT/UINT/FLOAT/STRING)
│ └── setting_min_max_step_get() → 读取同级 minVal/maxVal/stepSize
│ └── 范围校验 + 步长校验
├── cb_setting() → 通知应用层
└── return DATA_ACCESS_ERROR_SUCCESS → libiec61850 更新 mmsValue
9. 数据属性值初始化(mms_s_value.cpp)
mms_s_values_init() 作为 IedModel.initializer 回调,在 IedServer 创建过程中由 libiec61850 自动调用。
初始化类型覆盖:BOOLEAN, INT8U/32, FLOAT32, VisString*, Unicode*, Timestamp, Dbpos, Enum(特殊:按文本值或序号查找枚举值)。
Dbpos 特殊处理:mms_s_da_value_init_Dbpos() 按 val 值映射到 DBPOS_INTERMEDIATE_STATE(0)/OFF(1)/ON(2)/BAD_STATE(>=3)。
9.1 值更新路径
mms_s_value_update() 是预留的值更新函数,通过 mms_s_value_update_register() 注册给外部。当数据中心信号变化时,上层调用此函数更新模型中的 DA 值,同时自动更新父 DO 的 t(时间戳)和 q(品质)。
10. 文件传输服务(mms_s_file.cpp)
提供 MMS 文件传输基础能力:
- 设置文件存储根目录
IedServer_setFilestoreBasepath() - 访问控制:禁止重命名、禁止删除
IEDSERVER.BIN - 连接事件日志
11. 后台运行线程
LOCAL void *mms_s_run_task(void *parameter)
{
while(g_running) {
IedServer_lockDataModel(gp_iedServer);
IedServer_unlockDataModel(gp_iedServer);
Thread_sleep(100); // 100ms 周期
}
IedServer_stop(gp_iedServer);
IedServer_destroy(gp_iedServer);
IedModel_destroy(iedModel);
}
线程负责保护 IedServer 生命周期,通过 lock/unlock 持有模型锁保持服务活跃。收到 SIGINT 后 g_running=0,线程退出并销毁资源。
12. RCB 事件监听
rcbEventHandler() 监听客户端的报告控制块操作(mms_s.cpp:48):
| 事件 | 含义 |
|---|---|
RCB_EVENT_ENABLE |
客户端使能报告 |
RCB_EVENT_DISABLE |
客户端关闭报告 |
RCB_EVENT_RESERVED |
客户端预订 RCB |
RCB_EVENT_UNRESERVED |
客户端释放预订 |
RCB_EVENT_GI |
总召触发 |
RCB_EVENT_SET_PARAMETER |
客户端设置 RCB 参数(如 TrgOps 等) |
RCB_EVENT_GET_PARAMETER |
客户端获取 RCB 参数 |
特殊处理:当客户端设置 TrgOps 参数时,服务器强制追加 dchg(rcb->trgOps |= 0x01),确保数据变化一定能触发报告上送。
13. 对外 API 汇总
| API | 文件 | 说明 |
|---|---|---|
mms_s_init(icd_path, port) |
mms_s.cpp | 完整初始化 MMS 服务器 |
mms_s_dbg_switch(on) |
mms_s.cpp | 调试开关 |
mms_s_get_icd_ptr() |
mms_s.cpp | 获取 ICD 数据 |
mms_s_get_ied_server_ptr() |
mms_s.cpp | 获取 IedServer |
mms_s_control_register(...) |
mms_s_control.cpp | 注册控制点 |
mms_s_setting_register(...) |
mms_s_setting.cpp | 注册定值 |
mms_s_param_register(...) |
mms_s_param.cpp | 注册参数(定值组) |
mms_s_file_path_set(path) |
mms_s_file.cpp | 设置文件根路径 |
mms_s_value_update_register(cb) |
mms_s_value.cpp | 注册值更新回调 |
mms_s_get_string_by_mms_type(...) |
mms_s.cpp | MMS 类型→字符串 |
mms_s_get_string_by_type(...) |
mms_s.cpp | 自定义类型→字符串 |
14. 已知问题
14.1 check_handler 未调用 cb_interlock
mms_s_control.h 中声明了 mms_s_interlock_cb 和 mms_s_set_interlock_cb(),但 check_handler() 中仅检查 ICD 中是否存在对应 DO,cb_interlock 未被实际调用。外部注册的联锁检查回调不会生效。
14.2 param_min_max_step_get 实现不一致
mms_s_param.cpp 的 param_min_max_step_get() 通过遍历 ModelNode 父子关系查找 minVal/maxVal/stepSize(向上找到 DataObject 再遍历 firstChild),而 mms_s_setting.cpp 的 setting_min_max_step_get() 是通过 parent->firstChild 直接遍历。两者逻辑不同,前者更复杂(向上一级再到 DO),可能是修正后的版本,但后者仍保留了旧逻辑。
14.3 mms_s_value_update 未使用 g_iec61850s_value_init_cb_map
mms_s_value_update() 使用 g_iec61850s_value_update_cb_map(按 DataAttributeType 索引),但表中大部分类型回调为 NULL,仅 BOOLEAN/INT32/INT32U/FLOAT32/Enum/VisString32/Unicode255/Timestamp 有实际实现。其他类型(如 INT8/INT16/INT64/FLOAT64 等)更新时会被跳过。