使用python-opcua 实现modbus网关(2)

 

         我们继续来研究如何使用python-opcua 实现opcua/modbus 网关。 opcua 开发包包含了大量的函数,通过研究opcua/modbus 网关的实现,可以了解这些函数的使用方法。由于函数过多,文章中函数的使用方式可能不尽合理,或者存在错误。希望读者指正和讨论。

信息模型

 构建了两个模型,一个是motor ,另一个是modbus。motor 对象具有四个属性变量(Property):

  • 状态
  • 电流
  • 电压
  • 温度
  • 速度

modbus 对象有三类对象,它们分别是

  1. Coils
  2. inputRegisters
  3. holdingRegisters 

在它们的内部包含了一些modbus的变量地址。 而变量的长度是由对应的opcua 属性的datatype 确定的,例如 Float 是32位,对应modbus 两个register。

 

 OPCUA 信息模型与modbus 通过to_modbus 引用建立联系。它的反向名称是to_Property

信息模型的描述,编译

        使用前面博文介绍的方法,使用UA ModelCompiler 的Model.xml来描述,通过UA ModelCompiler 编译成NodeSet2 文档,由OPCUA Server 读入。你也可以使用uaModeler 来构建和生成NodeSet2 文档。

我使用UA Modelcompiler 方法

<?xml version="1.0" encoding="utf-8"?>
<ModelDesign xmlns:OpcUaModbus="http://www.maxim.org/Modbus/"xmlns:OpcUa="http://opcfoundation.org/UA/"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"TargetNamespace="http://www.maxim.org/Modbus/"TargetXmlNamespace="http://www.maxim.org/Modbus/"TargetVersion="1.00"TargetPublicationDate="2023-06-25T17:49:15"xmlns="http://opcfoundation.org/UA/ModelDesign.xsd"><Namespaces><Namespace Name="OpcUaModbus"Prefix="OpcUaModbus"XmlPrefix="OpcUaModbus">http://www.maxim.org/Modbus/</Namespace><Namespace Name="OpcUa"Version="1.03"PublicationDate="2013-12-02T00:00:00Z"Prefix="Opc.Ua"InternalPrefix="Opc.Ua.Server"XmlNamespace="http://opcfoundation.org/UA/2008/02/Types.xsd"XmlPrefix="OpcUa">http://opcfoundation.org/UA/</Namespace></Namespaces><ReferenceType SymbolicName="OpcUaModbus:To_Modbus"BaseType="OpcUa:HierarchicalReferences"><Description>modbus EndPoint</Description><InverseName>To_Property</InverseName></ReferenceType><Object SymbolicName="OpcUaModbus:Motor"TypeDefinition="OpcUa:BaseObjectType"><Children><Property SymbolicName="OpcUaModbus:Status"DataType="OpcUa:Boolean"><DefaultValue><uax:Boolean>true</uax:Boolean></DefaultValue><References><Reference IsInverse="false"><ReferenceType>OpcUaModbus:To_Modbus</ReferenceType><TargetId>OpcUaModbus:Device_Coils_Coil1</TargetId></Reference></References></Property><Property SymbolicName="OpcUaModbus:Current"DataType="OpcUa:Float"><DefaultValue><uax:Float>10</uax:Float></DefaultValue><References><Reference IsInverse="false"><ReferenceType>OpcUaModbus:To_Modbus</ReferenceType><TargetId>OpcUaModbus:Device_inputRegisters_inputRegister1</TargetId></Reference></References></Property><Property SymbolicName="OpcUaModbus:Voltage"DataType="OpcUa:Float"><DefaultValue><uax:Float>10</uax:Float></DefaultValue><References><Reference IsInverse="false"><ReferenceType>OpcUaModbus:To_Modbus</ReferenceType><TargetId>OpcUaModbus:Device_inputRegisters_inputRegister2</TargetId></Reference></References></Property><Property SymbolicName="OpcUaModbus:Temperature"DataType="OpcUa:Float"><DefaultValue><uax:Float>10</uax:Float></DefaultValue><References><Reference IsInverse="false"><ReferenceType>OpcUaModbus:To_Modbus</ReferenceType><TargetId>OpcUaModbus:Device_holdingRegisters_holdingRegister1</TargetId></Reference></References></Property><Property SymbolicName="OpcUaModbus:Speed"DataType="OpcUa:Int16"><DefaultValue><uax:Int16>10</uax:Int16></DefaultValue><References><Reference IsInverse="false"><ReferenceType>OpcUaModbus:To_Modbus</ReferenceType><TargetId>OpcUaModbus:Device_holdingRegisters_holdingRegister2</TargetId></Reference></References></Property></Children><References><Reference IsInverse="true"><ReferenceType>OpcUa:Organizes</ReferenceType><TargetId>OpcUa:ObjectsFolder</TargetId></Reference></References></Object><Object SymbolicName="OpcUaModbus:Device"TypeDefinition="OpcUa:BaseObjectType"><Children><Object SymbolicName="OpcUaModbus:Coils"TypeDefinition="OpcUa:FolderType"><Children><Property SymbolicName="OpcUaModbus:Coil1"DataType="OpcUa:UInt16"><DefaultValue><uax:String>4000</uax:String></DefaultValue></Property></Children></Object><Object SymbolicName="OpcUaModbus:holdingRegisters"TypeDefinition="OpcUa:FolderType"><Children><Property SymbolicName="OpcUaModbus:holdingRegister1"DataType="OpcUa:UInt16"><DefaultValue><uax:String>3000</uax:String></DefaultValue></Property><Property SymbolicName="OpcUaModbus:holdingRegister2"DataType="OpcUa:UInt16"><DefaultValue><uax:String>3002</uax:String></DefaultValue></Property></Children></Object><Object SymbolicName="OpcUaModbus:inputRegisters"TypeDefinition="OpcUa:FolderType"><Children><Property SymbolicName="OpcUaModbus:inputRegister1"DataType="OpcUa:UInt16"><DefaultValue><uax:String>5000</uax:String></DefaultValue><References><Reference IsInverse="true"><ReferenceType>OpcUaModbus:To_Modbus</ReferenceType><TargetId>OpcUaModbus:Motor_Current</TargetId></Reference></References></Property><Property SymbolicName="OpcUaModbus:inputRegister2"DataType="OpcUa:UInt16"><DefaultValue><uax:String>5002</uax:String></DefaultValue><References><Reference IsInverse="true"><ReferenceType>OpcUaModbus:To_Modbus</ReferenceType><TargetId>OpcUaModbus:Motor_Voltage</TargetId></Reference></References></Property></Children></Object></Children><References><Reference IsInverse="true"><ReferenceType>OpcUa:Organizes</ReferenceType><TargetId>OpcUa:ObjectsFolder</TargetId></Reference></References></Object>
</ModelDesign>

