C#利用自动化接口编写OPC客户端,OPC Client,源码直接放网盘
大部分源码参考自某位乐于分享的大佬,但是他的源码和接口都年代久远了(2009年的代码),基本用不了,还存在些许BUG,我的VS版本是2019最新版的,采用的OPC自动化接口也是最新的,修复致命BUG之后已经可以正常使用了,对于初学者来说算是不错的教程(非常讨厌那些写教程但源码还要积分下载的伪大佬,我呸!),大家可以先去下个OPC服务器模拟器,不然做出来的客户端是测不到数据的,相关的服务器模拟器可以百度一下,这个比较好找。
少罗嗦,先看东西,客户端界面如图所示↓
客户端运行图如图所示↓
东西看完了,接下来是引用
在“解决方案资源管理器”里鼠标右键单击项目依次选择“添加”→“引用”→“COM”,然后在右上角搜索框搜索“OPC”
选择下图这个自动化接口dll就可以了↓
并在代码区的头部(命名空间处)利用using关键词添加引用,如下图最后一行↓
准备工作做完了,接下来就是思路了:
1.先扫描本地的OPC服务器,将所有的服务器名加入到下拉框控件里
2.填上IP地址,本地测试一般为127.0.0.1,点击连接按钮触发事件,连接服务器
3.连接上服务器之后创建一个OPCBrowser对象,主要用于展开服务器的“树枝”和“叶子”,如下图↓就是服务器那边所有的节点。
4.创建一个组和设置组的属性
5.添加一个节点(通过选择列表里的节点达成添加操作)
6.编写订阅事件,当服务器端的数据有变化时,会把第5步添加的节点的对应值返回来,并显示在对应的文本控件上
7编写异步写入事件,可以通过写入按钮将对应文本框的值写入到服务端
耐心的同学可以在博客慢慢看,不习惯的同学直接拉到末尾下载源码,然后在软件里看会舒服很多
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Net; 8 using System.Text; 9 using System.Threading.Tasks; 10 using System.Windows.Forms; 11 12 using OPCAutomation; 13 14 namespace OPCtest4 15 { 16 public partial class Form1 : Form 17 { 18 OPCServer KepServer; 19 OPCGroups KepGroups; 20 OPCGroup KepGroup; 21 OPCItems KepItems; 22 OPCItem KepItem; 23 bool opc_connected = false;//连接状态 24 int itmHandleClient = 0;//客户端的句柄,句柄即控件名称,如“张三”,用来识别是哪个具体的对象,此处可理解为每个节点的编号 25 int itmHandleServer = 0;//服务器的句柄 26 public Form1() 27 { 28 InitializeComponent(); 29 } 30 31 private void Form1_Load(object sender, EventArgs e) 32 { 33 GetLocalServer(); 34 } 35 36 /// <summary> 37 /// 获取本地的OPC服务器名称 38 /// </summary> 39 public void GetLocalServer() 40 { 41 IPHostEntry host = Dns.GetHostEntry("127.0.0.1"); 42 var strHostName = host.HostName; 43 try 44 { 45 KepServer = new OPCServer(); 46 object serverList = KepServer.GetOPCServers(strHostName); 47 48 foreach (string turn in (Array)serverList) 49 { 50 cmbServerName.Items.Add(turn); 51 } 52 53 cmbServerName.SelectedIndex = 0; 54 btnConnServer.Enabled = true; 55 } 56 catch (Exception err) 57 { 58 MessageBox.Show("枚举本地OPC服务器出错:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning); 59 } 60 } 61 62 63 /// <summary> 64 /// "连接"按钮点击事件 65 /// </summary> 66 /// <param name="sender"></param> 67 /// <param name="e"></param> 68 private void BtnConnServer_Click(object sender, EventArgs e) 69 { 70 try 71 { 72 if (!ConnectRemoteServer(txtRemoteServerIP.Text, cmbServerName.Text)) 73 { 74 return; 75 } 76 77 btnSetGroupPro.Enabled = true; 78 79 opc_connected = true; 80 81 GetServerInfo(); 82 83 RecurBrowse(KepServer.CreateBrowser()); 84 85 if (!CreateGroup()) 86 { 87 return; 88 } 89 } 90 catch (Exception err) 91 { 92 MessageBox.Show("初始化出错:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning); 93 } 94 } 95 96 97 /// <summary> 98 /// 连接服务器 99 /// </summary> 100 /// <param name="remoteServerIP">服务器IP</param> 101 /// <param name="remoteServerName">服务器名称</param> 102 /// <returns></returns> 103 public bool ConnectRemoteServer(string remoteServerIP, string remoteServerName) 104 { 105 try 106 { 107 KepServer.Connect(remoteServerName, remoteServerIP); 108 109 if (KepServer.ServerState == (int)OPCServerState.OPCRunning) 110 { 111 tsslServerState.Text = "已连接到-" + KepServer.ServerName + " "; 112 } 113 else 114 { 115 //这里你可以根据返回的状态来自定义显示信息,请查看自动化接口API文档 116 tsslServerState.Text = "状态:" + KepServer.ServerState.ToString() + " "; 117 } 118 } 119 catch (Exception err) 120 { 121 MessageBox.Show("连接远程服务器出现错误:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning); 122 return false; 123 } 124 return true; 125 } 126 127 /// <summary> 128 /// 获取服务器信息,并显示在窗体状态栏上 129 /// </summary> 130 public void GetServerInfo() 131 { 132 tsslServerStartTime.Text = "开始时间:" + KepServer.StartTime.ToString() + " "; 133 tsslversion.Text = "版本:" + KepServer.MajorVersion.ToString() + "." + KepServer.MinorVersion.ToString() + "." + KepServer.BuildNumber.ToString(); 134 } 135 136 /// <summary> 137 /// 展开树枝和叶子 138 /// </summary> 139 /// <param name="oPCBrowser">opc浏览器</param> 140 public void RecurBrowse(OPCBrowser oPCBrowser) 141 { 142 //展开分支 143 oPCBrowser.ShowBranches(); 144 //展开叶子 145 oPCBrowser.ShowLeafs(true); 146 foreach (object turn in oPCBrowser) 147 { 148 listBox1.Items.Add(turn.ToString()); 149 } 150 } 151 152 153 /// <summary> 154 /// 创建组,将本地组和服务器上的组对应 155 /// </summary> 156 /// <returns></returns> 157 public bool CreateGroup() 158 { 159 try 160 { 161 KepGroups = KepServer.OPCGroups;//将服务端的组集合复制到本地 162 KepGroup = KepGroups.Add("S");//添加一个组 163 SetGroupProperty();//设置组属性 164 165 KepItems = KepGroup.OPCItems;//将组里的节点集合复制到本地节点集合 166 167 KepGroup.DataChange += KepGroup_DataChange; 168 KepGroup.AsyncWriteComplete += KepGroup_AsyncWriteComplete; 169 } 170 catch (Exception err) 171 { 172 MessageBox.Show("创建组出现错误:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning); 173 return false; 174 } 175 return true; 176 } 177 178 179 /// <summary> 180 /// 设置组的属性,从对应的控件里获取 181 /// </summary> 182 public void SetGroupProperty() 183 { 184 KepServer.OPCGroups.DefaultGroupIsActive = Convert.ToBoolean(txtGroupIsActive.Text);//激活组 185 KepServer.OPCGroups.DefaultGroupDeadband = Convert.ToInt32(txtGroupDeadband.Text);// 死区值,设为0时,服务器端该组内任何数据变化都通知组 186 KepGroup.UpdateRate = Convert.ToInt32(txtUpdateRate.Text);//服务器向客户程序提交数据变化的刷新速率 187 KepGroup.IsActive = Convert.ToBoolean(txtIsActive.Text);//组的激活状态标志 188 KepGroup.IsSubscribed = Convert.ToBoolean(txtIsSubscribed.Text);//是否订阅数据 189 } 190 191 192 /// <summary> 193 /// 异步写方法 194 /// </summary> 195 /// <param name="TransactionID">处理ID</param> 196 /// <param name="NumItems">项个数</param> 197 /// <param name="ClientHandles">OPC客户端的句柄</param> 198 /// <param name="Errors">错误个数</param> 199 private void KepGroup_AsyncWriteComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array Errors) 200 { 201 lblState.Text = ""; 202 for (int i = 1; i <= NumItems; i++) 203 { 204 lblState.Text += "Tran:" + TransactionID.ToString() + " CH:" + ClientHandles.GetValue(i).ToString() + " Error:" + Errors.GetValue(i).ToString(); 205 } 206 207 } 208 209 210 /// <summary> 211 /// 数据订阅方法 212 /// </summary> 213 /// <param name="TransactionID">处理ID</param> 214 /// <param name="NumItems">项个数</param> 215 /// <param name="ClientHandles">OPC客户端的句柄</param> 216 /// <param name="ItemValues">节点的值</param> 217 /// <param name="Qualities">节点的质量</param> 218 /// <param name="TimeStamps">时间戳</param> 219 private void KepGroup_DataChange(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps) 220 { 221 for (int i = 1; i <= NumItems; i++)//下标一定要从1开始,NumItems参数是每次事件触发时Group中实际发生数据变化的Item的数量,而不是整个Group里的Items 222 { 223 this.txtTagValue.Text = ItemValues.GetValue(i).ToString(); 224 this.txtQualities.Text = Qualities.GetValue(i).ToString(); 225 this.txtTimeStamps.Text = TimeStamps.GetValue(i).ToString(); 226 } 227 } 228 229 230 /// <summary> 231 /// 选择列表时触发的事件 232 /// </summary> 233 /// <param name="sender"></param> 234 /// <param name="e"></param> 235 private void ListBox1_SelectedIndexChanged(object sender, EventArgs e) 236 { 237 try 238 { 239 if (itmHandleClient != 0) 240 { 241 this.txtTagValue.Text = ""; 242 this.txtQualities.Text = ""; 243 this.txtTimeStamps.Text = ""; 244 245 Array Errors; 246 OPCItem bItem = KepItems.GetOPCItem(itmHandleServer); 247 //注:OPC中以1为数组的基数 248 int[] temp = new int[2] { 0, bItem.ServerHandle }; 249 Array serverHandle = (Array)temp; 250 //移除上一次选择的项 251 KepItems.Remove(KepItems.Count, ref serverHandle, out Errors); 252 253 254 itmHandleClient = 1;//节点编号为1 255 KepItem = KepItems.AddItem(listBox1.SelectedItem.ToString(), itmHandleClient);//第一个参数为ItemID,第二个参数为节点编号,节点编号可自定义 256 itmHandleServer = KepItem.ServerHandle;//获取该节点的服务器句柄 257 } 258 else 259 { 260 itmHandleClient = 1;//节点编号为1 261 KepItem = KepItems.AddItem(listBox1.SelectedItem.ToString(), itmHandleClient);//第一个参数为ItemID,第二个参数为节点编号,节点编号可自定义 262 itmHandleServer = KepItem.ServerHandle;//获取该节点的服务器句柄 263 264 } 265 266 } 267 catch (Exception err) 268 { 269 //没有任何权限的项,都是OPC服务器保留的系统项,此处可不做处理。 270 itmHandleClient = 0; 271 txtTagValue.Text = "Error ox"; 272 txtQualities.Text = "Error ox"; 273 txtTimeStamps.Text = "Error ox"; 274 MessageBox.Show("此项为系统保留项:" + err.Message, "提示信息"); 275 } 276 } 277 278 279 /// <summary> 280 /// 设置组属性的按钮点击事件 281 /// </summary> 282 /// <param name="sender"></param> 283 /// <param name="e"></param> 284 private void BtnSetGroupPro_Click(object sender, EventArgs e) 285 { 286 SetGroupProperty(); 287 } 288 289 290 /// <summary> 291 /// “写入”按钮点击事件 292 /// </summary> 293 /// <param name="sender"></param> 294 /// <param name="e"></param> 295 private void BtnWrite_Click(object sender, EventArgs e) 296 { 297 OPCItem bItem = KepItems.GetOPCItem(itmHandleServer); 298 int[] temp = new int[2] { 0, bItem.ServerHandle }; 299 Array serverHandles = (Array)temp; 300 object[] valueTemp = new object[2] { "", txtWriteTagValue.Text }; 301 Array values = (Array)valueTemp; 302 Array Errors; 303 int cancelID; 304 KepGroup.AsyncWrite(1, ref serverHandles, ref values, out Errors, 2009, out cancelID); 305 //KepItem.Write(txtWriteTagValue.Text);//这句也可以写入,但并不触发写入事件 306 GC.Collect(); 307 } 308 309 310 /// <summary> 311 /// 关闭窗口事件 312 /// </summary> 313 /// <param name="sender"></param> 314 /// <param name="e"></param> 315 private void Form1_FormClosing(object sender, FormClosingEventArgs e) 316 { 317 if (!opc_connected) 318 { 319 return; 320 } 321 322 if (KepGroup != null) 323 { 324 KepGroup.DataChange -= new DIOPCGroupEvent_DataChangeEventHandler(KepGroup_DataChange); 325 } 326 327 if (KepServer != null) 328 { 329 KepServer.Disconnect(); 330 KepServer = null; 331 } 332 333 opc_connected = false; 334 } 335 } 336 }
至此,一个可读可写的客户端就编写好了,大家要是做得更好(比如多个节点的数据同时显示在ListView控件上)可以分享出来大家一起学习。
源码百度云下载↓
链接:https://pan.baidu.com/s/1BmCa622CAdUQRbW8ahXtMg 提取码:yvdw
该分享连接期限为永久,送给爱学习的人。如果大家发现该源码里的BUG,或者有更好的解决方案可以在评论区说出来。最后由衷感谢那位不知名的大佬,在网上提供了初版代码,让我可以跌跌撞撞地入门学习。