资讯动态

libIEC61850 pyiec61850 Python 绑定:从 CMake 构建到 RCB 订阅回调的完整实践指南

发布时间:2026/10/13 15:40:20 来源:尧图企业网站定制
通信嵌入式工业制造【免费下载链接】libiec61850Official repository for libIEC61850, the open-source library for the IEC 61850 protocols项目地址https://gitcode.com/gh_mirrors/li/libiec61850点击查看免费下载本文基于 libIEC61850 仓库中的 pyiec61850/README.md 展开系统讲解如何通过 CMake 选项BUILD_PYTHON_BINDINGS编译并安装 SWIG 生成的 Python 绑定库pyiec61850覆盖 Linux Ubuntu / Alpine 开发环境搭建、make test集成测试、客户端连接的读写操作以及仓库示例中的模型浏览与 RCB报告控制块订阅回调实战。读完本文你可以独立完成 Python 绑定的构建安装并掌握其与 C API 的两处关键差异类型转换函数、指针参数的返回值约定以及 GIL 安全的事件回调机制。一、pyiec61850 的定位C 库之上的 SWIG 封装层pyiec61850是 libIEC61850 的 Python 封装位于仓库的 pyiec61850/ 目录下其核心构成包括iec61850.iSWIG 接口文件声明了%module(directors1) pyiec61850启用了 directors 特性以支持从 Python 继承 C 抽象类用于报告、GOOSE、控制等回调CMakeLists.txt负责查找 SWIG 与 Python 开发组件、生成并链接绑定库、确定安装路径、注册集成测试eventHandlers/ 目录eventHandler.hpp、reportControlBlockHandler.hpp、gooseHandler.hpp、commandTermHandler.hpp、controlActionHandler.hpp等 C 头文件实现 libIEC61850 的 C 回调到 Python 用户方法的桥接servicePythonWrapper.hpp对若干同步服务的 GIL 安全包装examples/ 与 test_pyiec61850.py示例与集成测试。README 明确指出Python 绑定的工作方式与基础 C 库类似但有两处必须注意的差异需要调用专门的转换函数完成变量类型转换如把void*列表数据转成字符串要调用toCharP通过指针传入的输出参数不再出现在参数列表中而是并入返回值列表。例如IedConnection_getLogicalDeviceList在 C API 中是void IedConnection_getLogicalDeviceList(IedConnection self, LinkedList *outList, IedClientError *error)而在 Python 中变为返回[deviceList, error]元组。这一约定由 iec61850.i 中的 SWIG 指令直接实现%apply int *OUTPUT {IedClientError* error};该指令把所有IedClientError* error形式的输出参数统一改写为返回值的一部分这就是错误码进返回值列表的底层原因。而第一类差异则由接口文件中定义的一组 C 辅助函数支撑char* toCharP(void * v) { return (char *) v; } ModelNode* toModelNode(LogicalNode * ln) { return (ModelNode*) ln; } DataObject* toDataObject(ModelNode * MN) { return (DataObject*)MN; }这些函数在 iec61850.i 的%{ %}块中定义供生成的 Python 代码显式调用从而让 Python 端能够处理LinkedList中存放的char*、模型节点之间的类型转换等 C 类型细节。此外iec61850.i 还引入了cstring.i并声明%cstring_bounded_output(char *buffer, 1024)对以char* buffer为出参的字符串型接口如MmsValue_getTypeString做有界字符串输出处理保证 C 字符串被安全转换为 Python 字符串。二、构建前置开发环境安装README 附录完整内容按照 pyiec61850/README.md 的附录构建 Python 绑定前需要安装 SWIG 和 Python开发头文件。以下是仓库给出的两套最小依赖集。Ubuntu已验证版本Ubuntu 20.04 LTS / 22.04 LTS / 23.10编译 libIEC61850 及 Python wrapper 所需的最小包集不含 TLS、SQLite 等可选依赖$ sudo apt-get update $ sudo apt-get install g cmake swig git python3 python3-all-devAlpine已验证版本Alpine 3.18$ apk update $ apk add git g swig make cmake python3 python3-dev linux-headers需要注意python3-all-dev/python3-dev提供 CPython 开发头文件与库因为 pyiec61850/CMakeLists.txt 中通过find_package(Python COMPONENTS Interpreter Development REQUIRED)显式要求 Python 的 Interpreter 与 Development 组件缺少头文件时 CMake 配置阶段就会失败。三、构建与安装BUILD_PYTHON_BINDINGS 的 CMake 流程3.1 标准构建命令README 给出的构建步骤开启BUILD_PYTHON_BINDINGS选项$ mkdir build cd build $ cmake -DBUILD_PYTHON_BINDINGSON ..然后编译并安装$ make $ sudo make install如有必要刷新动态库缓存sudo ldconfig。CMake 与 SWIG 会自动检测 Python 版本并把 Python 库安装到对应 Python 的库目录。README 特别强调自动检测这一点你无需手工指定版本后缀安装产物会落到当前 Python 的platlib目录。3.2 该选项在构建系统中的落点在根目录 CMakeLists.txt 中该选项默认是关闭的option(BUILD_PYTHON_BINDINGS Build Python bindings OFF)只有当选项开启时才会挂载子目录CMakeLists.txtif(BUILD_PYTHON_BINDINGS) add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/pyiec61850) endif(BUILD_PYTHON_BINDINGS)子目录的 pyiec61850/CMakeLists.txt 完成以下关键工作依赖检查find_package(SWIG REQUIRED)与find_package(Python COMPONENTS Interpreter Development REQUIRED)并include(${SWIG_USE_FILE})SWIG 模块配置将iec61850.i标记为 C 模式CPLUSPLUS ON并设置模块名SWIG_MODULE_NAME pyiec61850——生成的 Python 导入名pyiec61850即来源于此链接目标库if(WIN32) set(LIBS iec61850 ws2_32) else() set(LIBS iec61850-shared) endif() swig_link_libraries(pyiec61850 ${LIBS})即 Unix 下绑定库链接的是共享库iec61850-shared这也解释了为什么安装后可能需要sudo ldconfig刷新动态库缓存自动探测 Python 安装路径pyiec61850/CMakeLists.txtexecute_process( COMMAND ${Python_EXECUTABLE} -c from sysconfig import get_path; import sys; sys.stdout.write(get_path(platlib)) OUTPUT_VARIABLE PYTHON_SITE_DIR ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pyiec61850.py DESTINATION ${PYTHON_SITE_DIR}) install(TARGETS pyiec61850 LIBRARY DESTINATION ${PYTHON_SITE_DIR})即生成物pyiec61850.pySWIG 生成的 Python 胶水层与同名共享库被一起安装到 Python 的platlib目录安装完成后直接import pyiec61850即可无需再改PYTHONPATH注册集成测试add_test(test_pyiec61850 ${Python_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/test_pyiec61850.py)3.3 运行集成测试构建完成后按 README 运行make test即 CTest。该测试执行 test_pyiec61850.py它是一个同进程自产自销的完整闭环用IedModel_create(testmodel)创建一个动态模型建立逻辑设备SENSORS与逻辑节点LLN0、TTMP1并通过CDC_SAV_create/CDC_ASG_create生成一个模拟量TmpSv与一个设定值TmpSp用IedServer_createIedServer_start(…, 8102)在端口 8102 启动服务端并用IedServer_isRunning校验启动结果服务端线程跑起来后测试函数testClient()建立IedConnection连接localhost:8102读testmodelSENSORS/TTMP1.TmpSv.instMag.f功能约束IEC61850_FC_MX断言错误码为 0尝试写 SAV 值断言返回错误码 21SAV 为只读写入被拒这是预期的失败路径验证读/写testmodelSENSORS/TTMP1.TmpSp.setMag.f功能约束IEC61850_FC_SP断言写入成功err0且回读值与新值一致最后IedConnection_close/IedConnection_destroy收尾服务端线程退出时执行IedServer_stop与资源销毁。这个测试同时覆盖了 Python 端读、写、只读属性拒写、错误码判断四类基本路径是验证绑定库安装是否正确的最低成本手段。四、导入与客户端基础用法README 给出的导入方式是import pyiec61850 as iec618504.1 连接的生命周期README 的建连示例con iec61850.IedConnection_create() error iec61850.IedConnection_connect(con, localhost, 102) if (error iec61850.IED_ERROR_OK): # Do some work iec61850.IedConnection_close(con) iec61850.IedConnection_destroy(con)对应 C API 的三步曲create→connect阻塞式同步连接返回错误码→ 用完close断连、destroy释放。错误码枚举定义在 iec61850_client.h其中IED_ERROR_OK 0。这里的端口 102 是 IEC 61850 的 MMS 标准端口MMS over TCP 默认 102需相应权限。4.2 遍历逻辑设备列表输出参数并入返回值的典型场景[deviceList, error] iec61850.IedConnection_getLogicalDeviceList(con) device iec61850.LinkedList_getNext(deviceList) while device: print(LD: %s % iec61850.toCharP(device.data)) [logicalNodes, error] iec61850.IedConnection_getLogicalDeviceDirectory( con, iec61850.toCharP(device.data)) device iec61850.LinkedList_getNext(device) iec61850.LinkedList_destroy(deviceList)这里有三个要点IedConnection_getLogicalDeviceList与IedConnection_getLogicalDeviceDirectory均返回[LinkedList, error]元组正是%apply int *OUTPUT {IedClientError* error}规则的作用结果LinkedList元素中的字符串需要toCharP(device.data)显式转成 Python 字符串这体现了 README 说的专门函数做类型转换遍历结束后必须LinkedList_destroy释放链表注意只销毁链表本体logicalNodes子链表在更深一层的浏览中也要单独销毁可参考下文的 dispServerStruct.py 完整写法。4.3 读操作与写操作[floatValue, error] iec61850.IedConnection_readFloatValue(con, simpleIOGenericIO/GGIO1.AnIn1.mag.f, iec61850.IEC61850_FC_MX) err iec61850.IedConnection_writeFloatValue(con, simpleIOGenericIO/GGIO1.AnIn1.mag.f, iec61850.IEC61850_FC_MX, 10.0)读返回[值, 错误码]二元组写只返回错误码对应 C 函数本身返回IedClientError对象引用遵循LD/LN.DO.DA层级写法功能约束以iec61850.IEC61850_FC_*枚举传入。仓库示例 dispServerStruct.py 把连接 → 遍历 LD → 遍历 LN → 列出 Data Object的完整浏览流程做成了可直接运行的脚本支持python3 dispServerStruct.py hostname port默认localhost、102其核心结构[deviceList, error] iec61850.IedConnection_getLogicalDeviceList(con) device iec61850.LinkedList_getNext(deviceList) while device: LD_name iec61850.toCharP(device.data) print(LD: %s % LD_name) [logicalNodes, error] iec61850.IedConnection_getLogicalDeviceDirectory(con, LD_name) logicalNode iec61850.LinkedList_getNext(logicalNodes) while logicalNode: LN_name iec61850.toCharP(logicalNode.data) [LNobjects, error] iec61850.IedConnection_getLogicalNodeVariables( con, LD_name / LN_name) ...这是熟悉绑定后快速摸清远端 IED 模型结构哪些 LD、LN、DO的实用工具。五、进阶在 Python 中订阅 RCB 报告director 回调机制README 正文之外的 examples/rcbSubscriptionExample.py 展示了 pyiec61850 最有价值的进阶能力以 Python 类继承的方式接收报告控制块RCB报告。该示例与仓库 C 示例 server_example_basic_io 配套用法$ sudo python3 ./pyiec61850/examples/rcbSubscriptionExample.py # 或指定主机与端口 $ python3 ./pyiec61850/examples/rcbSubscriptionExample.py localhost 81025.1 两个被 SWIG 包装的角色示例文件头部注释说明SWIG 生成了两个被包装对象通用 RCB handler与通用 RCB subscriber。用户需要在 Python 中创建自己的 RCB handler决定如何处理收到的报告在 Python 中创建自己的 RCB subscriber携带注册参数用组合关系把 handler 挂到 subscriber 上。handler 端PyRCBHandler继承自 director 类iec61850.RCBHandler并重写trigger()方法在其中解析报告l_rcb_ref iec61850.ClientReport_getRcbReference(self._client_report) if iec61850.ClientReport_hasDataSetName(self._client_report): l_dataset_name iec61850.ClientReport_getDataSetName(self._client_report) # 报告 id、序列号、时间戳毫秒…… mms_value_array iec61850.ClientReport_getDataSetValues(self._client_report) for mms_value_index in range(mms_value_array_size): mms_value iec61850.MmsValue_getElement(mms_value_array, mms_value_index) mms_value_type iec61850.MmsValue_getTypeString(mms_value) if mms_value_type boolean: ... elif mms_value_type float: ... l_reason iec61850.ClientReport_getReasonForInclusion(self._client_report, mms_value_index) l_reason_str iec61850.ReasonForInclusion_getValueAsString(l_reason)注意trigger()内部用try/except捕获了全部 Python 异常。这不是防御性编程的装饰而是机制要求这段代码运行在 SWIG director 回调所在的 C 子线程里若 Python 异常向上逃逸到 C 回调层会导致进程崩溃——这一点在 reportControlBlockHandler.hpp 的注释中也得到印证we must catch the Python exceptions, otherwise it will crash。5.2 底层桥接从 C 回调到 Python 方法director 机制的 C 侧实现位于 eventHandlers/reportControlBlockHandler.hpp。RCBSubscriber::subscribe()中把一个 C 静态方法作为函数指针安装进 libIEC61850IedConnection_installReportHandler(m_ied_connection, m_rcb_reference.c_str(), m_rcb_rpt_id.c_str(), RCBSubscriber::triggerRCBHandler, NULL);当服务端报告到达时triggerRCBHandler被 C 层调用其处理链是PyThreadStateLock PyThreadLock;—— 构造 RAII 锁对象。该类在 eventHandlers/eventHandler.hpp 中定义构造时PyGILState_Ensure()析构时PyGILState_Release()保证回调线程进入 Python 代码前正确持有 GIL通过ClientReport_getRcbReference(report)取出 RCB 参考串在EventSubscriber::m_subscriber_map一个std::string → EventSubscriber*的静态注册表定义于 eventHandler.hpp中查找对应的 subscriber调l_event_handler_p-setReceivedData(report)RCBHandler 的该实现把ClientReport值拷贝进_client_report成员见 reportControlBlockHandler.hpp调l_event_handler_p-trigger()—— 这一跳由 SWIG director 转发为 Python 端的PyRCBHandler.trigger()。subscriber 端Python 类PyRCBSubscriber的subscribe()流程与 C API 的报告订阅一致只是把回调环节换成了 director 对象# 与 C API 相同读取 RCB 值 [l_rcb_client, l_error] iec61850.pyWrap_IedConnection_getRCBValues( self._connection, i_report_control_block_ref, None) # Python 特有初始化通用 subscriber l_client_report_control_block_id iec61850.ClientReportControlBlock_getRptId(l_rcb_client) self._wrapped_rcb_subscriber.setIedConnection(self._connection) self._wrapped_rcb_subscriber.setRcbReference(i_report_control_block_ref) self._wrapped_rcb_subscriber.setRcbRptId(l_client_report_control_block_id) # Python 特有挂接回调 handler self._internal_rcb_handler.thisown 0 # 所有权移交给 subscriber self._wrapped_rcb_subscriber.setEventHandler(self._internal_rcb_handler) l_registering_status self._wrapped_rcb_subscriber.subscribe() # 与 C API 相同设置触发选项并使能报告 l_trigger_options (iec61850.TRG_OPT_DATA_UPDATE | iec61850.TRG_OPT_INTEGRITY | iec61850.TRG_OPT_GI | iec61850.TRG_OPT_DATA_CHANGED | iec61850.TRG_OPT_QUALITY_CHANGED) iec61850.ClientReportControlBlock_setTrgOps(l_rcb_client, l_trigger_options) iec61850.ClientReportControlBlock_setRptEna(l_rcb_client, True) l_error iec61850.pyWrap_IedConnection_setRCBValues( self._connection, l_rcb_client, iec61850.RCB_ELEMENT_RPT_ENA | iec61850.RCB_ELEMENT_TRG_OPS, True)其中self._internal_rcb_handler.thisown 0是 SWIG 的所有权语义把 handler C 对象的生命周期交给 subscriber 管理避免 Python 引用归零时对象被提前析构。TRG_OPT_INTEGRITY等触发选项宏定义在 iec61850_common.hRCB_ELEMENT_RPT_ENA定义在 iec61850_client.h。destroy()方法则对称地先把rptEna置 False 并通过pyWrap_IedConnection_setRCBValues下发再ClientReportControlBlock_destroy与del self._wrapped_rcb_subscriber释放对象。示例主程序在同一连接上订阅了simpleIOGenericIO/LLN0.RP.EventsRCB01与simpleIOGenericIO/LLN0.BR.Measurements01两个 RCB等待 3 秒接收报告后统一销毁。5.3 为什么用 pyWrap_* 包装同步服务GIL 与死锁防护上面示例中刻意调用的是pyWrap_IedConnection_getRCBValues/pyWrap_IedConnection_setRCBValues而非裸的IedConnection_getRCBValues/IedConnection_setRCBValues。原因见 servicePythonWrapper.hpp这些同步服务内部可能触发已注册的 Python 回调例如一次 get/set RCB 交互过程中立即收到报告若调用期间 GIL 仍被当前 Python 线程持有回调线程将因拿不到 GIL 而死锁。包装类PyThreadStateSave在 RAII 语义下先PyEval_SaveThread()释放 GIL调用 C 服务再在析构时恢复class PyThreadStateSave { public: PyThreadStateSave(void) { if (PyGILState_Check()) state PyEval_SaveThread(); else state nullptr; } ~PyThreadStateSave(void) { if (state) PyEval_RestoreThread(state); } private: PyThreadState* state; };该文件为readObject、writeObject、getRCBValues、setRCBValues四个同步服务提供了同名pyWrap_前缀的包装版本。结论在 Python 里对同一连接执行可能引发回调的同步服务时应优先使用pyWrap_版本这是 pyiec61850 区别于直接调 C 库的一个实用细节。六、其他已桥接的事件回调能力iec61850.i 通过%feature(director)声明了五组 director 类eventHandlers/ 目录中各有对应实现director 类对应头文件用途RCBHandlerreportControlBlockHandler.hpp客户端报告RCB接收处理GooseHandlergooseHandler.hppGOOSE 订阅消息接收处理CommandTermHandlercommandTermHandler.hpp控制命令结束事件ControlObjectClient的 command termination 回调CheckHandlerForPython/WaitForExecutionHandlerForPython/ControlHandlerForPythoncontrolActionHandler.hpp服务端直接控制的三个执行阶段回调perform check / wait for execution / control它们共享同一套桥接模式C 层静态回调 →PyThreadStateLock获取 GIL → 按对象引用RCB 参考、GOOSE 控制块引用、控制对象引用等在EventSubscriber::m_subscriber_map中查表 →setReceivedData拷贝数据 →trigger()分发给 Python 方法。例如 GOOSE 侧以GooseSubscriber_getGoCbRef作为注册键gooseHandler.hpp控制侧以ModelNode_getObjectReference生成的对象引用作为注册键controlActionHandler.hpp。此外 iec61850.i 还为 GOOSE 订阅/发布做了两个 Python 友好的适配由于 C API 的GooseSubscriber_setDstMac/CommParameters_setDstAddress需要传 6 字节 MAC 数组SWIG 层额外生成了把 6 个uint8_t参数组装成数组的包装函数Python 端可以逐个字节传入目的 MAC。七、要点小结构建cmake -DBUILD_PYTHON_BINDINGSON ..makesudo make install必要时sudo ldconfig产物自动安装到 CMake 探测到的 Pythonplatlib目录Ubuntu/Alpine 的最小依赖包集合见上文附录前提是g、cmake、swig与 Python 开发组件齐备验证make test会执行 test_pyiec61850.py 的同进程服务端 客户端读写闭环覆盖只读 SAV 拒写与可写 ASG 成功写入两条路径API 差异C 的指针出参在 Python 中并入返回值元组[值, error]void*/模型节点转换需显式调用toCharP、toModelNode等辅助函数根源是 iec61850.i 中的%apply int *OUTPUT与辅助 C 函数回调RCB、GOOSE、控制、命令结束四类事件都可通过继承 SWIG director 类RCBHandler、GooseHandler、ControlHandlerForPython等在 Python 中实现回调运行于 C 子线程Python 端必须自行捕获异常并发安全对可能触发回调的同步服务getRCBValues/setRCBValues/readObject/writeObject优先使用 servicePythonWrapper.hpp 提供的pyWrap_前缀版本以避免 GIL 死锁。赞分享通信嵌入式工业制造【免费下载链接】libiec61850Official repository for libIEC61850, the open-source library for the IEC 61850 protocols项目地址https://gitcode.com/gh_mirrors/li/libiec61850点击查看免费下载相关推荐Mastra timeTravel 报 Time travel target step not found in execution graph 怎么排查Mastra timeTravel 报 Time travel target step not found in execution graph 怎么排查 在网络安全模式匹配Z3 Julia 绑定从 CMake 构建到 Z3.jl 本地二进制接入的完整指南Z3 Julia 绑定从 CMake 构建到 Z3.jl 本地二进制接入的完整指南 导读 Z3 定理证明器The Z3 Theorem Prover通过多开发工具Unicorn .NET 绑定实战指南从源码构建到 C/F 仿真的完整实践Unicorn .NET 绑定实战指南从源码构建到 C /F 仿真的完整实践 本篇技术指南围绕 Unicorn CPU 模拟器框架支持 ARM、AArch6虚拟化逆向工程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