FreeOpcUa,是使用Python开发基于OPC统一架构的优选第三方库,项目链接:https://github.com/FreeOpcUa/python-opcua
创建一个OPC服务器的步骤非常简单:
from opcua import Server
server = Server() # 实例化一个UA服务器
server.set_endpoint("opc.tcp://0.0.0.0:48400/freeopcua/server/") # 设定服务器URI
server.start() # 启动UA服务器
测试所创建的UA服务器,建议可以使用UaExpert,它可以实现UA客户端的功能,下载链接:https://www.unified-automation.com
软件打开后,右键“Servers”,添加新连接
2.在“Custom Discovery”下双击,并填入上面代码中设定的服务器URI
上步添加完URI后,会扫描到Python中创建的UA服务器,选择“Anonymous”,建立连接
3.连接成功后在主页面能够看到OPC UA规范定义的标准地址空间结构
对于如何通过FreeOpcUa,创建自己的地址空间,项目源代码Examples文件夹下的server-example.py文件进行了举例。这里要介绍的是如何通过XML文件来编辑地址空间。
在XML文件中创建节点和分配引用,是非常非常棒的一种方法。
编写XML文件,首先添加命名空间,主要包括节点类、数据类型以及W3C标准。
<UANodeSet xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd"
xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
UANodeSet>
填写正确的NodeId,用于明确标识节点
填写BrowseName,作为浏览地址空间时的非本地化名称
填写ParentNodeId,在实例化时非常关键,与ModelParent相关,如果不对实例声明进行明确指定,会导致无法实例化
分配引用,需要注意每种引用可以使用的次数(在节点类学习心得中进行了总结中)
创建变量节点
<UAVariable NodeId="" BrowseName="" DataType="" ParentNodeId="" AccessLevel="" UserAccessLevel="">
<Description>Description>
<DisplayName>DisplayName>
<References>
<Reference ReferenceType="" IsForward="">Reference>
References>
<Value>Value>
UAVariable>
AccessLevel,设定访问方式,可读可写或其他
Value,定义节点Value属性
一个简单的例子
from opcua import Server
server = Server()
server.set_endpoint("opc.tcp://0.0.0.0:48400/freeopcua/server/") # 设定服务器URI
server.import_xml("custom_nodes.xml") # 导入XML文件
server.start()
<UANodeSet xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd"
xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<UAObject NodeId="i=30001" BrowseName="MyXMLFolder">
<Description>A custom folder.Description>
<References>
<Reference ReferenceType="HasTypeDefinition">i=61Reference>
<Reference ReferenceType="Organizes" IsForward="false">i=85Reference>
References>
UAObject>
<UAObject NodeId="i=30002" BrowseName="MyXMLObject">
<Description>A custom object node.Description>
<References>
<Reference ReferenceType="HasTypeDefinition">i=58Reference>
<Reference ReferenceType="Organizes" IsForward="false">i=30001Reference>
References>
UAObject>
<UAVariable NodeId="i=30004" BrowseName="MyXMLVariable" DataType="String">
<References>
<Reference ReferenceType="HasTypeDefinition">i=63Reference>
<Reference ReferenceType="Organizes" IsForward="false">i=30002Reference>
References>
<Value><uax:String>StringValueuax:String>Value>
UAVariable>
<UAVariable NodeId="i=30005" BrowseName="MyXMLProperty" DataType="UInt32">
<References>
<Reference ReferenceType="HasTypeDefinition">i=68Reference>
<Reference ReferenceType="HasProperty" IsForward="false">i=30002Reference>
References>
<Value><uax:UInt32>76uax:UInt32>Value>
UAVariable>
UANodeSet>
一个复杂的例子
创建一个自定义的对象类型节点,然后通过实例化该类型节点得到一个复杂的对象节点。
from opcua import Server
server = Server()
server.set_endpoint("opc.tcp://0.0.0.0:48400/freeopcua/server/") # 设定服务器URI
uri = 'http://examples.freeopcua.github.io'
idx = server.register_namespace(uri) # 注册地址空间
server.import_xml("test.xml") # 导入自定义的节点类型
my_sensor_type = server.get_root_node().get_child([
"0:Types", "0:ObjectTypes", "0:BaseObjectType", "0:TemperatureSensorType"]).nodeid
my_sensor = server.nodes.objects.add_object(idx, "TemperatureSensor", my_sensor_type)
server.start()
在XML文件中创建自定义的类型定义节点MyTemperatureSensorType
编程实例化该节点,得到地址空间结构如图:
<UANodeSet xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd"
xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<UAObjectType NodeId="i=30000" BrowseName="TemperatureSensorType">
<DisplayName>MyTemperatureSensorTypeDisplayName>
<Description>温度传感器类型Description>
<References>
<Reference ReferenceType="HasSubtype" IsForward="false">i=58Reference>
References>
UAObjectType>
<UAObject NodeId="i=30001" BrowseName="Configuration">
<Description>配置Description>
<References>
<Reference ReferenceType="HasTypeDefinition">i=58Reference>
<Reference ReferenceType="HasModellingRule">i=78Reference>
<Reference ReferenceType="HasComponent" IsForward="false">i=30000Reference>
References>
UAObject>
<UAVariable NodeId="i=30002" BrowseName="EngineeringUnit" ParentNodeId="i=30001" DataType="String">
<DisplayName>EngineeringUnitDisplayName>
<Description>温度工程单位Description>
<References>
<Reference ReferenceType="HasTypeDefinition">i=63Reference>
<Reference ReferenceType="HasModellingRule">i=78Reference>
<Reference ReferenceType="Organizes" IsForward="false">i=30001Reference>
References>
<Value><uax:String>℃uax:String>Value>
UAVariable>
<UAObject NodeId="i=30003" BrowseName="Measurement">
<Description>温度测试Description>
<References>
<Reference ReferenceType="HasTypeDefinition">i=58Reference>
<Reference ReferenceType="HasModellingRule">i=78Reference>
<Reference ReferenceType="HasComponent" IsForward="false">i=30000Reference>
References>
UAObject>
<UAVariable NodeId="i=30004" BrowseName="Temperature" ParentNodeId="i=30003" DataType="Float">
<DisplayName>TemperatureDisplayName>
<Description>温度Description>
<References>
<Reference ReferenceType="HasTypeDefinition">i=63Reference>
<Reference ReferenceType="HasModellingRule">i=78Reference>
<Reference ReferenceType="Organizes" IsForward="false">i=30003Reference>
References>
<Value><uax:Float>0.0uax:Float>Value>
UAVariable>
<UAVariable NodeId="i=30005" BrowseName="EngineeringUnit" ParentNodeId="i=30004" DataType="String">
<DisplayName>EngineeringUnitDisplayName>
<Description>温度工程单位Description>
<References>
<Reference ReferenceType="HasTypeDefinition">i=68Reference>
<Reference ReferenceType="HasModellingRule">i=78Reference>
<Reference ReferenceType="HasProperty" IsForward="false">i=30004Reference>
References>
<Value><uax:String>℃uax:String>Value>
UAVariable>
UANodeSet>