数据网关方式

实验项目的结构如下:

         modbusTCP 是一个简单的modbus设备仿真程序(比如·PLC),产生动态数据。 OpcUa/modbus Gayeway 通过modbusTCP 协议访问 modbusTCP Server,OpcUa Client或者uaExperty 通过OpcUa 访问OpcUa /modbus Gateway.

轮询数据的方法

轮询数据的方式分为两种:

按需读取(on Demand)

        当client  需要读取数据时,通过Opcua 协议发送 Read_Value()请求。在网关中,转换为modbusTCP 的Read_inputRegisters或者Read_holdingRegisters。Write_Value 也是类似的方式,这种方式是同步访问方式(sync access)

轮询方式(Cycle polling)

按照一定的周期轮询modbusTCP Server 的数据。轮询程序的位置可以放置在两个地方

  1. Gateway端

     Gateway中有一个定时器轮询modbusTCP server 的数据,存放到OpcUa 的信息模型中。OPC UA Client 异步的方式访问Gateway中的信息模型中的数据。

  1. Client端

 在OpcUa 的Client 端轮询。这类似与按需存取,是一种同步方式。

在实验项目中,我们采取Gateway 端的轮询方法。

 Python 实现的要点

读取Holding 寄存器(Read_Holding_Registers)

def Read_Holding_Registers():global to_modbus_refroot=server.get_root_node()holdingRegisters=root.get_child(["0:Objects", "2:Device", "2:holdingRegisters"])Childrens=holdingRegisters.get_children()for children in Childrens:address=children.get_value()reg_l=ModbusInterface.read_input_registers(int(address),2)val=utils.word_list_to_long(reg_l)value=utils.decode_ieee(val[0],False)OpcUa_Property=children.get_referenced_nodes(to_modbus_ref,ua.BrowseDirection.Inverse,0,True)OpcUa_Property[0].set_value(value)

 step1  找到holding_register 节点,

holdingRegisters=root.get_child(["0:Objects", "2:Device", "2:holdingRegisters"])

step 找出holding_registers 目录下的所有holdingRegister 这些寄存器的值是该寄存器地址。这里数据为Float 对应两个modbus register。

holding_register1 3000

holding_register2 3002

Step 3 读取所有holding register的值

 address=children.get_value()reg_l=ModbusInterface.read_input_registers(int(address),2)

Step 4读出来的值是两个16位int,转换位Float

 val=utils.word_list_to_long(reg_l)value=utils.decode_ieee(val[0],False)

