C#学习记录——【实例】C#实现OPC Client

news2024/12/26 21:49:23

最近学习测试用C#开发OPC客户端连接OPC服务器,防止遗忘,记录学习测试结果。

1、OPC基础知识

1.1、OPC概述

OPC是Object Linking and Embedding(OLE)for Process Control 的缩写,它是微软公司的对象链接和嵌入技术在过程控制方面的应用。OPC以OLE/COM/DCOM技术为基础,采用客户/服务器模式,为工业自动化软件面向对象的开发提供了统一的标准,这个标准定义了应用Microsoft操作系统在基于PC的客户机之间交换自动化实时数据的方法,采用这项标准后,硬件开发商将取代软件开发商为自己的硬件产品开发统一的OPC接口程序,而软件开发者可免除开发驱动程序的工作,充分发挥自己的特长,把更多的精力投入到其核心产品的开发上。

1.2、OPC读写方式

在实际使用中,主要包括对现场数据的读写操作。

OPC读数有三种方式:同步、异步、订阅。

同步通讯时,OPC客户程序向OPC服务器进行请求时,OPC客户程序必须等到OPC服务器对应的响应全部完成以后才能返回,在此期间OPC客户程序一直处于等待状态,若进行读操作,那么必须等待OPC服务器响应后才返回。因此在同步通讯时,如果有大量数据进行操作或者有很多OPC客户程序对OPC服务器进行读操作,必然造成OPC客户程序的阻塞现象。因此同步通讯适用于OPC客户程序较少,数据量较小时的场合。
在这里插入图片描述
异步通讯时,OPC客户程序对服务器进行请求时,OPC客户程序请求后立刻返回,不用等待OPC服务器的响应,可以进行其它操作。OPC服务器完成响应后再通知OPC客户程序,如进行读操作,OPC客户程序通知OPC服务器后离开返回,不等待OPC服务器的读完成,而OPC服务器完成读后,会自动的通知OPC客户程序,把读结果传送给OPC客户程序。因此相对于同步通讯,异步通讯的效率更高。
在这里插入图片描述
订阅方式时,OPC客户程序对服务器进行请求时,OPC客户程序操作后立刻返回,不用等待OPC服务器的操作,可以进行其它操作, OPC 服务器的Group组在组内有数据发生改变时,自动根据更新周期刷新相应的客户端数据,如下图,客户端只向OPC服务发送一次请求,之后不再对服务器请求。
在这里插入图片描述
OPC写数有两种方式:同步、异步。区别与上面讲的机制一样,在生产应用中,如果写数据参与控制,一般采用同步方式。

OPC访问接口方式:

OPC主要包含两种接口:CUSTOM标准接口和OLE自动化标准接口,自定义接口是服务商必须提供的,而自动化接口则是可选的。

自定义接口是一组COM接口,主要用于采用C++语言的应用程序开发;

自动化接口是一组OLE接口,主要用于采用VB,DELPHI,Excel等基于脚本编程语言的应用程序开发。

在这里插入图片描述

2、OPC客户端接口方式

开发OPC客户端程序,其访问接口方式有多种,根据官方提供的资料大约有如下几种方式:

1>、使用OPCNetAPI,需要用到OPCNetAPI.dll,OPCNetAPI.Com.dll

2>、使用自动化接口,需要用到OPCDAAuto.dll

3>、使用自定义接口,需要用到多个Wrapper:OpcRcw.Ae.dll,OpcRcw.Batch.dll,OpcRcw.Comn.dll,OpcRcw.Da.dll,OpcRcw.Dx.dll,OpcRcw.Hda.dll,OpcRcw.Sec.dll

对于像C++这样的语言来开发OPC客户端时,一般需要使用自定义接口的方式。而如果采用VB和C#这样的语言来开发OPC客户端时,一般就采用自动化接口。要使用OPC自动化接口,首先要引用OPCDAAuto.dll文件,并在开发环境中做好相关的引用配置。

3、自动化接口简介

自动化接口是OPC基金会组织为了方便并统一OPC客户端开发而发布的一个接口、属性和方法的协议集。

自动化接口中共定义了6类对象:OPCServer对象、OPCBrowser对象、OPCGroups对象、OPCGroup对象、OPCItems对象、OPCItem对象。接下来简要描述一下这些对象的主要功能。

3.1、OPCServer对象

由客户端创建的OPCServer自动化对象。然后客户端通过其方法实现连接到OPC数据访问自定义接口。OPCServer对象现在可以用来获取关于OPC服务器的一般信息,并创建和操作OPCGroup对象的集合。

主要的属性:

StartTimeCurrentTimeLastUpdateTime
MajorVersionMinorVersionBuildNumber
VendorInfoServerStateLocaleID
BandwidthOPCGroupsPublicGroupNames
ServerNameServerNodeClientName

主要的方法:

GetOPCServersConnectDisconnect
CreateBrowserGetErrorStringQueryAvailableLocaleIDs
QueryAvailablePropertiesGetItemPropertiesLookupItemIDs

3.2、OPCBrowser对象

OPCBrowser对象是在服务器中存在的分支或项目名称的集合。其是可选的。如果服务器不支持,将不会创建这个对象。

