OpcUaHelper

一个通用的opc ua客户端类库,基于.net 4.6.1创建,基于官方opc ua基金会跨平台库创建,封装了节点读写,批量节点读写,引用读取,特性读取,历史数据读取,方法调用,节点订阅,批量订阅等操作。还提供了一个节点浏览器工具。

KEPServerEX

第三方的OPC服务器,各不同厂家多种设备下位PLC与上位机之间通讯。KEPServerEX是行业领先的通信平台,用于向您的所有应用程序提供单一来源的工业自动化数据。该平台的设计使用户能够通过一个直观的用户界面来连接、管理、监视和控制不同的自动化设备和软件应用程序。KEPServerEX 利用 OPC(自动化行业的互操作性标准)和以 IT 为中心的通信协议(如 SNMP、ODBC 和 Web 服务)为用户提供单一来源的工业数据。要开始使用,只需下载 KEPServerEX,然后从Kepware 的包含 150 多个设备驱动程序、客户端驱动程序和高级选项的库中进行选择,以满足您独特的工业控制系统需求。

以下基于开源的OpcUaHelper自带的demo,修改了界面,测试了部分功能。

KEPServerEX数据写入数据库案例 sql server数据库_c++

KEPServerEX数据写入数据库案例 sql server数据库_c++_02

主界面

C# 通过OPC UA 访问KEPServerEx6 演示视频

部分代码:

//同步读取
        private void button1_Click( object sender, EventArgs e )
        {
           
            if (cbo函数Nodeid.Text=="")
            {
                return;
            }
            DataValue dataValue = m_OpcUaClient.ReadNode( new NodeId(cbo函数Nodeid.Text ) );
            if (dataValue.WrappedValue.Value != null)
            {
                txtvalue.Text = dataValue.WrappedValue.Value.ToString();
            }
            //test();
        }
//单节点订阅
        private void button2_Click( object sender, EventArgs e )
        {
            // sub
            m_OpcUaClient.AddSubscription( "A", textBox4.Text, SubCallback );
        }
        //取消订阅 关键字A
        private void button3_Click( object sender, EventArgs e )
        {
            // remove sub
            m_OpcUaClient.RemoveSubscription( "A" );
        }
        //订阅回调
        private void SubCallback(string key, MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args )
        {
            if (InvokeRequired)
            {
                Invoke( new Action<string, MonitoredItem, MonitoredItemNotificationEventArgs>( SubCallback ), key, monitoredItem, args );
                return;
            }


            if (key == "A")
            {
                // 如果有多个的订阅值都关联了当前的方法,可以通过key和monitoredItem来区分
                MonitoredItemNotification notification = args.NotificationValue as MonitoredItemNotification;
                if (notification != null)
                {
                    textBox3.Text = notification.Value.WrappedValue.Value.ToString( );
                }
            }
            else if(key == "B")
            {
                // 需要区分出来每个不同的节点信息
                MonitoredItemNotification notification = args.NotificationValue as MonitoredItemNotification;
                if (monitoredItem.StartNodeId.ToString( ) == MonitorNodeTags[0])
                {
                    textBox5.Text = notification.Value.WrappedValue.Value.ToString( );
                }
                else if (monitoredItem.StartNodeId.ToString( ) == MonitorNodeTags[1])
                {
                    textBox9.Text = notification.Value.WrappedValue.Value.ToString( );
                }
                else if (monitoredItem.StartNodeId.ToString( ) == MonitorNodeTags[2])
                {
                    textBox10.Text = notification.Value.WrappedValue.Value.ToString( );
                }
            }
        }
// 缓存的批量订阅的节点
        private string[] MonitorNodeTags = null;
        //多节点订阅
        private void button5_Click( object sender, EventArgs e )
        {
            // 多个节点的订阅
            MonitorNodeTags = new string[]
            {
                textBox6.Text,
                textBox7.Text,
                textBox8.Text,
            };
            m_OpcUaClient.AddSubscription( "B", MonitorNodeTags, SubCallback );
        }
        //取消订阅    
        private void button4_Click( object sender, EventArgs e )
        {
            // 取消多个节点的订阅
            m_OpcUaClient.RemoveSubscription( "B" );//关键字 B
        }