Step5 通过to_modbus_ref 引用找到对应的Node ,并且设置值

OpcUa_Property=children.get_referenced_nodes(to_modbus_ref,ua.BrowseDirection.Inverse,0,True)
OpcUa_Property[0].set_value(value)

改变数据通知(datachange_notification)

        当Client 写入Property 值时,需要将该值写入modbusTCP Server 。在Open62541 中,有BeforeRead和AfterWrite 函数,在Python-opcua 中,是通过建立一个子处理(subHandler) 来响应数据的改变。

        下面这一段程序监控 Temperature,当其值改变时,会调用 datachange_notification的方法。这里我们做了一些简化,没有判断Coils 的情形。

   class SubHandler(object):def datachange_notification(self, node, val, data):print("Python: New data change event", node, val)modbusEndpoint=node.get_referenced_nodes(to_modbus_ref,ua.BrowseDirection.Forward,0,True)print(modbusEndpoint)Address=modbusEndpoint[0].get_value#parentNode=modbusEndpoint[0].get_parent()#parentNodeName=parentNode.get_browse_name().Name  b32_l=[utils.encode_ieee(val,False)]regs_value = utils.long_list_to_word(b32_l)ModbusInterface.write_multiple_registers(Address, regs_value)#print(parentNode.get_browse_name().Name)pass
........server.start()  handler = SubHandler()sub = server.create_subscription(100, handler)handle = sub.subscribe_data_change(get_Property_By_Name("2:Temperature"))

完整的程序

import sys
sys.path.insert(0, "..")
import time
from opcua import  ua,Server
from pyModbusTCP.client import ModbusClient # Modbus TCP Client
from pyModbusTCP import utils
class SubHandler(object):def datachange_notification(self, node, val, data):print("Python: New data change event", node, val)modbusEndpoint=node.get_referenced_nodes(to_modbus_ref,ua.BrowseDirection.Forward,0,True)print(modbusEndpoint)Address=modbusEndpoint[0].get_value#parentNode=modbusEndpoint[0].get_parent()#parentNodeName=parentNode.get_browse_name().Name  b32_l=[utils.encode_ieee(val,False)]regs_value = utils.long_list_to_word(b32_l)ModbusInterface.write_multiple_registers(Address, regs_value)#print(parentNode.get_browse_name().Name)pass
def get_Property_By_Name(Name):root=server.get_root_node()Property=root.get_child(["0:Objects", "2:Motor",Name])print(Property.get_browse_name())return   Property def get_referenced_Type_By_Name(Name):root=server.get_root_node()ReferenceType=root.get_child(["0:Types", "0:ReferenceTypes", "0:References","0:HierarchicalReferences",Name])return   ReferenceType 
def get_Property_DataType(Property):DataTypeNodeId=Property.get_data_type()return server.get_node(DataTypeNodeId).get_browse_name().Namedef Read_Input_Registers():global to_modbus_refroot=server.get_root_node()inputRegisters=root.get_child(["0:Objects", "2:Device", "2:inputRegisters"])Childrens=inputRegisters.get_children()for children in Childrens:OpcUa_Property=children.get_referenced_nodes(to_modbus_ref,ua.BrowseDirection.Inverse,0,True)DataType=get_Property_DataType(OpcUa_Property[0])print(DataType)address=children.get_value()#print(address)reg_l=ModbusInterface.read_input_registers(int(address),2)val=utils.word_list_to_long(reg_l)value=utils.decode_ieee(val[0],False)#print(to_modbus_ref)#print(children.get_browse_name())OpcUa_Property[0].set_value(value)#print(OpcUa_Property[0].get_browse_name())
def Read_Holding_Registers():global to_modbus_refroot=server.get_root_node()holdingRegisters=root.get_child(["0:Objects", "2:Device", "2:holdingRegisters"])Childrens=holdingRegisters.get_children()for children in Childrens:address=children.get_value()reg_l=ModbusInterface.read_input_registers(int(address),2)val=utils.word_list_to_long(reg_l)value=utils.decode_ieee(val[0],False)OpcUa_Property=children.get_referenced_nodes(to_modbus_ref,ua.BrowseDirection.Inverse,0,True)OpcUa_Property[0].set_value(value)
def Read_Coils():global to_modbus_refroot=server.get_root_node()Coils=root.get_child(["0:Objects", "2:Device", "2:Coils"])Childrens=Coils.get_children()for children in Childrens:address=children.get_value()val=ModbusInterface.read_coils(int(address),1)       OpcUa_Property=children.get_referenced_nodes(to_modbus_ref,ua.BrowseDirection.Inverse,0,True)OpcUa_Property[0].set_value(val)
if __name__ == "__main__":# setup our serverserver = Server()server.set_endpoint("opc.tcp://127.0.0.1:48400/freeopcua/server/")server.import_xml("OpcUaModbus.NodeSet2.xml")to_modbus_ref=get_referenced_Type_By_Name("2:To_Modbus")#print(to_modbus_ref)# get Objects node, this is where we should put our nodes#objects = server.get_objects_node() ModbusInterface = ModbusClient(host="localhost", port=502, unit_id=1, auto_open=True, auto_close=False) CurrebtNode=get_Property_By_Name("2:Current")CurrebtNode.set_writable()VoltageNode=get_Property_By_Name("2:Voltage")VoltageNode.set_writable()VoltageNode=get_Property_By_Name("2:Temperature")VoltageNode.set_writable()# starting!server.start()  handler = SubHandler()sub = server.create_subscription(100, handler)handle = sub.subscribe_data_change(get_Property_By_Name("2:Temperature"))try:count = 0while True:time.sleep(1)Read_Input_Registers()#reg_l=ModbusInterface.read_input_registers(0,2)#val=utils.word_list_to_long(reg_l)#print(utils.decode_ieee(val[0],False)) finally:#close connection, remove subcsriptions, etcserver.stop()