主要的属性:

OrganizationFilterDataType
AccessRightsCurrentPositionCount

主要的方法:

ItemShowBranchesShowLeafs
MoveUpMoveToRootMoveDown
MoveToGetItemIDGetAccessPaths

3.3、OPCGroups对象

OPCGroups是OPCGroup对象的集合,以及创建、删除和管理它们的方法。

该对象还具有OPCGroup默认属性。当添加OPCGroups时,DefaultGroupXXXX属性设置其初始状态。可以更改默认值,以添加具有不同初始状态的opc组。更改默认值并不会影响已经创建的组。添加OPCGroup后,它的属性可以被修改。这减少了调用Add方法所需的参数数量。

主要的属性:

ParentDefaultGroupIsActiveDefaultGroupUpdateRate
DefaultGroupDeadbandDefaultGroupLocaleIDDefaultGroupTimeBias
Count

主要的方法:

ItemAddGetOPCGroup
RemoveRemoveAllConnectPublicGroup
RemovePublicGroup

3.4、OPCGroup对象

OPC组为客户组织数据提供了一种方式。例如,组可能表示特定操作符显示或报告中的项。数据可以读写。基于异常的连接也可以在客户端和组中的项之间创建,可以根据需要启用和禁用。OPC客户机可以配置OPC服务器应该向OPC客户机提供数据更改的速率。

主要的属性:

ParentNameIsPublic
IsActiveIsSubscribedClientHandle
ServerHandleLocaleIDTimeBias
DeadBandUpdateRateOPCItems

主要的方法:

SyncReadSyncWriteAsyncRead
AsyncWriteAsyncRefreshAsyncCancel

3.5、OPCItems对象

这个对象还具有OPCItem默认的属性。当添加OPCItem时,DefaultXXXX属性设置其初始状态。可以更改默认值,以添加具有不同初始状态的OPCItems。当然,一旦添加了OPCItem,它的属性可以被修改。这减少了调用Add方法所需的参数数量。

主要的属性:

ParentDefaultRequestedDataTypeDefaultAccessPath
DefaultIsActiveCount

主要的方法:

ItemGetOPCItemAddItem
AddItemsRemoveValidate
SetActiveSetClientHandlesSetDataTypes

3.6、OPCItem对象

OPC项表示与服务器中的数据源的连接。与每个项目相关联的是一个值,质量和时间戳。值以变量的形式出现,质量类似于Fieldbus指定的值。

主要的属性:

ParentClientHandleServerHandle
AccessPathAccessRightsItemID
IsActiveRequestedDataTypeValue
QualityTimeStampCanonicalDataType
EUTypeEUInfo

主要的方法:

ReadWrite

4、OPC Client的开发

4.1、OPC Client 访问OPC 服务器的步骤

1、 连接 OPC 服务器
2、 创建 Group 到连接
3、遍历OPC服务器的所有的leaf item, 添加需要的 leaf item 到 Group
4、 现在可以
读取 item 的值
监听 Group 的 DataChange 事件,获取 item 的最新值
调用 Group 的方法异步修改 item 的值
调用 item 的方法同步修改值

4.2、代码示例

4.2.1、代码示例1

首先,封装一斜对象、用于存取相关的属性,如:OPC服务器信息(OPCServerInfo)、OPC数据项(OPCDataItem)、组属性(GroupProperty)等。对于组属性我们还需要赋予默认值。代码如下:

public class OPCServerInfo
    {
        public DateTime StartTime { get; set; }
        public string ServerVersion { get; set; }
    }
    public class OPCDataItem
    {
        public object ItemName { get; set; }
        public object ItemValue { get; set; }
        public object Quality { get; set; }
        public object TimeStamp { get; set; }
    }

    public class GroupProperty
    {
        public bool DefaultGroupIsActive { get; set; }
        public float DefaultGroupDeadband { get; set; }
        public int UpdateRate { get; set; }
        public bool IsActive { get; set; }
        public bool IsSubscribed { get; set; }
        public GroupProperty()
        {
            DefaultGroupIsActive = true;
            DefaultGroupDeadband = 0;
            UpdateRate = 250;
            IsActive = true;
            IsSubscribed = true;
        }
    }

接下来,为了使用方便我们封装了一个ClientHelper类用于实现相关的操作,应为在一个客户端应用中,该对象是唯一的我们为了使用方便将其声明为静态类,以便于使用。具体代码如下:

