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

news2024/12/25 0:10:11

 

         我们继续来研究如何使用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_ref
        root=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().Name
               
def Read_Input_Registers():
        global to_modbus_ref
        root=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_ref
        root=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_ref
        root=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 server
    server = 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 = 0
        while 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, etc
        server.stop()

上述代码会持续改进。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从不同视角绘制三维散点图

import numpy as np from matplotlib import pyplot as plt positive_data arr_feature_pca[y_dbscan_pred ! -1, :] negative_data arr_feature_pca[y_dbscan_pred -1, :] # --------------------------------------- 定义绘图函数 ----------------------------------- d…

华为云函数工作流FunctionGraph新手操作指南

函数工作流&#xff08;FunctionGraph&#xff09;是华为云提供的一款无服务器&#xff08;Serverless&#xff09;计算服务&#xff0c;无服务器计算是一种托管服务&#xff0c;服务提供商会实时为你分配充足的资源&#xff0c;而不需要预留专用的服务器或容量&#xff0c;真正…

CRM的哪些功能对企业最有用?

企业如何在竞争激烈的市场环境中&#xff0c;提高销售效率&#xff0c;管理客户关系&#xff0c;实现业绩增长&#xff1f;适合的CRM客户管理系统就可以帮助很多。Zoho CRM是一款SaaS云端CRM系统&#xff0c;它能够帮助企业管理客户关系&#xff0c;提高销售效率&#xff0c;获…

springboot集成camunda

1、相关软件下载Camunda流程引擎快速入门——Hello World示例 2、由于camunda-modeler最新版本为5.12.0.界面不太一样。 可以安装历史版本4.12.0camunda-bpm camunda-modeler等历史版本下载 3、汉化Camunda Modeler汉化添加简体中文和繁体中文支持 4、集成如何实现Springbootca…

Android Studio实现内容丰富的安卓高校评教系统

如需源码可以添加q-------3290510686&#xff0c;也有演示视频演示具体功能&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动。 项目编号114 1.开发环境 android stuido jdk1.8 eclipse mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登录 2.查看公告 3.查…

口语理解任务源码详解系列(一)数据集构建

口语理解任务源码详解系列&#xff08;一&#xff09;数据集构建 写在前面 本系列从零开始构建口语理解项目&#xff0c;整个项目分为意图分类与槽位填充两个子任务。项目采用的数据集为ATIS航空领域口语理解数据集&#xff0c;项目源码请传送到&#xff1a;github 一、处理数据…

网络编程1—— IP地址 + 端口号 +TCP/IP协议 + 协议分层的封装与应用

文章目录 前言一、网络发展各阶段二、网络通信的三大要素1.IP地址2.端口号3.网络协议 三、TCP/IP五层网络模型各层级的用处网络设备所在分层 四、封装和分用封装分用网络传输的实际情况 总结 前言 本人是一个刚刚上路的IT新兵,菜鸟!分享一点自己的见解,如果有错误的地方欢迎各…

搞懂推荐系统中的评价指标NDCG(CG、DCG、IDCG)

这些指标都是衡量搜索引擎算法的指标。搜索引擎一般采用PI&#xff08;peritem&#xff09;的方式进行评测&#xff0c;简单地说就是逐条对搜索结果进行分等级的打分。假设我们现在在Google上搜索一个词&#xff0c;然后得到5个结果。我们对这些结果进行3个等级的区分&#xff…

cmd可以用node但是vscode报错--node : 无法将“node”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。

原因&#xff1a;环境变量配置错误 解决&#xff1a; a.如果不想配置环境变量&#xff0c;可用管理员方式运行vscode后&#xff0c;重启编译器&#xff1a; 若以上方法不行&#xff0c;需要老老实实配置环境变量&#xff1a; b.配置 系统环境变量&#xff08;S&#xff09; 即…

机器学习20:嵌入-Embeddings

