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,或者有更好的解決方案可以在評論區說出來。最后由衷感謝那位不知名的大佬,在網上提供了初版代碼,讓我可以跌跌撞撞地入門學習。