public class ClientHelper
    {
        /// <summary>
        /// 获取可以使用的OPC服务器
        /// </summary>
        /// <param name="hostName">获取OPC服务器的主机名称</param>
        /// <returns>返回OPC服务器列表</returns>
        public static List<string> GetOPCServerName(string hostName)
        {
            try
            {
                OPCServer OpcServer = new OPCServer();
                object opcServers = OpcServer.GetOPCServers(hostName);
                List<string> serverList = new List<string>();
                foreach (string opcServer in (Array)opcServers)
                {
                    serverList.Add(opcServer);
                }
                return serverList;
            }
            catch(Exception ex)
            {
                throw ex;
            }
        }
        /// <summary>
        /// 连接到指定的OPC服务器
        /// </summary>
        /// <param name="serverName">服务器名称</param>
        /// <param name="serverIP">服务器IP</param>
        /// <returns>返回的OPC服务器</returns>
        public static OPCServer ConnectToServer(string serverName, string serverIP)
        {
            OPCServer opcServer = new OPCServer();
            try
            {
                opcServer.Connect(serverName, serverIP);
                if (opcServer.ServerState != (int)OPCServerState.OPCRunning)
                {
                    opcServer.Disconnect();
                    return null;
                }
            }
            catch
            {
                opcServer.Disconnect();
                return null;
            }
            return opcServer;
        }
        /// <summary>
        /// 获取OPC服务器的相关信息
        /// </summary>
        /// <param name="opcServer">OPC服务器对象</param>
        /// <returns>OPC服务器信息</returns>
        public static OPCServerInfo GetServerInfo(OPCServer opcServer)
        {
            OPCServerInfo serverInfo = new OPCServerInfo();
            serverInfo.StartTime=opcServer.StartTime;
            serverInfo.ServerVersion = opcServer.MajorVersion.ToString() + "." + opcServer.MinorVersion.ToString() + "." + opcServer.BuildNumber.ToString();
            return serverInfo;
        }
        /// <summary>
        /// 展开OPC服务器的节点
        /// </summary>
        /// <param name="opcServer">OPC服务器</param>
        /// <returns>返回展开后的节点数据</returns>
        public static OPCBrowser RecurBrowse(OPCServer opcServer)
        {
            OPCBrowser opcBrowser = opcServer.CreateBrowser();
            //展开分支
            opcBrowser.ShowBranches();
            //展开叶子
            opcBrowser.ShowLeafs(true);
            return opcBrowser;
        }
        public static OPCGroup CreateGroup(OPCServer opcServer, OPCItems opcItems, string opcGroupName, GroupProperty groupProperty)
        {
            try
            {
                OPCGroup opcGroup = opcServer.OPCGroups.Add(opcGroupName);
                opcServer.OPCGroups.DefaultGroupIsActive = groupProperty.DefaultGroupIsActive;
                opcServer.OPCGroups.DefaultGroupDeadband = groupProperty.DefaultGroupDeadband;
                opcGroup.UpdateRate = groupProperty.UpdateRate;
                opcGroup.IsActive = groupProperty.IsActive;
                opcGroup.IsSubscribed = groupProperty.IsSubscribed;
                //opcGroup.DataChange += new DIOPCGroupEvent_DataChangeEventHandler(OpcGroupDataChange);
                //opcGroup.AsyncWriteComplete += new DIOPCGroupEvent_AsyncWriteCompleteEventHandler(KepGroup_AsyncWriteComplete);
                //opcItems = opcGroup.OPCItems;
                return opcGroup;
            }
            catch (Exception err)
            {
                throw err;
            }
        }
    }

4.2.2、代码示例2

C#自动化接口

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using OPCSiemensDAAutomation;//引用连接库

第一步,添加下列命名空间(首先在COM组件中添加相应组件)

using OPCSiemensDAAutomation;

第二步,定义OPC相关变量

OPCServer MyOpcServer; //定义OPCServer
OPCGroup MyOpcGroup; //定义组
OPCItem MyOpcItem1; //Item
OPCItem MyOpcItem2; //值
long[] ServerHandle = new long[2]; //Item的句柄

第三步,建立连接及对象

MyOpcServer = new OPCServer();
MyOpcServer.Connect("OPC.SimaticNet", "192.168.0.102");
MyOpcGroup = MyOpcServer.OPCGroups.Add("MyGroup1");
MyOpcItem1 = MyOpcGroup.OPCItems.AddItem("S7:[S7 connection_1]DB10,INT0",1);
MyOpcItem2 = MyOpcGroup.OPCItems.AddItem("S7:[S7 connection_1]DB10,INT2", 2);
ServerHandle[0] = MyOpcItem1.ServerHandle;
ServerHandle[1] = MyOpcItem2.ServerHandle;

第四步,同步读数据,

private void Btn_Read_S_Click(object sender,EventArgs e)//同步读数据
 {
	……
  MyOpcItem1.Read(1,out ItemValues,out Qualities, out TimeStamps);
//ItemValues,Qualities,TimeStamps分别是值,质量码
//也可以通过调用SyncRead函数,参数可参考异步读函数
	……
}

第四步,同步写数据

private void Btn_Write_S_Click(object sender,EventArgs e)
 {
	……
  MyOpcItem1.Write(Txt_W1.Text);
//也可以通过调用SyncWrite函数,参数可参考异步写函数
	……
}

第五步,异步事件定义,

在异步操作情况下,需要定义相应的异步事件

MyOpcGroup.DataChange +=new DIOPCGroupEvent_DataChangeEventHandler(MyOpcGroup_DataChange); //
//订阅方式下数据改变
iteComplete +=new DIOPCGroupEvent_AsyncWriteCompleteEventHandler(MyOpcGroup_WriteComplete);
//写完成事件
MyOpcGroup.AsyncReadComplete += new DIOPCGroupEvent_AsyncReadCompleteEventHandler(MyOpcGroup_ReadComplete);
//读完成事件
MyOpcGroup.AsyncCancelComplete += new DIOPCGroupEvent_AsyncCancelCompleteEventHandler(MyOpcGroup_CancelComplete);
//取消操作事件