//异步读取节点
        private async void buttonReadNodeAsync_Click(object sender, EventArgs e)
        {
            Opc.Ua.DataValue value = m_OpcUaClient.ReadNode(cboNodeid.Text);//SelectedItem.ToString()
            if (value.Value == null)
            {
                return;
            }
            // 一个数据的类型是不是数组由 value.WrappedValue.TypeInfo.ValueRank 来决定的
            // -1 说明是一个数值
            // 1  说明是一维数组,如果类型BuiltInType是Int32,那么实际是int[]
            // 2  说明是二维数组,如果类型BuiltInType是Int32,那么实际是int[,]
            if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.Int32)
            {
                if (value.WrappedValue.TypeInfo.ValueRank == -1)
                {
                    int temp = (int)value.WrappedValue.Value;               // 最终值
                    txtvalue.Text = temp.ToString();
                }
                else if (value.WrappedValue.TypeInfo.ValueRank == 1)
                {
                    int[] temp = (int[])value.WrappedValue.Value;           // 最终值
                    txtvalue.Text = temp.ToString();
                }
                else if (value.WrappedValue.TypeInfo.ValueRank == 2)
                {
                    int[,] temp = (int[,])value.WrappedValue.Value;         // 最终值
                    txtvalue.Text = temp.ToString();
                }
            }
            else if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.UInt32)
            {
                uint temp = (uint)value.WrappedValue.Value;                 // 数组的情况参照上面的例子
                txtvalue.Text = temp.ToString();
            }
            else if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.Float)
            {
                float temp = (float)value.WrappedValue.Value;               // 数组的情况参照上面的例子
                txtvalue.Text = temp.ToString();
            }
            else if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.String)
            {
                string temp = (string)value.WrappedValue.Value;             // 数组的情况参照上面的例子
                txtvalue.Text = temp.ToString();
            }
            else if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.DateTime)
            {
                DateTime temp = (DateTime)value.WrappedValue.Value;         // 数组的情况参照上面的例子
                txtvalue.Text = temp.ToString();
            }
            else if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.Int16)
            {
                short temp = (short)value.WrappedValue.Value;         // 数组的情况参照上面的例子
                txtvalue.Text = temp.ToString();
            }
            else if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.Double)
            {
                double temp = (double)value.WrappedValue.Value;         // 数组的情况参照上面的例子
                txtvalue.Text = temp.ToString();
            }
        }