嵌入&#xff08;Embeddings&#xff09;是一个相对低维的空间&#xff0c;我们可以将高维向量转换到其中。嵌入使得对大型输入&#xff08;例如表示单词的稀疏向量&#xff09;进行机器学习变得更加容易。理想情况下&#xff0c;嵌入通过将语义相似的输入紧密地放置在嵌入空间…

高中生用台灯哪种好?盘点好用的高中生护眼台灯

随着科技的进步&#xff0c;台灯的外观和造型都开始不断的变化&#xff0c;而且台灯的功能也越来越多元化&#xff0c;各式各样的台灯都有。论哪种台灯最适合高中生使用&#xff0c;我的回答是护眼台灯&#xff01;因为台灯的主要作用就是照明&#xff0c;便于学习、阅读、工作…

基于Springboot+Vue的手机商城(源代码+数据库)081

基于SpringbootVue的手机商城(源代码数据库)081 一、系统介绍 本项目前后端分离&#xff08;该项目还有ssmvue版本&#xff09; 本系统分为管理员、用户两种角色 用户角色包含以下功能&#xff1a; 登录、注册、商品搜索、收藏、购物车、订单提交、评论、退款、收货地址管…

2023年5月 少儿编程 中国电子学会图形化编程等级考试Scratch编程一级真题解析(选择题)

2023年5月scratch编程等级考试一级真题 选择题(共25题,每题2分,共50分) 1、看图找规律,请问下图红框中是 A、 B、 C、 D、 答案:D 考点分析:

DS-font

paper:https://arxiv.org/pdf/2301.10008.pdf title: Few-shot Font Generation by Learning Style Difference and Similarity accepted: arXiv 2023 abstract 少镜头字体生成(FFG)旨在保留原始字符的底层全局结构,同时通过参考一些样本生成目标字体。它已应用于字体库创…

怎么解决找不到msvcp120.dll,msvcp120.dll一键修复方法

小伙伴们知道msvcp120.dll是什么文件吗?那么今天小编就来讲解电脑出现msvcp120.dll丢失的解决方法介绍&#xff0c;希望能够帮助到大家呢。 msvcp120.dll 是windows系统中必备的动态链接库文件。msvcp120.dll可以解决某些大型游戏、程序由于vs2010编译系统中缺失此dll的问题。…

R 语言的安装(详细教程)

文章目录 前言一、R 语言是什么&#xff1f;二、R 下载1. 官网2. download base3. download Rtools 三、Rstudio 下载1. 官网2. download Rstudio 四、R 安装五、Rtools 安装六、Rstudio 安装七、java 的环境配置八、运行 RStudio十、R 包安装策略1. 配置镜像1. 修改配置文件1.…

Microsoft遭遇DDoS攻击,3000万客户数据遭窃

6月初&#xff0c;微软部分服务遭遇严重中断&#xff0c;包括Outlook电子邮件、OneDrive文件共享应用程序和云计算基础设施Azure。 一个名为”匿名苏丹”的(又名“风暴-1359”)的组织声称对此次DDoS攻击负责。 匿名苏丹组织自2023年1月以来一直活动频繁&#xff0c;声称其目标…

SpinalHDL的使用和开发经验研讨会

SpinalHDL始于2014年&#xff0c;最初是作为VHDL/Verilog的替代而做的创新尝试&#xff0c;伴随着数年来开源硬件设计的蓬勃发展&#xff0c;基于开源技术的硬件设计方法和范式逐渐受到业界的关注。 达坦科技&#xff08;DatenLord&#xff09;致力于打造高性能跨云存储&#…

oracle 基础2

目录 1.oracle字符函数 2.oracle数值函数 3.Oracle日期函数 4.oracle转换函数 5.oracle多表实战 1.oracle字符函数 拼接 concat或 || 拼接三个参数 首字母大写函数 initcap 字符串改为小写 lower 转大写 upper 删除两边空格 trim 截取字符串 substr 替换字符串 repl…