在使用中注意,其事件函数要按照特定接口:

void MyOpcGroup_DataChange(int TransactionID, int NumItems,ref Array ClientHandles,ref Array ItemValues,ref Array Qualities, ref Array TimeStamps)
void MyOpcGroup_WriteComplete(int TransactionID, int NumItems, ref Array ClientHandles,ref Array Errors)
void MyOpcGroup_ReadComplete(int TransactionID, int NumItems,ref System.Array ClientHandles,ref System.Array ItemValues,ref System.Array Qualities,ref System.Array TimeStamps,ref System.Array Errors)
void MyOpcGroup_CancelComplete(int CancelID)

第六步订阅方式读

void MyOpcGroup_DataChange(int TransactionID, int NumItems,ref Array ClientHandles,ref Array ItemValues,ref Array Qualities, ref Array TimeStamps)
{
……
//注意数据改变时,Item数量要通过NumItems得到,也就是说只有数据改变时,才对一遍,所以降低了服务器负担。要注意读语句写法。
……
}

第七步异步读

private void Btn_Read_A_Click(object sender,EventArgs e)//异步读事件
{
int[] handle = new int[3] {ServerHandle[0], ServerHandle[1],0};//注意方式
Array MyServerHandles = (Array)handle;
Array errors;
int cancelID;
……
MyOpcGroup.AsyncRead(2, ref MyServerHandles, out errors, READASYNC_ID, out cancelID);
……
}
void MyOpcGroup_ReadComplete(int TransactionID, int NumItems,ref System.Array ClientHandles,ref System.Array ItemValues,ref System.Array Qualities,ref System.Array TimeStamps,ref System.Array Errors)
{
……
//注意TransactionID的对应
……
}

注意array在函数内部做参数时,数据下标是从1开始的,所以要考虑将第0位空出来,n个Item,就要定义n+1列数组,添加一个0,但在函数使用时,又是从左开始读的。否则会报错。

第八步异步写

private void Btn_Write_A_Click(object sender,EventArgs e)
{
  ……
MyOpcGroup.AsyncWrite(2,ref MyServerHandles, ref Myvalues,out errors,WRITEASYNC_ID,out cancelID);
  ……
}
void MyOpcGroup_WriteComplete(int TransactionID, int NumItems, ref Array ClientHandles ref Array Errors)
 {
  ……
}

同样要注意Array在函数内部做参数的传递。

第九步释放对象

private void Btn_Disconn_Click(object sender,EventArgs e)
{
  ……
}

OPCItem的数据类型:

在通过自定义接口访问时,

ItemArray[1].szAccessPath = "";
ItemArray[1].szItemID = "S7:[S7 connection_1]DB10,Real4";//地址,不同数据类型表示
ItemArray[1].bActive = 1;//是否激活
ItemArray[1].hClient = 2;//表示ID
ItemArray[1].dwBlobSize = 0;
ItemArray[1].pBlob =IntPtr.Zero;
ItemArray[1].vtRequestedDataType = 5;
ItemArray[2].szAccessPath = "";
ItemArray[2].szItemID = "S7:[S7 connection_1]DB10,STRING26.10";//地址,不同数据类型表示方法不同
ItemArray[2].bActive = 1;//是否激活
ItemArray[2].hClient = 3;//表示ID
ItemArray[2].dwBlobSize = 0;
ItemArray[2].pBlob =IntPtr.Zero;
ItemArray[2].vtRequestedDataType = 8;

4.2.3、AsyncReadComplete说明

Private Sub GroupObj_AsyncReadComplete(ByVal TransactionID As Long, ByVal NumItems As Long, ClientHandles() As Long, ItemValues() As Variant, Qualities() As Long, TimeStamps() As Date, Errors() As Long)

这里的参数ClientHandles和AddItems方法中的ClientHandles是对应的,用于判断哪一个OPC Item在被读取。其它参数的说明如下:
TransactionID:客户端自由使用,应用程序开发商自定义;
NumItems:表示读取的OPC Item的个数;
ItemValues():各个OPC Item连接的数据源的值,类型为Variant;
Qualities():OPC Item的品质值;
TimeStamps():时间戳;
Errors():记录服务器返回的信息。

5、实例

5.1、OPC应用场景1

OPC应用场景

5.2、实例展示说明

在这里插入图片描述
刷新列表按钮:刷新可选择的服务器节点,计算机名称或IP,选择对应节点,下边选择框可以选择对应节点下的OPC服务器;
连接按钮:选中要连接的OPC服务器名称,点击连接按钮,下端会显示该服务器下所有的点位标签。
双击其中一个变量添加到下端列表,显示所点击变量的标签名称,对应数值,和刷新时间。
打开浏览器按钮:点击打开浏览器按钮可以打开浏览器并显示所要查看的平台连接。

5.3、实例主要代码

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Windows.Forms;
using Microsoft.Win32;
using OPCAutomation;

namespace OPCClientDemo
{
    public partial class Frm_OPCClient : Form
    {
        public Frm_OPCClient()
        {
            InitializeComponent();
            this.Load += Frm_OPCClient_Load;
        }

