8.7 KiB
8.7 KiB
libmms_m 模块分析
日期: 2026-06-12
基于源码: src/protocol/libmms_m/(8个文件,约3000行)
1. 模块定位
libmms_m 是 RTU 的 IEC 61850 MMS 客户端库,位于协议层。它通过 libiec61850 库连接到远方 IED 装置,以事件驱动架构实现遥测/遥信读取(All-Call、GI)、遥控执行(SBO/Direct)、AO/参数读写、报告订阅等全部 MMS 客户端功能。
与上下层关系:
- 下层: 依赖 libiec61850 v1.5.x 提供的 IedConnection API
- 上层: 被
iec61850m(系统层封装)通过对外 API 调用 - 并发: 每个 IED 连接一个独立线程
mms_m_run_thread,通过事件队列 + 信号量与其他线程通信
2. 核心数据结构
2.1 主对象 stru_mms_m_obj
每个 IED 连接创建一个 stru_mms_m_obj 实例,全局保存在 g_mms_m_obj_map(map<int, obj*>)中,以 obj_fd 为句柄。
| 字段 | 用途 |
|---|---|
cfg_path |
配置文件路径,用于错误日志和重复检测 |
ied_name |
IED 名称,从配置中提取 |
debug_print_flag |
调试打印开关(MMS_M_DEBUG_PRINT_ON = 1) |
connectionTimeout |
连接超时(毫秒) |
p_cfg |
指向 stru_cfg 的点表配置(ST/MX/CO/AO/Param) |
ldevs |
LDevice 树(LD→LN→DO→point_item 指针),用于 dataset member 匹配 |
ld_datasets |
运行时发现的 LD→Dataset→Member→RCB 完整结构 |
rcb_numbers |
可配置的 RCB 编号列表(如 {"01","02"}),空则默认 "01" |
zone_saddr |
绑定定值区信号的 saddr,用于检测定值区切换 |
current_zone |
当前定值区号 |
obj_fd |
对象句柄,对外 API 用此标识 |
run |
运行时状态(连接、定时器、事件队列、回调列表) |
2.2 运行时结构 stru_mms_m_run
| 字段 | 用途 |
|---|---|
con |
IedConnection 句柄 |
con_state / old_con_state |
连接状态(前后帧对比检测上线/离线) |
ip / port |
远方 IED 的 IP/端口 |
running_init |
ICD 初始化是否完成标志 |
event_queue |
环形事件队列(容量 EVENT_QUEUE_SIZE) |
sem |
信号量,保护事件队列的读写 |
timer[4] |
四个定时器 T0-T3 |
pthread_task |
工作线程句柄 |
out_cb_lists |
数据回调函数列表,向上层推送读取到的值 |
out_status_cb |
连接状态回调(上线/离线通知) |
2.3 事件类型 _MMS_M_EVENT
_MMS_M_EVENT_ALL_CALL // 全数据读取(ST+MX)
_MMS_M_EVENT_GI_CALL // 总召
_MMS_M_EVENT_CO_SELECT // 遥控选择
_MMS_M_EVENT_CO_DIRECT // 遥控执行
_MMS_M_EVENT_CO_CANCEL // 遥控取消
_MMS_M_EVENT_AO_READ // 读取AO值
_MMS_M_EVENT_AO_WRITE // 写入AO值
_MMS_M_EVENT_PARAM_READ // 读取参数值
_MMS_M_EVENT_PARAM_WRITE // 写入参数值
2.4 定时器配置
| 定时器 | 间隔 | 触发动作 |
|---|---|---|
| T0 | 120s | 发送 All-Call → 全数据刷新 |
| T1 | 60s | 发送 GI → 总召 |
| T2 | 30s | 读取所有 AO + Param |
| T3 | 20s | 预留(未使用) |
定时器实现为倒计数(cnt--),在每次 mms_m_run 中检查。到零时触发回调并重置计数。
3. 核心流程
3.1 初始化流程
上层调用 mms_m_out_init(p_cfg, debug_flag, timeout)
→ 检查配置文件是否重复
→ new stru_mms_m_obj,初始化基础字段
→ mms_m_ied_init(obj):
- 提取 ied_name, ip, port
- 遍历 ST/MX/CO/AO/Param 点表
- 调用 mms_m_add_point_to_ldevs 构建 LD→LN→DO→point 树形索引
- 用于后续 dataset member 匹配
→ sem_init 信号量
→ pthread_create → mms_m_run_thread 启动工作线程
→ 返回 obj_fd
3.2 连接管理(mms_m_do_comm)
mms_m_run_thread 主循环(每 MMS_M_THREAD_RUN_TM 毫秒):
→ mms_m_do_comm:
比较 con_state 与 old_con_state:
- 非连接状态: IedConnection_connectAsync 异步重连
- 新上线 (old != CONNECTED, new == CONNECTED):
mms_m_control_init() 重建 ControlObjectClient
回调 out_status_cb(ON_LINE)
- 新离线 (old == CONNECTED, new != CONNECTED):
running_init = false // RCB 需重新订阅
回调 out_status_cb(OFF_LINE)
old_con_state = con_state
→ if CONNECTED: mms_m_run(obj)
3.3 运行流程(mms_m_run)
mms_m_run(obj):
→ mms_m_run_init(obj): // 仅首次执行
mms_m_icd_init(obj):
- 遍历 LD → LN(通过 IedConnection_getLogicalDeviceList/Directory)
- 对每个 LN: mms_m_icd_dataset_init(获取所有 DataSet + Members)
- 对每个 LN: mms_m_icd_report_init(URCB + BRCB,按 rcb_numbers 过滤)
mms_m_ld_dataset_match_point_init(obj):
- 遍历所有 dataset member
- 解析 ref 得到 LD/LN/DO 名称
- 在 ldevs 树中查找匹配的 DO,建立 member.p_do_vec 指针关系
- 报告回调时通过此关系快速定位到具体信号点
mms_m_rcb_init(obj):
- 获取 RCB 值(Resv→TrgOps→RptEna→GI)
- 安装 mms_m_report_callback
- 设置 TrgOps = dchg | qchg | GI
→ mms_m_do_send(obj): 处理事件队列中的操作
→ mms_m_timer_running(obj): 检查四个定时器
3.4 All-Call 流程
T0 定时器到期 → mms_m_do_call_all:
→ 推送 _MMS_M_EVENT_ALL_CALL 事件
→ 重置 T0
mms_m_do_send → mms_m_send_call_all:
遍历 ST 和 MX 点表:
IedConnection_readObject(p_con, point.reference, fc)
→ mms_m_get_mmsValue: MMS 类型转换 (BOOLEAN→uint8, INTEGER→int32, UNSIGNED→uint32, FLOAT→float, BIT_STRING→quality)
→ 构造 out_val { name, desc, reference, time, value, quality, reason }
→ mms_m_put_value: 遍历 out_cb_lists 执行每个回调
3.5 遥控流程(SBO/Direct)
上层调用 mms_m_out_do_set_yk(event):
→ 按 app_fd 或 ied_name 定位 obj
→ mms_m_push_event
mms_m_do_send → mms_m_send_co:
switch ctrl_type:
_MMS_M_EVENT_CO_SELECT: ControlObjectClient_select
_MMS_M_EVENT_CO_DIRECT: ControlObjectClient_operate
_MMS_M_EVENT_CO_CANCEL: ControlObjectClient_cancel
→ mms_m_send_set_callback: 设置操作完成回调
3.6 AO/Param 读写
AO 读取:mms_m_send_read_ao
→ 按 saddr 或全表读取
→ IedConnection_readObject → 类型转换 → 回调推送
Param 写入:mms_m_send_param_write
→ 检测定值区是否切换(对比 current_zone)
→ 定值区变化:
读取 SG 信息 → select EditSG → 写入 SE 值 → confirm CnfEdit
→ 定值区不变:
直接写入 SE 值 → confirm
→ 操作完成回调
3.7 报告回调(mms_m_report_callback)
IedConnection 收到 report → mms_m_report_callback:
→ 遍历 dataset members
→ 检查 ReasonForInclusion(非 NOT_INCLUDED 才处理)
→ mms_m_get_MmsValue 解析数据值和时标
→ 通过 member.p_do_vec 找到所有绑定的信号点
→ 构造 out_val → 遍历 out_cb_lists 推送
4. 对外 API
| API | 说明 |
|---|---|
mms_m_out_init(p_cfg, dbg, timeout) |
创建 IED 连接,返回 obj_fd |
mms_m_out_get_connect_status(fd, cb) |
注册连接状态回调 |
mms_m_out_debug_print_swicth(fd, flag) |
开关调试打印 |
mms_m_out_do_set_yk(event) |
下达遥控命令(Select/Direct/Cancel) |
mms_m_out_read_ao_or_params(fd, type, saddr) |
读取 AO/Param 值 |
mms_m_out_get_value(fd, cb) |
注册数据回调 |
mms_m_out_bind_param_zone_signal(fd, saddr) |
绑定定值区信号 |
mms_m_out_set_rcb_numbers(fd, numbers) |
设置 RCB 订阅编号 |
mms_m_create_data_ptr(type) |
创建类型对应的数据指针 |
mms_m_set_data_value(src, dst, type) |
类型化数据赋值 |
mms_m_get_data_value_str(data, type, str) |
类型化数据转字符串 |
mms_m_set_data_by_str(data, type, str) |
字符串转类型化数据 |
mms_m_out_reason_str(reason) |
原因码转字符串 |
mms_m_dbg_get(obj) |
获取调试标志 |
mms_m_get_obj(fd) |
按句柄获取对象指针 |
5. 线程安全
- 事件队列
push/pop受sem_wait/sem_post保护 out_cb_lists在初始化时注册,运行时只读con_state/old_con_state仅在主线程(mms_m_run_thread)修改- libiec61850 的 report callback 在其他线程触发,通过
mms_m_push_event转入主线程处理
6. 已知问题
- 定时器倒计数精度: 定时器基于循环次数×休眠时间,非高精度。取决于
MMS_M_THREAD_RUN_TM大小 - 事件队列溢出: 容量
EVENT_QUEUE_SIZE,环形覆盖不报错 - RCB 编号过滤在 report_init 中执行: 一旦初始化完成,变更 rcb_numbers 需重建连接
- 离线时 running_init 重置: 重连后整个 ICD 发现流程重新执行