//浏览节点引用
        private void buttonBrowseNodeReference_Click(object sender, EventArgs e)
        {
            richTextBox1.Clear();
            try
            {
                //ReferenceDescription[] references = m_OpcUaClient.BrowseNodeReference("ns=2;s=Devices.分厂一.车间二.ModbusTcp客户端");
                ReferenceDescription[] references = m_OpcUaClient.BrowseNodeReference(comboBox1.SelectedItem.ToString());
                richTextBox1.AppendText(string.Format("{0,-30}", "NodeClass")+ string.Format("{0,-30}","BrowseName")+ string.Format("{0,-40}","DisplayName")+ string.Format("{0,-40}","NodeId")+"\r\n");
                foreach (var item in references)
                {
                    Console.Write(string.Format("{0,-30}", item.NodeClass));
                    Console.Write(string.Format("{0,-30}", item.BrowseName));
                    Console.Write(string.Format("{0,-20}", item.DisplayName));
                    Console.WriteLine(string.Format("{0,-20}", item.NodeId.ToString()));
                    /*  string s1 = "中文";
                    string s2 = "语言学English";
                    Console.WriteLine("{0}{1}", padRightEx(s1, 20), "hello");
                    Console.WriteLine("{0}{1}", padRightEx(s2, 20), "hello");*/
                    //richTextBox1.AppendText(string.Format("{0,-30}", item.NodeClass));
                    //richTextBox1.AppendText(string.Format("{0,-30}", item.BrowseName));
                    //richTextBox1.AppendText(string.Format("{0,-20}", item.DisplayName));
                    //richTextBox1.AppendText(string.Format("{0,-20}", item.NodeId.ToString()) + "\r\n");
                    richTextBox1.AppendText(string.Format("{0}{1}{2}{3}\r\n", padRightEx(item.NodeClass.ToString().Trim(), 30), padRightEx(item.BrowseName.ToString().TrimStart(), 40), padRightEx(item.DisplayName.ToString().TrimStart(), 40), padRightEx(item.NodeId.ToString().TrimStart(), 100)));
                }


                ;
                // 输出如下
                //  NodeClass                     BrowseName                      DisplayName           NodeId


                //  Variable                      2:温度                          温度                  ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/温度
                //  Variable                      2:风俗                          风俗                  ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/风俗
                //  Variable                      2:转速                          转速                  ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/转速
                //  Variable                      2:机器人关节                    机器人关节            ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/机器人关节
                //  Variable                      2:cvsdf                         cvsdf                 ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/cvsdf
                //  Variable                      2:条码                          条码                  ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/条码
                //  Variable                      2:开关量                        开关量                ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/开关量
            }
            catch (Exception ex)
            {
                ClientUtils.HandleException(this.Text, ex);
            }
        }
//启用设备属性-操作模式-模拟  可读取节点属性
        private void buttonReadNoteAttributes_Click(object sender, EventArgs e)
        {
            richTextBox1.Clear();
            try
            {
                //OpcNodeAttribute[] nodeAttributes = m_OpcUaClient.ReadNoteAttributes("ns=2;s=Devices.分厂一.车间二.ModbusTcp客户端.温度");
                OpcNodeAttribute[] nodeAttributes = m_OpcUaClient.ReadNoteAttributes(cboNodeid.Text);
                richTextBox1.AppendText(string.Format("{0,-30}", "Name") + string.Format("{0,-40}", "Type") + string.Format("{0,-40}", "StatusCode") + string.Format("{0,-40}", "Value") + "\r\n");
                foreach (var item in nodeAttributes)
                {
                    //Console.Write(string.Format("{0,-30}", item.Name));
                    //Console.Write(string.Format("{0,-20}", item.Type));
                    //Console.Write(string.Format("{0,-20}", item.StatusCode));
                    //Console.WriteLine(string.Format("{0,20}", item.Value));
                    richTextBox1.AppendText(string.Format("{0}{1}{2}{3}\r\n", padRightEx(item.Name.ToString().Trim(), 30), padRightEx(item.Type.ToString().Trim(), 40), padRightEx(item.StatusCode.ToString().Trim(), 40), padRightEx(item.Value.ToString().Trim(), 100)));


                }
//写入操作
        private void buttonWrite_Click(object sender, EventArgs e)
        {
            try
            {
                // 此处演示写入一个short,2个float类型的数据批量写入操作。启用设备模拟后写入成功。但是很快就被模拟的0覆盖
                bool success = m_OpcUaClient.WriteNodes(new string[] {
                    "ns=2;s=Devices.分厂一.车间二.ModbusTcp客户端.温度",
                    "ns=2;s=Devices.分厂一.车间二.ModbusTcp客户端.风俗",
                    "ns=2;s=Devices.分厂一.车间二.ModbusTcp客户端.转速"},
                    new object[] {
                        (short)1234,
                        123.456f,
                        123f
                    });
                //bool success = m_OpcUaClient.WriteNode("ns=2;s=Devices.分厂一.车间二.ModbusTcp客户端.风俗", 123.456f);
                if (success)
                {
                    MessageBox.Show("写入成功!");
                }
                else
                {
                    // 写入失败,一个失败即为失败
                    MessageBox.Show("写入失败!");
                }
            }
            catch (Exception ex)
            {
                ClientUtils.HandleException(this.Text, ex);
            }
        }