        void Frm_OPCClient_Load(object sender, EventArgs e)
        {
            this.btn_RefreshList_Click(null, null);
            this.timer1.Interval = 1000;
            this.dgv_data.AutoGenerateColumns = false;
        }

        //定义OPC服务器
        OPCServer KepServer;
        //定义OPC服务器Groups
        OPCGroups KepGroups;
        //定义OPC服务器Group
        OPCGroup KepGroup;
        //定义OPC服务器Items
        OPCItems KepItems;
        //定义OPC服务器Brower
        OPCBrowser KepBrower;

        //定义OPC变量集合
        List<OPCItem> OPCList = new List<OPCItem>();
        List<int> ServerHandles = new List<int>();
        List<int> ClientHandles = new List<int>();
        List<string> TempIDList = new List<string>();
        //定义返回的OPC标签错误
        Array iErrors;
        //定义要添加的OPC标签的标识符
        Array strTempIDs;
        Array strClientHandles;
        Array strServerHandles;
        Array readServerHandles;
        Array writeServerHandles;
        Array writeArrayHandles;
        Array readErrors;
        Array writeErrors;
        int readTransID;
        int writeTransID;
        int readCancelID;
        int writeCancelID;

        private void btn_RefreshList_Click(object sender, EventArgs e)
        {
            //清空cmb_ServerNode下拉菜单
            this.cmb_ServerNode.Items.Clear();
            IPHostEntry IPHost = Dns.GetHostEntry(Environment.MachineName);
            if (IPHost.AddressList.Length > 0)
            {
                int count = IPHost.AddressList.Length;
                for (int i = 0; i < count; i++)
                {
                    //获取计算机名称
                    string HostName = Dns.GetHostEntry(IPHost.AddressList[i].ToString()).HostName;
                    //绑定下拉菜单
                    if (!this.cmb_ServerNode.Items.Contains(HostName))
                    {
                        this.cmb_ServerNode.Items.Add(HostName);
                    }
                }
            }
            else
            {
                return;
            }
        }

        private void cmb_ServerNode_SelectedIndexChanged(object sender, EventArgs e)
        {
            //清空cmb_ServerName下拉菜单
            this.cmb_ServerName.Items.Clear();
            KepServer = new OPCServer();
            object serverList = KepServer.GetOPCServers(this.cmb_ServerNode.Text.Trim());
            foreach (string item in (Array)serverList)
            {
                if (!this.cmb_ServerName.Items.Contains(item))
                {
                    this.cmb_ServerName.Items.Add(item);
                }
            }
        }

        private void btn_Connect_Click(object sender, EventArgs e)
        {
            if (this.btn_Connect.Text == "连接")
            {
                //连接选择的OPC服务器
                KepServer = new OPCServer();
                try
                {
                    //KepServer.Connect(this.cmb_ServerName.Text.Trim(), this.cmb_ServerNode.Text.Trim());
                    KepServer.Connect(this.cmb_ServerName.Text, this.cmb_ServerNode.Text);
                }
                catch (Exception ex)
                {

                    MessageBox.Show(ex.Message, "连接失败");
                }
                
                //通信组集合初始化
                //KepGroups对象赋值
                KepGroups = KepServer.OPCGroups;
                //KepGroups属性设置
                KepGroups.DefaultGroupDeadband = 0;//死区值,设为0时,服务器端该组内任何数据变化都通知组
                KepGroups.DefaultGroupIsActive = true;//激活组
                //通信组初始化
                KepGroup = KepGroups.Add("Group1");
                KepGroup.IsActive = true;
                KepGroup.IsSubscribed = true;//使用订阅功能,即可以异步,默认false
                KepGroup.UpdateRate = 250;

                KepGroup.AsyncReadComplete += KepGroup_AsyncReadComplete;
                //获取所有变量
                KepBrower = KepServer.CreateBrowser();
                KepBrower.ShowBranches();
                KepBrower.ShowLeafs(true);

                this.List_Items.Items.Clear();

                foreach (object item in KepBrower)
                {
                    this.List_Items.Items.Add(item.ToString());
                }

                //开始读取
                this.timer1.Enabled = true;

                this.btn_Connect.Text = "断开连接";
            }
            else
            {
                if (KepServer != null)
                {
                    KepServer.Disconnect();
                    KepServer = null;
                    this.btn_Connect.Text = "连接";
                    this.List_Items.Items.Clear();
                }
            }
            //异步读完成运行时,Array数组从下标1开始而非0!!!
            void KepGroup_AsyncReadComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps, ref Array Errors)
            {
                try
                {
                    for (int i = 1; i <= NumItems; i++)
                    {
                        object Value = ItemValues.GetValue(i);
                        if (Value != null)
                        {
                            this.OPCList[i - 1].Value = Value.ToString();
                            this.OPCList[i - 1].Time = ((DateTime)TimeStamps.GetValue(i)).AddHours(8).ToLongTimeString();
                        }
                    }
                    this.dgv_data.DataSource = null;
                    this.dgv_data.DataSource = this.OPCList;
                }
                catch(Exception ex)
                {

                }
            }
        }

