225 lines
8.7 KiB
Markdown
225 lines
8.7 KiB
Markdown
# 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. 已知问题
|
||
|
||
1. **定时器倒计数精度**: 定时器基于循环次数×休眠时间,非高精度。取决于 `MMS_M_THREAD_RUN_TM` 大小
|
||
2. **事件队列溢出**: 容量 `EVENT_QUEUE_SIZE`,环形覆盖不报错
|
||
3. **RCB 编号过滤在 report_init 中执行**: 一旦初始化完成,变更 rcb_numbers 需重建连接
|
||
4. **离线时 running_init 重置**: 重连后整个 ICD 发现流程重新执行
|