基于opc UA客户端上位机编写使用自动化接口进行OPCDA的客户端编写,但是OPCDA是基于COM/DCOM的技术,如果服务器与客户端不在同一台电脑上配置较为复杂;基于COM/DCOM的技术有着不可根除的缺点,因此随着技术的进步,以及数据交换各方面需求的提高,OPC基金会在2008年发布了新的规范:OPC UA。OPC UA规范不再是基于COM/DCOM技术,因此OPC UA不仅能在Windows平台上实现,更可以在Linux,以及其他的嵌入式平台中实现。与传统OPC规范相同,OPC UA同样有着相同的设计目标:1.功能等价:所有的基于COM的OPC规范中的功能,都映射到了OPC UA中。2. 多平台支持:支持从嵌入式的微控制器到基于云的分散式控制架构。3.安全:信息加密,互访认证以及安全监听功能。4.扩展性:不影响现有应用程序的情况下,就可以添加新的功能。5.丰富的信息建模:可定义复杂的信息,而不再是单一的数据。OPCUA服务器配置本次OPCUA服务器依然使用KEPServerEX 6,配置如下:1、在托盘找到KEPServerEX 6图标,右键弹出菜单,点击OPCUA配置2、在弹出的配置界面进行如下图的配置:3、打开KEPServerEX 6主界面,右键点击项目,选择属性,进行配置如下,即完成OPCUA服务器配置;03软件编写1、软件实现节点浏览,添加订阅,移除订阅,清空订阅的功能,演示界面如下:2、本次代码实现主要基于OPC UA的最新官方库二次封装的一个类库:OpcUaHelper.dll,开源地址:https://github.com/dathlin/OpcUaHelper;准备好开发的IDE,新建项目。注意:项目的.NET framework版本最低为4.6。打开NuGet管理器,引用OpcUaHelper;3、代码编写:<1> 建立连接private void button1_Click(object sender, EventArgs e){opcUaClient.UserIdentity = new UserIdentity(new AnonymousIdentityToken());opcUaClient.ConnectServer(textBox1.Text);PopulateBranch(ObjectIds.ObjectsFolder, treeView1.Nodes);}<2>节点浏览private async void PopulateBranch(NodeId sourceId, TreeNodeCollection nodes){odes.Clear();nodes.Add(new TreeNode("Browsering..."));// fetch references from the server.TreeNode[] listNode = await Task.Run(() =>{ReferenceDescriptionCollection references = GetReferenceDescriptionCollection(sourceId);List<TreeNode> list = new List<TreeNode>();if (references != null){// process results.for (int ii = 0; ii < references.Count; ii++){ReferenceDescription target = references[ii];TreeNode child = new TreeNode(Utils.Format("{0}", target));child.Tag = target;child.Nodes.Add(new TreeNode());list.Add(child);}}return list.ToArray();});//更新显示的属性// DisplayAttributes(sourceId);nodes.Clear();nodes.AddRange(listNode.ToArray());}private ReferenceDescriptionCollection GetReferenceDescriptionCollection(NodeId sourceId){//TaskCompletionSource<ReferenceDescriptionCollection> task = new TaskCompletionSource<ReferenceDescriptionCollection>();// 找到所有组件的节点BrowseDescription nodeToBrowse1 = new BrowseDescription();nodeToBrowse1.NodeId = sourceId;nodeToBrowse1.BrowseDirection = BrowseDirection.Forward;nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.Aggregates;nodeToBrowse1.IncludeSubtypes = true;nodeToBrowse1.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method | NodeClass.ReferenceType | NodeClass.ObjectType | NodeClass.View | NodeClass.VariableType | NodeClass.DataType);nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All;// 找到所有节点组织的节点.BrowseDescription nodeToBrowse2 = new BrowseDescription();nodeToBrowse2.NodeId = sourceId;nodeToBrowse2.BrowseDirection = BrowseDirection.Forward;nodeToBrowse2.ReferenceTypeId = ReferenceTypeIds.Organizes;nodeToBrowse2.IncludeSubtypes = true;nodeToBrowse2.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method | NodeClass.View | NodeClass.ReferenceType | NodeClass.ObjectType | NodeClass.VariableType | NodeClass.DataType);nodeToBrowse2.ResultMask = (uint)BrowseResultMask.All;BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection();nodesToBrowse.Add(nodeToBrowse1);nodesToBrowse.Add(nodeToBrowse2);// 从服务器获取引用ReferenceDescriptionCollection references = FormUtils.Browse(opcUaClient.Session, nodesToBrowse, false);return references;}private void treeView1_BeforeExpand(object sender, TreeViewCancelEventArgs e){try{// check if node has already been expanded once.if (e.Node.Nodes.Count != 1){return;}if (e.Node.Nodes.Count > 0){if (e.Node.Nodes[0].Text != String.Empty){return;}}// get the source for the node.得到的源节点ReferenceDescription reference = e.Node.Tag as ReferenceDescription;if (reference == null || reference.NodeId.IsAbsolute){e.Cancel = true;return;}// populate children.PopulateBranch((NodeId)reference.NodeId, e.Node.Nodes);}catch (Exception exception){ClientUtils.HandleException(this.Text, exception);}}