        private void List_Items_DoubleClick(object sender, EventArgs e)
        {
            if (this.List_Items.SelectedItem != null)
            {
                //添加一个变量到集合里
                OPCList.Add(new OPCItem()
                {
                    Tag = this.List_Items.SelectedItem.ToString()
                });
            }

            TempIDList.Clear();
            ClientHandles.Clear();
            TempIDList.Add("0");
            ClientHandles.Add(0);
            int count = this.OPCList.Count;
            for (int i = 0; i < count; i++)
            {
                TempIDList.Add(this.OPCList[i].Tag);
                ClientHandles.Add(i + 1);
            }
            strTempIDs = (Array)TempIDList.ToArray();
            strClientHandles = (Array)ClientHandles.ToArray();
            KepItems = KepGroup.OPCItems;
            //KepItems添加标签
            KepItems.AddItems(this.OPCList.Count, ref strTempIDs, ref strClientHandles, out strServerHandles, out iErrors);
            /*AddItems说明
             * void AddItems(int NumItems, ref Array ItemIDs, ref Array ClientHandles, out Array ServerHandles, out Array Errors, object RequestedDataTypes, object AccessPaths);
             * int NumItems:加几个变量;输入
             * ref Array ItemIDs:表示ItermID集合;输入
             * ref Array ClientHandles:句柄,不能重复;输入
             * out Array ServerHandles:服务器的一个句柄;输出
             * out Array Errors:错误的;输出;
             
             */
            ServerHandles.Clear();
            ServerHandles.Add(0);
            for (int i = 0; i < count; i++)
            {
                ServerHandles.Add(Convert.ToInt32(strServerHandles.GetValue(i + 1)));
            }
            readServerHandles = (Array)ServerHandles.ToArray();
        }
        /// <summary>
        /// 定时读取事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timer1_Tick(object sender, EventArgs e)
        {
            this.lbl_CurrentTime.Text = "当前时间:" + DateTime.Now.ToLongTimeString();
            if (KepServer != null)
            {
                if (KepServer.ServerState == 1)
                {
                    this.lbl_Status.Text = "Connected";
                }
                else
                {
                    this.lbl_Status.Text = "Disonnected";
                }

            }
            if (this.OPCList.Count > 0)
            {
                if (KepServer != null)
                    KepGroup.AsyncRead(this.OPCList.Count, ref readServerHandles, out readErrors, readTransID, out readCancelID);
            }
        }

        private void dgv_data_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {
            if (this.dgv_data.SelectedRows != null)
            {
                int index = this.dgv_data.CurrentRow.Index;
                OPCItem objItem = this.OPCList[index];
                Frm_Modify objFrm = new Frm_Modify(objItem.Value);
                DialogResult res = objFrm.ShowDialog();
                int[] ServerHandle = new int[] { 0, Convert.ToInt32(strServerHandles.GetValue(index + 1)) };
                object[] Values = new object[2];
                string[] ModifyResult;
                writeServerHandles = (Array)ServerHandle;
                if (res == DialogResult.OK)
                {
                    ModifyResult = objFrm.Tag.ToString().Split('|');
                    Values[1] = ModifyResult[0];
                    writeArrayHandles = (Array)Values;
                    if (ModifyResult[1] == "1")
                    {
                        KepGroup.AsyncWrite(1, writeServerHandles, writeArrayHandles, out writeErrors, writeTransID, out writeCancelID);
                    }
                    else
                    {
                        KepGroup.SyncWrite(1, writeServerHandles, writeArrayHandles, out writeErrors);
                    }
                }
            }
        }

