28 KiB
libmms_m 模块工程文档
概述
libmms_m 是 RTU 项目中 IEC 61850 MMS 客户端的封装库,位于 src/protocol/libmms_m/,负责将 libiec61850 的 C 客户端 API 封装成 RTU 内部的事件驱动架构。
核心职责
- 连接管理:通过异步方式与远端 IED 建立/维护 MMS 连接
- 报告接收:通过 Report 回调接收 IED 主动上送的变化数据
- 数据操作:总召、总召遥信遥测、AO 读写、参数(定值)读写
- 控制指令:遥控(CO)的 Select/Operate/Cancel 操作
- 扩展服务:文件传输、服务器信息查询、定值组读取
文件结构
src/protocol/libmms_m/
├── inc/
│ ├── mms_m.h ← 核心头文件(日志宏、数据结构、API 声明)
│ ├── mms_m_ext.h ← 扩展功能接口声明(文件/服务器/定值组)
│ └── mms_m_errstr.h ← 错误码转字符串
└── src/
├── mms_m.cpp ← 核心实现(连接/报告/总召/遥控/参数)
├── mms_m_errstr.cpp ← 错误码/控制模型/AddCause 转字符串
├── mms_m_file.cpp ← 文件服务实现
├── mms_m_server.cpp ← 服务器身份/状态查询
└── mms_m_sg.cpp ← 定值组信息读取
基础类型定义位于 release/inc/myMms_m.h。
1. 核心数据结构
1.1 全局对象管理
static std::map<int, stru_mms_m_obj *> g_mms_m_obj_map;
所有客户端实例通过全局 map 管理,key 为 app_fd(客户端句柄,从 1 开始自增),value 为对象指针。
1.2 主对象结构 stru_mms_m_obj
typedef struct {
int obj_fd; // 客户端句柄(自增)
uint32_t connectionTimeout; // 连接超时 [毫秒]
std::string cfg_path; // 配置文件路径
stru_cfg *p_cfg; // XML 配置解析结果
int debug_print_flag; // 调试打印开关(0/1)
std::string ied_name; // IED 名称
MmsValue *param_zone; // 定值区选择值(MmsValue, 可复用)
MmsValue *set_confirm; // 定值确认值(boolean, 可复用)
MMS_STR zone_saddr; // 关联定值区的信号 saddr(如 "ao.0")
int current_zone; // 当前定值区号
stru_mms_m_run run; // 运行时数据
std::vector<stru_ldev> ldevs; // 逻辑设备树(LD→LN→DO→Point)
std::vector<stru_ld_dataset> ld_datasets; // 数据集和报告配置
} stru_mms_m_obj;
1.3 运行时结构 stru_mms_m_run
typedef struct {
std::string ip; // 服务器 IP
int port; // 服务器端口
bool running_init; // 是否已完成初始化
sem_t sem; // 回调同步信号量
pthread_t pthread_task; // 工作线程
IedConnection con; // libiec61850 连接句柄
IedConnectionState con_state; // 当前连接状态
IedConnectionState old_con_state; // 上一次连接状态
stru_mms_m_timer timer[_MMS_M_TIMER_END]; // 4 个定时器
stru_mms_m_event event; // 当前处理的事件
stru_event_queue event_queue; // 事件队列(容量 64)
// 事件类型(按枚举):
// [select] [operate] [cancel] ─→ 遥控
// [mms_m_send_co_select|direct|cancel]
// 通过 IedConnection_setRCBValues 发送总召
mms_m_out_status_cb out_status_cb; // 连接状态回调
std::vector<mms_m_out_value_cb> out_cb_lists; // 数据输出回调列表
} stru_mms_m_run;
1.4 数据模型层次结构
stru_mms_m_obj
├── ldevs[] ← 逻辑设备列表
│ └── stru_ldev
│ ├── ld_name ← LD 名称(如 "TEMPLATE")
│ └── lnodes[] ← 逻辑节点列表
│ └── stru_lnode
│ ├── ln_name ← LN 名称(如 "GGIO1")
│ └── dobjs[] ← 数据对象列表
│ └── stru_dobj
│ ├── do_name ← DO 名称(如 "Ind1")
│ └── p_do_vec[] ← 指向 stru_point_item 的指针集合
│
└── ld_datasets[] ← 数据集/报告配置
└── stru_ld_dataset
├── ref ← LD/LN 引用
├── ld_name ← LD 名称
├── ln_name ← LN 名称
├── ln_datasets[] ← 数据集列表
│ └── stru_ln_dataset
│ ├── dataset_name ← 数据集全名
│ ├── dataset_ref ← 数据集引用 ($格式)
│ └── members[] ← 成员列表
│ └── stru_member
│ ├── ref ← FCDA 引用
│ ├── reason ← 包含原因
│ └── p_do_vec ← 关联的点集合
└── ln_rpts[] ← 报告列表
└── stru_ln_rpt
├── rpt_ref ← RCB 引用
├── rpt_ref_with_no ← RCB 引用+编号
├── ds_ref ← 关联数据集引用
├── type ← URCB 或 BRCB
├── rcb ← ClientReportControlBlock 句柄
├── p_app ← 指向所属 mms_m_obj
└── p_dataset ← 指向关联数据集
1.5 事件与定时器
// 事件类型
enum {
_MMS_M_EVENT_ALL_CALL, // 总召遥信遥测
_MMS_M_EVENT_GI_CALL, // 总召(GI)
_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, // 写入参数
_MMS_M_EVENT_END
};
// 事件结构
typedef struct {
int app_fd; // 客户端句柄
MMS_STR ied; // IED 名称
MMS_STR saddr; // 短地址
uint8_t value_type; // MMS 数据类型
char val[MMS_M_DATA_STRING_LEN]; // 值(字符串形式)
uint8_t ctrl_type; // 事件类型
int set_zone; // 定值区号
void (*p_func)(void *arg, int ret); // 完成回调
} stru_mms_m_event;
// 事件队列(环形缓冲区,容量 64)
typedef struct {
uint8_t w_ptr; // 写指针
uint8_t r_ptr; // 读指针
uint8_t size; // 容量
uint8_t num; // 当前数量
stru_mms_m_event event[EVENT_QUEUE_SIZE];
} stru_event_queue;
// 4 个定时器
enum { _MMS_M_TIMER_T0, _MMS_M_TIMER_T1, _MMS_M_TIMER_T2, _MMS_M_TIMER_T3, _MMS_M_TIMER_END };
// 定时器时间定义
#define MMS_M_THREAD_RUN_TM (100 * 3) // 线程循环间隔:300ms
#define MMS_M_TIMER_T0 (120 * 3) // T0: 120s(总召所有数据)
#define MMS_M_TIMER_T1 (60 * 3) // T1: 60s(GI 触发)
#define MMS_M_TIMER_T2 (30 * 3) // T2: 30s(AO + 参数读取)
#define MMS_M_TIMER_T3 (20 * 3) // T3: 20s(预留,未使用)
2. 线程模型与主流程
2.1 生命周期
mms_m_out_init()
├── 分配 stru_mms_m_obj
├── mms_m_ied_init() ← 构建 ldevs 树
├── sem_init() ← 初始化信号量
├── pthread_create(mms_m_run_thread) ← 创建工作线程
└── 加入 g_mms_m_obj_map[id]
mms_m_run_thread() ← 工作线程入口
├── mms_m_timer_init() ← 初始化4个定时器
├── 初始化事件队列
├── IedConnection_create()
├── IedConnection_installStateChangedHandler()
├── IedConnection_connectAsync()
└── 主循环(300ms 周期)
├── mms_m_do_comm() ← 连接状态机
└── if CONNECTED → mms_m_run()
├── mms_m_run_init() ← 首次建连时执行
├── mms_m_do_send() ← 处理事件队列
└── mms_m_timer_running() ← 检查定时器
2.2 连接状态机 mms_m_do_comm()
状态检查: IedConnection_getState()
CLOSED/CLOSING → connectAsync() ← 自动重连
CONNECTING → 等待
CONNECTED → [首次] mms_m_control_init() ← 创建 ControlObjectClient
[首次] 触发 out_status_cb(ON_LINE)
断连检测: old=CONNECTED, new!=CONNECTED
→ running_init = false ← 标记需要重新初始化
→ 触发 out_status_cb(OFF_LINE)
2.3 首次连接初始化 mms_m_run_init()
1. mms_m_icd_init()
├── getLogicalDeviceList() ← 获取 LD 列表
├── 遍历每对 LD+LN:
│ ├── mms_m_icd_dataset_init() ← 读取该 LN 下的所有 DataSet
│ │ └── getDataSetDirectory() ← 读数据集成员
│ ├── mms_m_icd_report_init(URCB) ← 发现 URCB 报告
│ └── mms_m_icd_report_init(BRCB) ← 发现 BRCB 报告
└── ld_datasets 构建完成
2. mms_m_ld_dataset_match_point_init()
└── 将数据集成员与 ldevs 中的 point 关联(构建 p_do_vec)
3. mms_m_rcb_init()
├── getRCBValues() ← 获取每个 RCB 当前值
├── 匹配数据集引用 → p_dataset
├── setResv(true) ← 预留 RCB
├── setTrgOps(dchg|qchg|gi)
├── setRptEna(true) ← 使能报告
├── installReportHandler() ← 安装回调
├── setRCBValues() ← 写入配置到服务器
└── setGI(true) ← 触发一次总召
3. 数据流程
3.1 周期性总召(T0 定时器)
mms_m_do_call_all() ← 每 120s 触发
└── push _MMS_M_EVENT_ALL_CALL 事件
└── mms_m_send_call_all()
└── 遍历 ST + MX 数据点
├── IedConnection_readObject() ← 逐一读取
├── mms_m_get_mmsValue() ← MmsValue→C 类型
├── mms_m_put_value() ← 通过回调输出
└── MmsValue_delete() ← 清理
3.2 报告控制块(RCB)订阅全流程
RCB 订阅是 MMS 客户端最核心的机制,负责接收 IED 主动上送的数据变化。整个流程分为发现→匹配→激活→接收四个阶段。
3.2.1 阶段一:发现 RCB(mms_m_icd_init → mms_m_icd_report_init)
连接成功后,遍历所有 LD→LN,对每对调用 IedConnection_getLogicalNodeDirectory() 分别查询两类 RCB:
// 查询 URCB (unbuffered,引用名含 "RP")
mms_m_icd_report_init(obj, ld_dataset, ACSI_CLASS_URCB);
// 查询 BRCB (buffered,引用名含 "BR")
mms_m_icd_report_init(obj, ld_dataset, ACSI_CLASS_BRCB);
mms_m_icd_report_init() 的核心筛选逻辑:
std::string rpt_ref_str = (URCB == acsiClass) ? "RP" : "BR";
LinkedList reports = IedConnection_getLogicalNodeDirectory(
p_con, &err, ld_ds.ref.c_str(), acsiClass);
LinkedList report = LinkedList_getNext(reports);
while (report != NULL) {
std::string rpt_data = (char*) report->data;
// 提取名称和编号:最后2位是编号,前面是名称
rpt_name = rpt_data.substr(0, rpt_data.length() - 2);
rpt_no = rpt_data.substr(rpt_data.length() - 2);
rpt_ref = ld_ds.ref + "." + rpt_ref_str + ".";
if (0 == rpt_no.compare("01")) // 只取编号 "01"
{
ln_rpt.rpt_ref = rpt_ref + rpt_name;
// 例: "TEMPLATE/LLN0.RP.EventsRCB"
ln_rpt.rpt_ref_with_no = rpt_ref + rpt_name + rpt_no;
// 例: "TEMPLATE/LLN0.RP.EventsRCB01"
ld_ds.ln_rpts.push_back(ln_rpt);
}
// else: 其他编号直接丢弃
report = LinkedList_getNext(report);
}
限制:只订阅编号末尾为 "01" 的 RCB 实例。
3.2.2 阶段二:匹配数据集(mms_m_ld_dataset_match_point_init)
将 XML 配置加载的 ldevs 信号树与从 IED 发现的 ld_datasets 数据集树进行关联,使得每个 dataset member 知道对应哪些信号点(saddr)。
数据集 member 的 FCDA 引用示例: "IEDNAME+TEMPLATE/GGIO1.ST.Ind1.stVal[ST]"
↓ 解析
ld = "IEDNAME+TEMPLATE" → 去 IED 前缀 → "TEMPLATE"
ln = "GGIO1"
d_name = "Ind1"
↓ 匹配 ldevs 树
ldevs[ld="TEMPLATE"] → lnodes[ln="GGIO1"] → dobjs[d_name="Ind1"]
↓
member.p_do_vec = &dobjs.p_do_vec // 建立关联
如果匹配不到(找不到对应的 LD/LN/DO),member.p_do_vec 为 NULL,
该 member 的报告数据将无法输出到任何信号点。
引用字符串解析规则(mms_m.cpp:1228-1257):
- 第1段(
/前):LD 名称,需去除 IED 名前缀 - 第2段(
.前):LN 名称 - 第3段(
[前):DO 名称 - 不匹配此格式的 member 直接跳过
3.2.3 阶段三:配置并激活 RCB(mms_m_rcb_init)
对每个已发现的 RCB,执行完整的配置和激活流程(mms_m.cpp:1158-1214):
┌─ 步骤1:getRCBValues(rcb_ref) 读服务端当前 RCB 值
│
├─ 步骤2:getDataSetReference(rcb) 获取 RCB 当前关联的数据集
│ ↓ ds_ref ← "TEMPLATE/LLN0$Events"
├─ 步骤3:匹配本地数据集
│ 遍历 ln_datasets,通过 dataset_ref 匹配
│ ln_rpt.p_dataset = &matched_dataset
│
├─ 步骤4:本地设置 RCB 参数
│ setResv(true) 预留 RCB(URCB)
│ setTrgOps(dchg | qchg | gi) 触发条件:数据变化+品质变化+总召
│ setDataSetReference(ds_ref) 确认数据集引用
│ setRptEna(true) 使能报告
│ ⚠ 未调用 setOptFlds() 完全依赖服务端默认值
│
├─ 步骤5:installReportHandler() 安装报告回调
│ rcbReference = rpt_ref 如 "LD/LLN0.RP.EventsRCB"
│ rptId = 从 RCB 获取
│ handler = mms_m_report_callback
│ parameter = &ln_rpt 回传 RCB 上下文
│
├─ 步骤6:setRCBValues() 写入服务端
│ parametersMask = RCB_ELEMENT_RPT_ENA |
│ RCB_ELEMENT_TRG_OPS |
│ RCB_ELEMENT_INTG_PD |
│ RCB_ELEMENT_GI
│ singleRequest = true
│ ⚠ mask 包含 INTG_PD 但未调用 setIntgPd()
│ ⚠ mask 不含 OPT_FLDS,OptFlds 沿用服务端默认
│
└─ 步骤7:触发总召
setGI(true) → setRCBValues(mask=RCB_ELEMENT_GI)
让 IED 立即上送一次完整数据
步骤 6 的潜在问题:
parametersMask中包含RCB_ELEMENT_INTG_PD,但本地并未调用setIntgPd(),写入的 IntgPd 值实际是步骤1从服务端读取的原始值RCB_ELEMENT_OPT_FLDS未包含在 mask 中,意味着不会设置服务端的 OptFlds,完全依赖服务端默认配置 — 可能导致报告缺少DataReference、ConfRev、BufferOverflow等字段- 使用
singleRequest=true,一次 MMS 写请求携带多个变量;如果服务端兼容性不好,可能需要改为false
3.2.4 阶段四:报告接收与分发(mms_m_report_callback)
当 IED 发送报告时,libiec61850 回调 mms_m_report_callback()(mms_m.cpp:1079-1153):
ClientReport 到达
│ parameter = &ln_rpt ← 阶段三步骤5传入的回调参数
│
├── ClientReport_getDataSetValues(report) → MmsValue* (MMS_ARRAY)
│
└── 遍历 dataset->members(按数据集顺序,index 从 0 递增)
│
├── reason = ClientReport_getReasonForInclusion(report, index)
│
├── if (reason == NOT_INCLUDED) → 跳过该成员
│
├── mms_value = MmsValue_getElement(dataset_value, index)
│ │
│ └── mms_m_get_MmsValue(point_value, out_value, mms_value, ...)
│ 递归解包 MmsValue → C 类型
│ ├── MMS_STRUCTURE/ARRAY → 递归展开子元素
│ ├── MMS_BOOLEAN → *(uint8_t*)p_val
│ ├── MMS_INTEGER → *(int32_t*)p_val
│ ├── MMS_UNSIGNED → *(uint32_t*)p_val
│ ├── MMS_FLOAT → *(float*)p_val
│ ├── MMS_BIT_STRING → MmsValue_getBitStringAsIntegerBigEndian()
│ ├── MMS_UTC_TIME → 解析时间 → point_value.time
│ └── MMS_BINARY_TIME → 解析时间 → point_value.time
│
├── 如果 mms_value 含时间戳 → tm_flag=1 → 使用报告中的时间
│ 否则 → tm_flag=0 → mms_m_get_local_time() 使用本地时间
│
└── 通过 member.p_do_vec 遍历关联的信号点
for (each point_item in p_do_vec)
out_value.name = point.saddr
out_value.desc = point.desc
out_value.reference = point.reference
out_value.reason = reason
out_value.p_value = 解析后的值指针
out_value.time = 时间(报告时间或本地时间)
out_value.app_fd = obj.obj_fd
↓
mms_m_put_value(obj, out_value) // 回调所有注册的 out_cb
数据输出链路:mms_m_put_value() → 遍历 run.out_cb_lists → 回调上层注册的数据处理函数(如 libiec61850m 中的 mms_data_back)。
3.2.5 断连后的重新订阅
连接状态机在检测到断连时(mms_m.cpp:1579-1588):
if (run.con_state != CONNECTED && run.old_con_state == CONNECTED) {
obj.run.running_init = false; // 清除已初始化标志
out_status_cb(obj.obj_fd, OFF_LINE);
}
下一次 mms_m_run() 检测到 running_init==false 且已重连时,自动重新执行完整的四阶段订阅流程(阶段一→二→三),确保断连恢复后重新订阅所有 RCB。
3.2.6 GI 定时触发
在阶段三完成首次订阅后,定时器 T1(60s)周期性触发 GI 总召(mms_m.cpp:1636-1649):
// 每 60s 一次
event.ctrl_type = _MMS_M_EVENT_GI_CALL;
mms_m_push_event(obj, event);
// → mms_m_send_set_gi()
// 遍历所有 RCB → setGI(true) → setRCBValues(mask=RCB_ELEMENT_GI)
3.2.7 流程总览图
首次连接 / 断连重连
│
▼
阶段一 ✦ 发现 ──────────────────────────────────────
mms_m_icd_init() → mms_m_icd_report_init()
遍历 LD→LN → getLogicalNodeDirectory(URCB/BRCB)
└── 筛选:只取编号末尾为 "01" 的 RCB
产出: ld_datasets[].ln_rpts[] (RCB 列表)
│
▼
阶段二 ✦ 匹配 ──────────────────────────────────────
mms_m_ld_dataset_match_point_init()
解析 dataset member 的 FCDA 引用 (LD/LN/DO)
└── 匹配 ldevs 信号树 → member.p_do_vec 关联
│
▼
阶段三 ✦ 激活 ──────────────────────────────────────
mms_m_rcb_init()
对每个 RCB:
├── getRCBValues() 读取服务端 RCB
├── setResv/setTrgOps/setRptEna 配置参数
├── installReportHandler() 安装回调
├── setRCBValues() 写入服务端
└── setGI(true) 触发总召
│
▼ (此后异步回调)
阶段四 ✦ 接收 ──────────────────────────────────────
mms_m_report_callback()
ClientReport 到达 → 遍历 dataset members
├── 跳过 NOT_INCLUDED 成员
├── 递归解包 MmsValue → C 类型
├── 解析时间戳
└── p_do_vec → mms_m_put_value() → 上层回调
周期性触发:
T1 (60s) → GI ─────────────────────────────────→ 阶段四再次触发
T0 (120s) → AllCall 读取全部 ST+MX 值
3.3 遥控流程
外部调用 mms_m_out_do_set_yk(event)
└── push 遥控事件到事件队列
└── mms_m_send_co() ← 处理遥控事件
├── 按 saddr 匹配 co_point
├── 首次: ControlObjectClient_create()
│ └── setOrigin(ORCAT_STATION_CONTROL)
├── 构造 set_value (MmsValue_newBoolean)
└── 按 ctrl_type 分发:
├── SELECT → mms_m_send_co_select()
│ ├── SBO_NORMAL: select()
│ └── SBO_ENHANCED: selectWithValue()
│ └── 先 setCommandTerminationHandler()
├── DIRECT → mms_m_send_co_direct()
│ ├── operate()
│ └── 按 ctrl_model 检查结果:
│ ├── DIRECT_NORMAL: 立即回读 stVal 验证
│ ├── DIRECT_ENHANCED: 等待 1s 后回读验证
│ └── SBO_ENHANCED: 等待 1s(不验证)
└── CANCEL → mms_m_send_co_cancel()
3.4 参数(定值)写入流程
_PARAM_WRITE 事件
└── mms_m_send_param_write()
├── 匹配参数点
├── 构造 EditSG 引用: {LD}/LLN0.SGCB.EditSG
├── 构造 CnfEdit 引用: {LD}/LLN0.SGCB.CnfEdit
├── 步骤1: writeObject(EditSG, FC=SP) ← 选择编辑定值组
├── 步骤2: writeObject(ref, FC=SE) ← 写入新定值
└── 步骤3: writeObject(CnfEdit, FC=SP) ← 确认编辑
3.5 定值区自动切换
mms_m_send_read_ao() 中检测:
如果某个 AO 信号的 saddr == zone_saddr(绑定的定值区信号)
→ 读值后比较 current_zone
→ 若变化 (new_zone != current_zone):
update current_zone
push _MMS_M_EVENT_PARAM_READ 事件 ← 自动触发参数重读
4. 对外接口
4.1 myMms_m.h 中声明的主要 API
// === 生命周期 ===
int mms_m_out_init(stru_cfg *p_cfg, int debug_print_flag, uint32_t connectionTimeout);
// 返回 app_fd (>0 成功,-1 失败)
// === 状态回调 ===
int mms_m_out_get_connect_status(int fd, mms_m_out_status_cb p_func);
// 连接/断开时回调: void (int app_fd, int status) // MMS_M_ON_LINE / MMS_M_OFF_LINE
// === 数据回调 ===
int mms_m_out_get_value(int fd, mms_m_out_value_cb p_func);
// 数据到达时回调: void (stru_mms_m_out_value *p_value)
// === 遥控 ===
int mms_m_out_do_set_yk(stru_mms_m_event *p_event);
// 发送遥控事件(select/direct/cancel),返回 -1 失败
// === AO/参数操作 ===
int mms_m_out_read_ao_or_params(int app_fd, uint8_t type, const char *saddr);
// type: _MMS_M_EVENT_AO_READ 或 _MMS_M_EVENT_PARAM_READ
// saddr 为 NULL 则读取全部
// === 定值区绑定 ===
int mms_m_out_bind_param_zone_signal(int app_fd, const char *zone_saddr);
// 绑定一个 AO 信号作为定值区指示器
// === 调试 ===
int mms_m_out_debug_print_swicth(int id, int debug_print_flag);
// === 工具函数 ===
void *mms_m_create_data_ptr(uint8_t type); // 按 MMS 类型创建数据指针
int mms_m_set_data_value(void *srt, void *dst, uint8_t type); // 复制值
int mms_m_get_data_value_str(void *data, uint8_t type, char *str); // 转字符串
int mms_m_set_data_by_str(void *data, uint8_t type, const char *str); // 从字符串设置
char *mms_m_out_reason_str(int reason); // 原因码转字符串
void *mms_m_get_obj(int app_fd); // 获取对象(给扩展模块)
4.2 扩展接口(mms_m_ext.h)
// 文件服务
typedef void (*mms_m_file_read_cb)(int fd, const char *fn, const uint8_t *d, int len, bool mf, int e);
typedef void (*mms_m_file_op_cb)(int fd, const char *fn, int e);
int mms_m_read_file(int fd, const char *rf, mms_m_file_read_cb cb);
int mms_m_delete_file(int fd, const char *rf, mms_m_file_op_cb cb);
// 服务器信息查询
typedef void (*mms_m_server_cb)(int fd, const char *vendor, const char *model, const char *rev, int log_st, int phy_st, int e);
int mms_m_query_server(int fd, mms_m_server_cb cb);
// 定值组信息
typedef void (*mms_m_sg_cb)(int fd, const char *ld, int act_sg, int num_sg, int e);
int mms_m_read_sg_info(int fd, const char *ld, mms_m_sg_cb cb);
4.3 扩展回调类型汇总
| 回调类型 | 签名 | 用途 |
|---|---|---|
mms_m_out_status_cb |
void(int fd, int status) |
连接状态 |
mms_m_out_value_cb |
void(stru_mms_m_out_value *p_value) |
数据到达 |
mms_m_file_read_cb |
void(int, const char*, const uint8_t*, int, bool, int) |
文件分块读取 |
mms_m_file_op_cb |
void(int fd, const char* filename, int err) |
文件删除结果 |
mms_m_server_cb |
void(int, const char*, const char*, const char*, int, int, int) |
服务器信息 |
mms_m_sg_cb |
void(int, const char*, int act_sg, int num_sg, int err) |
定值组信息 |
5. 错误码(mms_m_errstr.cpp)
提供四种枚举值到字符串的映射:
| 函数 | 映射 |
|---|---|
mms_m_err_str(IedClientError) |
IED 客户端错误(30 个映射) |
mms_m_control_err_str(ControlLastApplError) |
控制应用错误(4 个映射) |
mms_m_ctl_add_cause_str(ControlAddCause) |
控制附加原因(28 个映射) |
mms_m_ctrl_model_str(ControlModel) |
控制模型(5 个映射) |
6. 文件服务(mms_m_file.cpp)
实现状态
| 功能 | 状态 | 使用的 API |
|---|---|---|
| 文件打开 | 已实现 | MmsConnection_fileOpen() |
| 分块异步读取 | 未完成(TODO) | 待实现 MmsConnection_fileReadAsync() |
| 文件删除 | 已实现 | MmsConnection_fileDelete() |
调用方式
所有文件操作通过 IedConnection_getMmsConnection() 获取底层 MmsConnection 句柄,然后调用 MMS 低层文件 API。
7. 设计要点
- 事件驱动:所有对外操作通过事件队列异步执行,避免阻塞调用线程
- 自动重连:连接断开后自动重试连接,连接恢复后重新初始化报告
- 数据结构三棵树:
- ldevs:按 LD→LN→DO 组织的信号点树(用于信号匹配输出)
- ld_datasets/ln_rpts:数据集和 RCB 树(用于报告接收和 GI 触发)
- g_mms_m_obj_map:多客户端实例管理
- 定值区自动跟踪:通过绑定 zone_saddr,自动检测定值区变化并重读参数
- 调试标志:全局
debug_print_flag控制日志输出详细程度 - RCB 激活配置缺陷:
setRCBValues的parametersMask包含RCB_ELEMENT_INTG_PD但本地未调用setIntgPd(),写入的是从服务端读回的旧值RCB_ELEMENT_OPT_FLDS未包含在 mask 中,OptFlds 完全依赖服务端默认值,可能导致报告缺少DataReference、ConfRev、BufferOverflow等字段mms_m_icd_report_init只取编号"01"的 RCB,其他编号直接丢弃