写在前面
本文配套的完整示例工程与调试笔记已整理成压缩包,需要的朋友可以自行转存留档,方便对照代码阅读:
- 链接:https://pan.baidu.com/s/1zKDKjb-v7AxQnth5wKxtfg?pwd=8q9q
- 提取码:8q9q
复制链接后在浏览器打开即可,建议先转存到自己的网盘再下载,避免链接失效。文中所有代码均已在本地环境验证,压缩包仅作学习参考之用。
正文
读这篇文章的朋友请提前了解下什么是 OPC,OPC 技术的产生在这里我不再多缀述了。
本篇文章主要讲解一下c#调用Interop.OPCAutomation.dll实现OPC Client的注意事项,已通过正规项的检验,在这里给大家分享一下核心经验, 此文代码版权所有,仅供参考学习,不得转载或用于商业用途,如果引起
商业利益纠纷后果自负
这里我的OPC Client从OPC Server端读到的数据是一个分层级的Tree状数据结构,所以Connect上服务后通过OPCBrowser对象来遍历整个Tree, 一般网上提供的例子大多无法动态的读到所有的OPCGroup,而且无法分辨多
层级的Branch或是Leaf,经过我对数据多次调试终于找到一种通用的解决方案,基上可以适用多种OPCServer的
数据结构,包括Kepware公司的OPCServer, OPCServer Simulator等等的一些测试。你也可以用Kassl-dOPC-Explorer工具验证你的OPCClient读到的数据结构是否一样,经过验证我的OPCClient和此工具的结果是吻合的,
OPCBrowser通过递归调用将数据的各个节点加载到TreeView控件中,Leaf认为是OPCItem, Leaf的上一级为OPCGroup.Leaf不在TreeView的TreeNode中显示.具体实现请看以下代码
private OPCServer kepServer; private OPCBrowser opcBrowser; private Dictionary<string, OPCGroup> groupMap = new Dictionary<string, OPCGroup>(); private Dictionary<int, OPCItem> itemMap = new Dictionary<int, OPCItem>(); //此方法通过OPCBrowser将数据结节加载到TreeView中. private bool LoadDataToTree(TreeNode node, string nodeName) { TreeNode childNode = null; OPCGroup group = null; try { this.opcBrowser.ShowBranches(); int count = this.opcBrowser.Count; if (count == 0) { this.opcBrowser.ShowLeafs(true); group = this.kepServer.OPCGroups.Add(nodeName); group.UpdateRate = 1000; group.IsSubscribed = true; group.IsActive = true; node.Tag = group; this.groupMap.Add(nodeName, group); } HashSet<string> itemList = new HashSet<string>(); foreach (object turn in this.opcBrowser) { string path = nodeName; string name = turn.ToString(); if (count != 0) { childNode = node.Nodes.Add(name); if (path != "") { path += "."; } path += name; this.opcBrowser.MoveDown(name); LoadDataToTree(childNode, path); this.opcBrowser.MoveUp(); } else { OPCItem opcItem = group.OPCItems.AddItem(name, 0); itemList.Add(name); if (!this.itemMap.ContainsKey(opcItem.GetHashCode())) { this.itemMap.Add(opcItem.GetHashCode(), opcItem); } } } this.opcBrowser.ShowLeafs(false); List<string> leafItems = new List<string>(); foreach (object t in this.opcBrowser) { string itemId = this.opcBrowser.GetItemID(t.ToString()); if (itemList.Contains(itemId)) { continue; } leafItems.Add(itemId); } if (leafItems.Count != 0) { group = this.kepServer.OPCGroups.Add(nodeName); group.UpdateRate = 1000; group.IsSubscribed = true; group.IsActive = true; this.groupMap.Add(nodeName, group); node.Tag = group; foreach (string t in leafItems) { OPCItem opcItem = group.OPCItems.AddItem(t, 0); //childNode = node.Nodes.Add(t.ToString()); if (!this.itemMap.ContainsKey(opcItem.GetHashCode())) { this.itemMap.Add(opcItem.GetHashCode(), opcItem); } } } } catch(Exception ex) { LogHelper.Error(ex); } return true; } //用来标识每一个OPCItem数据的唯一标识 private void SetItemsClientHandle() { foreach(OPCItem item in this.itemMap.Values) { item.ClientHandle = item.GetHashCode(); } } //此方法连接OPCServer上的一个Program. public bool Connect(string host, string prog) { try { if (connected) { if (this.kepServer != null) { this.kepServer.Disconnect(); this.connected = false; } } this.tvwGroupList.Nodes.Clear(); this.groupMap.Clear(); this.itemMap.Clear(); this.kepServer.Connect(prog, host); this.tsslblServerState.Text = "OPC服务[" + prog + "]已连接"; TreeNode node = this.tvwGroupList.Nodes.Add(prog); node.Tag = this.kepServer; this.opcBrowser = this.kepServer.CreateBrowser(); LoadDataToTree(node, ""); this.SetItemsClientHandle(); this.LoadConfig(prog); this.tsslblItemCount.Text = "Item Count " + this.itemMap.Count.ToString(); this.tsslblGroupCount.Text = "Group Count " + this.groupMap.Count.ToString(); this.connected = true; } catch (Exception ex) { LogHelper.Error(ex); } return true; } //此方法将选中的叶子节点(OPCGroup)下的所有OPCItem显示在ListView中. private void tvwGroupList_MouseDown(object sender, MouseEventArgs e) { try { TreeNode crtNode = this.tvwGroupList.GetNodeAt(e.X, e.Y); TreeNode oldNode = this.tvwGroupList.SelectedNode; if (oldNode != null) { this.lstviewItems.Items.Clear(); if (oldNode.Tag is OPCGroup) { OPCGroup old_group = oldNode.Tag as OPCGroup; } } this.tvwGroupList.SelectedNode = crtNode; if (e.Button == MouseButtons.Right) { if (crtNode.Tag is OPCServer) { this.opcServercontextMenuStrip.Show(this.tvwGroupList, new Point(e.X, e.Y)); } else if (crtNode.Tag is OPCGroup) { this.opcGroupcontextMenuStrip.Show(this.tvwGroupList, new Point(e.X, e.Y)); } } if (crtNode.Tag is OPCGroup) { OPCGroup crt_group = crtNode.Tag as OPCGroup; OPCItems items = crt_group.OPCItems; if (items.Count != 0) { foreach (OPCItem item in items) { string itemId = item.ItemID; string itemValue = Convert.ToString(item.Value); if (item.CanonicalDataType == 11) { itemValue = Convert.ToInt32(Convert.ToBoolean(itemValue)).ToString(); } ListViewItem listitem = new ListViewItem(new string[] { itemId, item.CanonicalDataType.ToString(), itemValue, item.TimeStamp.ToString(), OPCEnumHelper.GetQuality((OPCQuality)item.Quality), "0"}); listitem.Tag = item.ClientHandle; this.lstviewItems.Items.Add(listitem); } } else { Array branches = crt_group.Name.Split('.'); this.opcBrowser.MoveTo(ref branches); this.opcBrowser.ShowLeafs(true); foreach (object turn in this.opcBrowser) { string name = turn.ToString(); //crt_group.OPCItems.AddItem(name, this.itemHandleClient); ListViewItem listitem = new ListViewItem(new string[] { name, "", "", "", "", ""}); this.lstviewItems.Items.Add(listitem); } this.opcBrowser.ShowLeafs(true); this.opcBrowser.MoveToRoot(); } } } catch (Exception ex) { LogHelper.Error(ex); } }