技术论坛

 OPC UA网关(服务)程序附完整代码

返回主题列表
作者 主题
zunzhi
侠圣

经验值:2499
发帖数:113
精华帖:5
楼主    2022-08-18 19:02:58
主题:OPC UA网关(服务)程序附完整代码 精华帖 

网上OPC UA服务端的介绍非常少,网关的更是找不到,找到的几乎都是收费的,最近静下心把OPC基金会的代码学习了一遍,结合一些大牛的文章,写了一个简单的OPC UA网关。目前网关的设备只支持西门子,因为只有西门子的仿真器足够强大(S7协议可以仿真),后续根据条件会加上三菱、欧姆龙等等。人过中年,总想踏踏实实做点事情,如果有好的工作平台希望大家能介绍一下,感谢!

OPC是应用于工业通信的,在windows环境的下一种通讯技术,原有的通信技术难以满足日益复杂的环境,在可扩展性,安全性,跨平台性方面的不足日益明显,所以OPC基金会在几年前提出了面向未来的架构设计的OPC 统一架构,简称OPC UA,截止目前为止,越来越多公司将OPC UA作为开放的数据标准.

OPC UA 基于Web服务(windows的WCF)开发的,同时又具有MQTT 可订阅的特性,部署方便、使用简单,容易被大家广泛的接受,于是随着工业4.0的发展逐渐普及

一、测试平台搭建

1.软件配置

仿真采用:S7-PLCSIM Advanced V4.0 SP1

编程采用:TIA V15.1

2.编写简单程序,提供基础变量,如布尔,整型,实数等供测试



3.仿真注意事项

1)PLC组态地址,仿真PLC地址必须和虚拟机一个网段





2)项目属性要选择块编译时支持仿真、数据块优化项去掉,通信属性中勾选允许远程PUT/GET



二.OPC UA网关架构流程



三、OPC UA 编译环境

编译软件:Visual Studio 2022

编译框架:Microsoft .NET Framework 4.6.2


四、OPC UA 网关主要部分

  1. 节点配置

  2. 服务配置

  3. PLC变量监控,读取和写入

部分代码展示

//节点的类

        public class OpcuaNode

        {

            //节点名称

            public string NodeName { get; set; }

            //父节点

            public string ParentNode { get; set; }

            //节点数据类型

            public string DataType { get; set; }

            //节点类型

            public string NodeType { get; set; }

            //节点的值

            public string NodeValue { get; set; }

            //节点的地址

            public string Adress { get; set; }

        }

OPC UA的主要数据类型,注意和PLC变量对应


-<Aliases>


<Alias Alias="Boolean">i=1</Alias>


<Alias Alias="SByte">i=2</Alias>


<Alias Alias="Byte">i=3</Alias>


<Alias Alias="Int16">i=4</Alias>


<Alias Alias="UInt16">i=5</Alias>


<Alias Alias="Int32">i=6</Alias>


<Alias Alias="UInt32">i=7</Alias>


<Alias Alias="Int64">i=8</Alias>


<Alias Alias="UInt64">i=9</Alias>


<Alias Alias="Float">i=10</Alias>


<Alias Alias="Double">i=11</Alias>


<Alias Alias="DateTime">i=13</Alias>


<Alias Alias="String">i=12</Alias>

</Aliases>

