OPCUA与asyncua实战入门:从零构建一个异步数据服务器与客户端

张开发
2026/4/19 1:34:33 15 分钟阅读

分享文章

OPCUA与asyncua实战入门:从零构建一个异步数据服务器与客户端
1. 为什么选择OPC UA和asyncua在工业物联网和自动化控制领域设备间的数据通信一直是个头疼的问题。不同厂商的设备使用不同的协议就像一群人说着不同的方言沟通起来特别费劲。OPC UAOpen Platform Communications Unified Architecture就是为了解决这个问题而生的它就像工业领域的普通话让各种设备能够用同一种语言交流。而asyncua则是OPC UA的Python实现它有几个特别吸引人的特点纯Python实现不需要复杂的编译环境安装即用异步IO支持基于asyncio适合高并发的工业场景简单易用API设计直观学习曲线平缓开源免费社区活跃问题容易得到解决我去年在一个智能工厂项目中就用到了asyncua当时需要在30多台设备间建立实时数据交换。传统方案要么太贵要么开发周期长而asyncua让我们在两周内就搭建起了原型系统实测下来非常稳定。2. 环境准备与安装2.1 Python环境要求asyncua需要Python 3.7及以上版本。我强烈建议使用虚拟环境来管理项目依赖这样可以避免包冲突问题。下面是创建虚拟环境的命令# 创建虚拟环境 python -m venv opcua_env # 激活虚拟环境 # Windows opcua_env\Scripts\activate # Linux/MacOS source opcua_env/bin/activate2.2 安装asyncua安装asyncua非常简单一条pip命令就能搞定pip install asyncua如果你需要用到额外的功能比如历史数据记录可以安装可选依赖pip install asyncua[history]我在Windows和Linux上都测试过安装过程基本不会遇到什么问题。唯一需要注意的是在某些企业内网环境下可能需要先配置好pip的代理设置。3. 构建OPC UA服务器3.1 基础服务器搭建让我们从一个最简单的服务器开始。新建一个文件server.py写入以下代码import logging import asyncio from asyncua import Server logging.basicConfig(levellogging.INFO) logger logging.getLogger(asyncua) async def main(): # 创建服务器实例 server Server() await server.init() # 设置服务器端点地址 server.set_endpoint(opc.tcp://0.0.0.0:4840/freeopcua/server/) # 设置服务器名称 server.set_server_name(My First OPC UA Server) # 注册命名空间 uri http://mycustom.namespace.com idx await server.register_namespace(uri) logger.info(Server started at opc.tcp://0.0.0.0:4840/freeopcua/server/) async with server: while True: await asyncio.sleep(1) if __name__ __main__: asyncio.run(main())这段代码做了以下几件事创建了一个OPC UA服务器实例设置监听地址为0.0.0.0:4840注册了一个自定义命名空间启动服务器并保持运行你可以通过运行python server.py来启动服务器。启动后用专业的OPC UA客户端如UaExpert就能连接到这个服务器了。3.2 添加变量和对象光有服务器还不够我们需要添加一些实际的数据点。修改main函数添加以下内容async def main(): # ...之前的代码不变... # 获取Objects节点 objects server.nodes.objects # 添加一个自定义对象 my_obj await objects.add_object(idx, MyDevice) # 添加一些变量 temperature await my_obj.add_variable(idx, Temperature, 25.0) pressure await my_obj.add_variable(idx, Pressure, 1.0) status await my_obj.add_variable(idx, Status, Idle) # 设置变量为可写 await temperature.set_writable() await pressure.set_writable() await status.set_writable() # ...之后的代码不变...现在我们的服务器就有了三个变量温度、压力和状态。这些变量都可以通过客户端读取和写入。在实际项目中你可能会把这些变量绑定到实际的设备数据上。4. 构建OPC UA客户端4.1 基础客户端实现有了服务器我们还需要一个客户端来测试通信。新建一个文件client.py写入以下代码import asyncio import logging from asyncua import Client logging.basicConfig(levellogging.INFO) logger logging.getLogger(asyncua) async def main(): client Client(urlopc.tcp://localhost:4840/freeopcua/server/) try: async with client: # 获取命名空间索引 uri http://mycustom.namespace.com idx await client.get_namespace_index(uri) # 读取变量 temp_node await client.nodes.root.get_child([ 0:Objects, f{idx}:MyDevice, f{idx}:Temperature ]) pressure_node await client.nodes.root.get_child([ 0:Objects, f{idx}:MyDevice, f{idx}:Pressure ]) current_temp await temp_node.read_value() current_pressure await pressure_node.read_value() logger.info(fCurrent Temperature: {current_temp}) logger.info(fCurrent Pressure: {current_pressure}) except Exception as e: logger.error(fConnection failed: {e}) if __name__ __main__: asyncio.run(main())这个客户端会连接到我们之前创建的服务器并读取温度和压力变量的值。运行它之前确保服务器已经在运行。4.2 写入变量和订阅变化除了读取变量客户端还可以修改变量值和订阅变量变化。在main函数中添加以下代码async def main(): client Client(urlopc.tcp://localhost:4840/freeopcua/server/) try: async with client: # ...之前的代码不变... # 写入新值 new_temp current_temp 1.0 await temp_node.write_value(new_temp) logger.info(fTemperature updated to: {new_temp}) # 订阅变量变化 subscription await client.create_subscription(500, handler) handle await subscription.subscribe_data_change(temp_node) # 保持连接以接收通知 await asyncio.sleep(10000) except Exception as e: logger.error(fError: {e}) # 变化处理函数 def handler(node, val, data): print(fData change on {node}: {val})现在每当温度值发生变化时handler函数就会被调用打印出新的值。这在实际应用中非常有用比如当某个设备参数超出安全范围时可以立即触发警报。5. 实际应用中的进阶技巧5.1 安全配置在生产环境中安全性至关重要。OPC UA支持多种安全策略我们可以这样配置async def main(): server Server() # 启用基本安全策略 await server.init() server.set_security_policy([ ua.SecurityPolicyType.Basic256Sha256_SignAndEncrypt, ua.SecurityPolicyType.Basic256Sha256_Sign ]) # 加载证书和私钥 await server.load_certificate(server_cert.pem) await server.load_private_key(server_key.pem) # ...其他代码...客户端连接时也需要提供相应的证书client Client(urlopc.tcp://localhost:4840/) client.set_security_string(Basic256Sha256,SignAndEncrypt,cert.pem,key.pem)5.2 性能优化当需要处理大量数据时性能优化就很重要了。这里有几个我实践过的技巧批量读取使用read_values()代替多次read_value()合理设置订阅参数采样间隔不宜过短使用历史数据对于不常变化的数据可以启用历史记录功能# 批量读取示例 nodes_to_read [temp_node, pressure_node, status_node] values await client.read_values(nodes_to_read)5.3 错误处理与重连机制网络不稳定是常见问题实现自动重连很有必要async def run_client(): while True: try: await main() except ConnectionError: logger.warning(Connection lost, retrying in 5 seconds...) await asyncio.sleep(5) except Exception as e: logger.error(fUnexpected error: {e}) break if __name__ __main__: asyncio.run(run_client())6. 调试与问题排查6.1 常见问题解决在实际开发中你可能会遇到这些问题连接失败检查防火墙设置和端口是否开放证书错误确保证书有效且时间正确权限问题检查用户是否有读写权限我遇到过最棘手的问题是证书时间不同步导致安全连接失败。后来发现是服务器时间比实际时间慢了5分钟调整后问题解决。6.2 日志配置合理的日志配置能大大简化调试过程logging.basicConfig( levellogging.DEBUG, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(opcua.log), logging.StreamHandler() ] )这样既能将日志输出到控制台又能保存到文件中方便后续分析。6.3 使用Wireshark抓包分析对于复杂的通信问题可以使用Wireshark抓取OPC UA协议包安装Wireshark过滤条件设置为opcua分析通信过程中的具体报文这个方法帮我解决过多次协议层面的疑难杂症特别是在安全策略配置出错时特别有用。

更多文章