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

225 lines
8.7 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_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_initURCB + 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 发现流程重新执行