读取节点配置文件加载到树形控件,同时给节点类赋值,详细见附件源码

             connectPlc();

                //判断PLC连接状态

                if (lianjie == 1&&server==null)

                {

                    //清空节点列表

                    opcuaNodes.Clear();

                    //清空列表

                    treeView1.Nodes.Clear();

                    //加载文件

                    doc.Load(xmlpath);

                    //遍历节点

                    RecursionTreeControl1(doc.DocumentElement, treeView1.Nodes);

                    //展开视图

                    treeView1.ExpandAll();

服务启动初始化,读取服务配置,包括服务地址、端口、证书、签名或密钥的存放路径,是否启用匿名登录、是否启用非安全登录等等,详细见附件源码

 //声明应用实例

        ApplicationInstance application;

        //声明服务

        ServerBase server;

        //声明服务配置

        ApplicationConfiguration config;

        private void UaServerInit()

        {

            try

            {

                // Initialize the user interface.

                Application.EnableVisualStyles();

                ApplicationInstance.MessageDlg = new ApplicationMessageDlg();

                application = new ApplicationInstance();

                application.ApplicationType = ApplicationType.Server;

                application.ConfigSectionName = "MyOPC.UA.Server";

                //读取服务配置

                config = application.LoadApplicationConfiguration(false).Result;

                //读取服务地址和端口

                textBox1.Text = config.ServerConfiguration.BaseAddresses.ElementAt(1).ToString();


            }

            catch (Exception ex)

            {

                ExceptionDlg.Show(application.ApplicationName, ex);

            }

        }

PLC 变量监控分读和写2个部分,读是开一个定时器循环读取PLC的变量的值,不断刷新,写是绑定OPC UA的一个变量写入事件,写入动作时才会触发,具体见附件源码

定时读取

//CreateAddressSpace中开启定时器,读PLC               

 m_simulationTimer = new Timer(DoSimulation, null, 1000, 1000);

//定时读取PLC并更新数据

        private void DoSimulation(object state)

        {

            try

            {

                lock (Lock)

                {

                    var timeStamp = DateTime.UtcNow;

                    foreach (BaseDataVariableState variable in m_dynamicNodes)

                    {

                        variable.Value = GetNewValue(variable);

                        variable.Timestamp = timeStamp;

                        variable.ClearChangeMasks(SystemContext, false);

                    }

                }

            }

            catch (Exception e)

            {

                Utils.LogError(e, "Unexpected error doing simulation.");

            }

        }

  //读取PLC

        private object GetNewValue(BaseVariableState variable)

        {

            object value = null;

                //判断变量地址是否为空

                if (variable.Des cription.Text.Length > 0)

                {

                    //实数处理

                    //if (variable.DataType == DataTypeIds.Float)

                    if (variable.DataType == DataTypeIds.Float variable.DataType == DataTypeIds.Double)


                    {

                        value = Form1.plc.Read(variable.Des cription.Text);

                        var buffer = new byte[4];

                        buffer[3] = (byte)((uint)value >> 24);

                        buffer[2] = (byte)((uint)value >> 16);

                        buffer[1] = (byte)((uint)value >> 8);

                        buffer[0] = (byte)((uint)value >> 0);

                        value = (BitConverter.ToSingle(buffer, 0)).ToString();

                        return value;

                    }

                    //字符串处理

                    //if (variable.DataType == DataTypeIds.Float)

                    else if (variable.DataType == DataTypeIds.String)


                    {

                        int tt = variable.Des cription.Text.IndexOf(".");

                        int  dbnum = int.Parse(variable.Des cription.Text.Substring(2,1));

                        int  dbcount = int.Parse(variable.Des cription.Text.Substring(tt+4));

                        var count = (byte)Form1.plc.Read(DataType.DataBlock, dbnum, dbcount, VarType.Byte, 1);

                        value = Form1.plc.Read(DataType.DataBlock, dbnum, dbcount + 1, VarType.String, count+1).ToString().Substring(1);

                        return value;

                    }

                    //字符处理

                     else if (variable.DataType == DataTypeIds.ByteString)

                    {

                        value = Form1.plc.Read(variable.Des cription.Text);

                        byte[] array = new byte[1];

                        array[0] = (byte)(Convert.ToInt32(value)); //ASCII码强制转换二进制

                        value = Convert.ToString(System.Text.Encoding.ASCII.GetString(array));//str为ASCII码对应的字符

                        return value;

                    }

                    else

                    {

                        //读取变量

                        value = Form1.plc.Read(variable.Des cription.Text);

                        return value;

                    }

                }

                else

                {

                    value = null;

                }

                // skip Variant Null

                if (value is Variant variant)

                {

                    if (variant.Value == null)

                    {

                        value = null;

                    }

                }

            return value;

        }

写的事件绑定

/// <summary>

        /// Creates a new variable.

        /// </summary>

        private BaseDataVariableState CreateVariable(NodeState parent, string path, string name, NodeId dataType, int valueRank)

        {

          

            //绑定客户端写入事件

            variable.OnWriteValue = ToWritePLC;

            

        }

具体见附件吧,已经超出字数限制了!

为了数据的安全,防止误操作,我加了一个写入保护开关,需要时开启


五、运行测试,读写正常




努力工作,好好学习!
post_jingbian_commit.aspx
您收到0封站内信:
×
×
信息提示
很抱歉!您所访问的页面不存在,或网址发生了变化,请稍后再试。