上述代码会持续改进。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/8298.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 串口工具minicom

Linux minicom Linux中的Minicom是一个串口通信工具&#xff0c;用于与外部设备进行串口通信。它可以用于与嵌入式设备、调试设备、网络设备等进行通信和配置。 调试和配置串口设备&#xff1a;minicom可以用于连接和调试各种串口设备&#xff0c;如调制解调器、路由器、交换…

飞控学习笔记-飞行器数学模型(2)

十字型模型 旋翼动力学 动力模型 电机模型 模型仿真 升力模型 力矩模型 反扭力 仿真

基于.Net Core微服务-第1章:说明及技术栈

微服务是一种架构模式&#xff0c;提倡将单一应用程序划分为一组小的服务&#xff0c;服务相互协调、互相配合&#xff0c;为用户提供最终价值。

蓝桥杯专题-真题版含答案-【猜年龄】【逆波兰表达式】【三部排序】【核桃的数量】

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

2023亚马逊云科技中国峰会之Serverless

序言 Amazon Web Services&#xff0c;是Amazon.com推出的一系列云计算服务。 它提供了一系列的基础设施服务、平台服务和软件服务&#xff0c;希望可以帮助我们更轻松地构建和管理基于云的应用程序。 今天来学习一下 Serverless 本文会介绍以下六个模块&#xff1a; 为什么会…

2023.7.4总结:HCIP中rip实验

目录 一、题目要求&#xff1a; 二、实验思路&#xff1a; 第一步&#xff1a;划分ip如下&#xff1a;配置本地环回以及端口ip&#xff0c;创建R1环回 172.16.1.1/24 172.16.2.1/24172.16.3.1/24&#xff0c;以及端口上的rip协议 第二步&#xff1a;要求R3使用R2访问R1环回&a…

python爬虫_正则表达式获取天气预报并用echarts折线图显示

文章目录 ⭐前言⭐python re库&#x1f496; re.match函数&#x1f496; re.search函数&#x1f496; re.compile 函数 ⭐正则获取天气预报&#x1f496; 正则实现页面内容提取&#x1f496; echarts的天气折现图 ⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分…

MySQL之数据库引擎详解(内附面试题:InnoDB和MyISAM的联系与区别)

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于MySQL数据库引擎的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一. 数据库引擎是什么&#xff…

问诊住院医疗业务数仓建模实操案例

一、数仓建模实超案例 &#xff08;一&#xff09;前言 医疗业务系统比较复杂&#xff0c;有HIS&#xff1a;医院信息管理系统&#xff08; Hospital Information System&#xff09;、CIS&#xff1a;临床信息系统&#xff08;Clinical Information System&#xff09;、LIS&…

图像处理常用算法(基础)

同图像灰度不同,边界处一般会有明显的边缘,利用此特征可以分割图像。需要说明的是:边缘和物体间的边界并不等同,边缘指的是图像中像素的值有突变的地方,而物体间的边界指的是现实场景中的存在于物体之间的边界。有可能有边缘的地方并非边界,也有可能边界的地方并无边缘,…

13---罗马数字转整数

罗马数字包含以下七种字符: I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如&#xf…

docker进阶

Docker网络 [rootecs-56325218 ~]# docker network ls NETWORK ID NAME DRIVER SCOPE 2c63c1a8145c bridge bridge local 70d3439bbb55 host host local ffc74cf89143 none null local[rootecs-56325218 ~]# docker network cre…