        private void dgv_data_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
        {
            //new DataGridViewStyle().DgvRowPostPaint(this.dgv_data, e);
        }
        /// <summary>
        /// 打开系统默认浏览器(用户自己设置了默认浏览器)
        /// </summary>
        /// <param name="url"></param>
        public static void OpenDefaultBrowserUrl(string url)
        {
            try
            {
                // 方法1
                //从注册表中读取默认浏览器可执行文件路径
                RegistryKey key = Registry.ClassesRoot.OpenSubKey(@"http\shell\open\command\");
                if (key != null)
                {
                    string s = key.GetValue("").ToString();
                    //s就是你的默认浏览器,不过后面带了参数,把它截去,不过需要注意的是:不同的浏览器后面的参数不一样!
                    //"D:\Program Files (x86)\Google\Chrome\Application\chrome.exe" -- "%1"
                    var lastIndex = s.IndexOf(".exe", StringComparison.Ordinal);
                    if (lastIndex == -1)
                    {
                        lastIndex = s.IndexOf(".EXE", StringComparison.Ordinal);
                    }
                    var path = s.Substring(1, lastIndex + 3);
                    var result = Process.Start(path, url);
                    if (result == null)
                    {
                        // 方法2
                        // 调用系统默认的浏览器
                        var result1 = Process.Start("explorer.exe", url);
                        if (result1 == null)
                        {
                            // 方法3
                            Process.Start(url);
                        }
                    }
                }
                else
                {
                    // 方法2
                    // 调用系统默认的浏览器
                    var result1 = Process.Start("explorer.exe", url);
                    if (result1 == null)
                    {
                        // 方法3
                        Process.Start(url);
                    }
                }
            }
            catch
            {
                OpenIe(url);
            }
        }
        /// <summary>
        /// 用IE打开浏览器
        /// </summary>
        /// <param name="url"></param>
        public static void OpenIe(string url)
        {
            try
            {
                Process.Start("iexplore.exe", url);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                // IE浏览器路径安装:C:\Program Files\Internet Explorer
                // at System.Diagnostics.process.StartWithshellExecuteEx(ProcessStartInfo startInfo)注意这个错误
                try
                {
                    if (File.Exists(@"C:\Program Files\Internet Explorer\iexplore.exe"))
                    {
                        ProcessStartInfo processStartInfo = new ProcessStartInfo
                        {
                            FileName = @"C:\Program Files\Internet Explorer\iexplore.exe",
                            Arguments = url,
                            UseShellExecute = false,
                            CreateNoWindow = true
                        };
                        Process.Start(processStartInfo);
                    }
                    else
                    {
                        if (File.Exists(@"C:\Program Files (x86)\Internet Explorer\iexplore.exe"))
                        {
                            ProcessStartInfo processStartInfo = new ProcessStartInfo
                            {
                                FileName = @"C:\Program Files (x86)\Internet Explorer\iexplore.exe",
                                Arguments = url,
                                UseShellExecute = false,
                                CreateNoWindow = true
                            };
                            Process.Start(processStartInfo);
                        }
                        else
                        {
                            if (MessageBox.Show(@"系统未安装IE浏览器,是否下载安装?", null, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question) == DialogResult.Yes)
                            {
                                // 打开下载链接,从微软官网下载
                                OpenDefaultBrowserUrl("http://windows.microsoft.com/zh-cn/internet-explorer/download-ie");
                            }
                        }
                    }
                }
                catch (Exception exception)
                {
                    MessageBox.Show(exception.Message);
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            OpenDefaultBrowserUrl("http://123.183.10003.18:33333/introduction?url=%2Fhome%2Fengine");
        }
    }
}

5.4、实例源码下载

感兴趣的同学可以下载:源码。

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

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

相关文章

【JavaEE】认识HTTP协议

✨哈喽&#xff0c;进来的小伙伴们&#xff0c;你们好耶&#xff01;✨ &#x1f6f0;️&#x1f6f0;️系列专栏:【JavaEE】 ✈️✈️本篇内容:认识HTTP协议、请求。 &#x1f680;&#x1f680;代码存放仓库github&#xff1a;JavaEE仓库&#xff01; ⛵⛵作者简介&#xff1…

Golang 提取视频中音频,存为MP3格式 | Golang工具

目录 前言 环境依赖 代码 总结 前言 本文提供将视频中音频提取出来存为mp3格式&#xff0c;一如既往的实用主义。 主要也是学习一下golang使用ffmpeg工具的方式。 环境依赖 ffmpeg环境安装&#xff0c;可以参考我的另一篇文章&#xff1a;windows ffmpeg安装部署_阿良的博…

Delphi复制HID、ID门禁卡源码

T5557卡是美国Atmel公司生产的多功能非接触式射频芯片卡&#xff0c;属于125KHz的低频卡&#xff0c;在国内有广大的应用市场&#xff0c;如很多酒店的门禁卡都是使用T5557卡。该芯片共有330bit(比特)的EPROM(分布为10个区块, 每个区块33bit)。0页的块0是被保留用于设置T5557操…

模电学习3. MCU接三极管开关电路简单分析

模电学习3. MCU接三极管开关电路简单分析一、说明二、使用NPN型三极管工作状态分析1. 饱和状态基本要求2. 计算限流电阻R23. IbI_bIb​4. 计算 RbR_bRb​5. 下拉电阻三、使用PNP型三极管工作状态分析1. 示例原理图2. IO输出低电平3. IO输出高电平一、说明 本文演示了一个使用M…

adb调试工具-笔记

adb调试工具adb工具可以在电脑通过终端命令操作安卓手机/模拟器命令&#xff1a;adb shell dumpsys window windows | findstr mFocusedApp该命令是查找程序的包名和界面名让我们打开夜神模拟器&#xff0c;并打开设置界面然后在电脑打开命令行&#xff0c;输入上述查询命令传送…

小红书数据分析网站:2023年如何产出爆文(纯干货分享)

导语&#xff1a; 不懂小红书最近火热的赛道、总是错过爆款打造的节奏……2023年了&#xff0c;相信不少人会发现关于小红书营销种草投放&#xff0c;入门容易&#xff0c;做好难。本文将借助第三方工具为品牌锚定赛道&#xff0c;寻找新风向。 1、行业流量大盘 —— 实时掌握…

Android 进程保活(一)

最近公司项目需求&#xff0c;需要给应用加入进程保活。 这里简述一下需求&#xff0c;由于App应用对接了蓝牙接收实时数据&#xff0c;并且数据量很大&#xff1b; 用户在操作App获取实时数据的时候&#xff0c;不可能一直看着手机屏幕&#xff0c; 这时候手机一般会有黑屏或者…

freertos学习之路1-裸机和rtos的区别

写在最前 由于工作需要&#xff0c;需要开始学习freertos的相关知识&#xff0c;本专题主要记录freertos的相关内容 参考&#xff1a; https://www.bilibili.com/video/BV19g411p7UT 正点原子视频 1. 裸机和rtos的多任务处理 试想一种场景&#xff0c;我们正在打游戏&#xff0…

java基础巩固-宇宙第一AiYWM:为了维持生计,大数据之ElasticSearch【ElasticSearch的概念、关于文档、索引的命令】~整起

ElasticSearch一、ElasticSearch基本概念1.ElasticSearch是什么&#xff1f;从哪来、来干啥&#xff1f;2.ElasticSearch与Solr的对比与选型&#xff1a;3.ES核心概念及相关操作4.ELK&#xff1a;拆箱即用的技术&#xff0c;解压完成就能用5.IK分词器6.ElasticSearch与SpringBo…

字节跳动青训营--前端day6

文章目录前言一、React的历史和应用二、React的设计思路1. ui编程痛点 & 对React的期望2. 组件化3.状态归属问题4. 生命周期三、React&#xff08;hooks&#xff09;1.Virtual DOM(虚拟 DOM)&#xff1a;2. diff算法四、React状态管理库五、应用级框架科普前言 仅以此文章…

spark 内存管理机制与相关参数调优

spark 内存管理 文章目录spark 内存管理spark 1.6 内存管理机制spark 2.0 内存管理机制spark 3.3.1 官方文档spark 内存相关参数调优spark 1.6 内存管理机制 https://0x0fff.com/spark-memory-management 统一内存管理 Spark 1.6 之后引入的统一内存管理机制&#xff0c;与静…

SpringbootAdmin:轻量级的Springboot监控组件,用过的都说好

简介 Springboot Admin是一个管理和监控Springboot项目的组件&#xff0c;分为服务端和客户端&#xff0c;两端通过http进行通信。由于其轻量级的特性&#xff0c;所以特别适合中小项目使用。 其效果图如下&#xff1a; 服务端配置 1&#xff0c;引入Springboot admin和Spri…

3.5 异常

1.概述 异常是一些用来封装错误信息的对象 它由异常的类型、提示信息、报错的行号提示三部分组成 2.异常的继承结构 3.异常的处理方式 当程序中遇到了异常,通常有两种处理方式:捕获或者向上抛出 当一个方法抛出异常,调用位置可以不做处理继续向上抛出,也可以捕获处理异常 大…

简单使用OpenGauss数据库

1 参考网站 # OpenGauss官网 https://opengauss.org/zh/2 Docker安装OpenGauss 下载OpenGauss docker pull enmotech/opengauss:3.0.0安装OpenGauss 容器参数说明&#xff1a; GS_PASSWORD&#xff1a;必须设置该参数&#xff0c;该参数设置了openGauss数据库的超级用户omm…

LeetCode 刷题系列 -- 1110. 删点成林

给出二叉树的根节点 root&#xff0c;树上每个节点都有一个不同的值。如果节点值在 to_delete 中出现&#xff0c;我们就把该节点从树上删去&#xff0c;最后得到一个森林&#xff08;一些不相交的树构成的集合&#xff09;。返回森林中的每棵树。你可以按任意顺序组织答案。示…

多域(跨域)计算「起势」,智能汽车赛道迎来新拐点

多域&#xff08;跨域&#xff09;计算平台正在成为新一轮市场争夺战的焦点。 就在今年CES展上&#xff0c;采埃孚推出多域功能版本的ProAI高性能计算平台&#xff0c;可以在不同的单板上支持基于域的ADAS、信息娱乐以及车身控制功能&#xff0c;并适配不同供应商的系统芯片以及…

前后端分离的陷阱

不管你设计的系统架构是怎么样&#xff0c;最后都是你的组织内的沟通结构胜出。这个观点一直在组织内不断地被证明&#xff0c;但也不断地被忽略。 前后端分离的利与弊 近几年&#xff0c;随着微服务架构风格的引入、前后端生态的快速发展、多端产品化的出现&#xff0c;前后…

vue前端框架应用案例(三)实现简单的echarts柱状图表

目录前端效果展示项目架构Seller.vueSellerPage.vueindex.jsApp.vuemain.jsindex.html后端源程序接口测试本博客内容参考黑马课程&#xff0c;详细信息请参考以下网址 Bilibili官方黑马课程&#xff1a;【echarts数据可视化项目】 前端 效果展示 项目架构 Seller.vue 该部分…

点云双边滤波

双边滤波&#xff08;Bilateral filter&#xff09;是一种非线性的滤波方法&#xff0c;是结合图像的空间邻近度和像素值相似度的一种折中处理&#xff0c;同时考虑空域信息和灰度相似性&#xff0c;达到保边去噪的目的。具有简单、局部的特点。双边滤波器的好处是可以做边缘保…

rootlogger 和 logger的关系

你是不是经常看到日志框架&#xff08;log4j、log4j2、logback等&#xff09;配置文件中有类似配置&#xff0c;但是始终搞不清楚啥意思&#xff1f;<root level"INFO"><appender-ref ref"CONSOLE" /><appender-ref ref"FILE" /&…