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編寫異步寫入事件,可以通過寫入按鈕将對應文本框的值寫入到服務端
耐心的同學可以在部落格慢慢看,不習慣的同學直接拉到末尾下載下傳源碼,然後在軟體裡看會舒服很多
完整代碼
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using OPCAutomation;
namespace OPCtest4
{
public partial class Form1 : Form
{
OPCServer KepServer;
OPCGroups KepGroups;
OPCGroup KepGroup;
OPCItems KepItems;
OPCItem KepItem;
bool opc_connected = false;//連接配接狀态
int itmHandleClient = 0;//用戶端的句柄,句柄即控件名稱,如“張三”,用來識别是哪個具體的對象,此處可了解為每個節點的編号
int itmHandleServer = 0;//伺服器的句柄
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
GetLocalServer();
}
/// <summary>
/// 擷取本地的OPC伺服器名稱
/// </summary>
public void GetLocalServer()
{
IPHostEntry host = Dns.GetHostEntry("127.0.0.1");
var strHostName = host.HostName;
try
{
KepServer = new OPCServer();
object serverList = KepServer.GetOPCServers(strHostName);
foreach (string turn in (Array)serverList)
{
cmbServerName.Items.Add(turn);
}
cmbServerName.SelectedIndex = 0;
btnConnServer.Enabled = true;
}
catch (Exception err)
{
MessageBox.Show("枚舉本地OPC伺服器出錯:" + err.Message, "提示資訊", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
/// <summary>
/// "連接配接"按鈕點選事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnConnServer_Click(object sender, EventArgs e)
{
try
{
if (!ConnectRemoteServer(txtRemoteServerIP.Text, cmbServerName.Text))
{
return;
}
btnSetGroupPro.Enabled = true;
opc_connected = true;
GetServerInfo();
RecurBrowse(KepServer.CreateBrowser());
if (!CreateGroup())
{
return;
}
}
catch (Exception err)
{
MessageBox.Show("初始化出錯:" + err.Message, "提示資訊", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
/// <summary>
/// 連接配接伺服器
/// </summary>
/// <param name="remoteServerIP">伺服器IP</param>
/// <param name="remoteServerName">伺服器名稱</param>
/// <returns></returns>
public bool ConnectRemoteServer(string remoteServerIP, string remoteServerName)
{
try
{
KepServer.Connect(remoteServerName, remoteServerIP);
if (KepServer.ServerState == (int)OPCServerState.OPCRunning)
{
tsslServerState.Text = "已連接配接到-" + KepServer.ServerName + " ";
}
else
{
//這裡你可以根據傳回的狀态來自定義顯示資訊,請檢視自動化接口API文檔
tsslServerState.Text = "狀态:" + KepServer.ServerState.ToString() + " ";
}
}
catch (Exception err)
{
MessageBox.Show("連接配接遠端伺服器出現錯誤:" + err.Message, "提示資訊", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
return true;
}
/// <summary>
/// 擷取伺服器資訊,并顯示在窗體狀态欄上
/// </summary>
public void GetServerInfo()
{
tsslServerStartTime.Text = "開始時間:" + KepServer.StartTime.ToString() + " ";
tsslversion.Text = "版本:" + KepServer.MajorVersion.ToString() + "." + KepServer.MinorVersion.ToString() + "." + KepServer.BuildNumber.ToString();
}
/// <summary>
/// 展開樹枝和葉子
/// </summary>
/// <param name="oPCBrowser">opc浏覽器</param>
public void RecurBrowse(OPCBrowser oPCBrowser)
{
//展開分支
oPCBrowser.ShowBranches();
//展開葉子
oPCBrowser.ShowLeafs(true);
foreach (object turn in oPCBrowser)
{
listBox1.Items.Add(turn.ToString());
}
}
/// <summary>
/// 建立組,将本地組和伺服器上的組對應
/// </summary>
/// <returns></returns>
public bool CreateGroup()
{
try
{
KepGroups = KepServer.OPCGroups;//将服務端的組集合複制到本地
KepGroup = KepGroups.Add("S");//添加一個組
SetGroupProperty();//設定組屬性
KepItems = KepGroup.OPCItems;//将組裡的節點集合複制到本地節點集合
KepGroup.DataChange += KepGroup_DataChange;
KepGroup.AsyncWriteComplete += KepGroup_AsyncWriteComplete;
}
catch (Exception err)
{
MessageBox.Show("建立組出現錯誤:" + err.Message, "提示資訊", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
return true;
}
/// <summary>
/// 設定組的屬性,從對應的控件裡擷取
/// </summary>
public void SetGroupProperty()
{
KepServer.OPCGroups.DefaultGroupIsActive = Convert.ToBoolean(txtGroupIsActive.Text);//激活組
KepServer.OPCGroups.DefaultGroupDeadband = Convert.ToInt32(txtGroupDeadband.Text);// 死區值,設為0時,伺服器端該組内任何資料變化都通知組
KepGroup.UpdateRate = Convert.ToInt32(txtUpdateRate.Text);//伺服器向客戶程式送出資料變化的重新整理速率
KepGroup.IsActive = Convert.ToBoolean(txtIsActive.Text);//組的激活狀态标志
KepGroup.IsSubscribed = Convert.ToBoolean(txtIsSubscribed.Text);//是否訂閱資料
}
/// <summary>
/// 異步寫方法
/// </summary>
/// <param name="TransactionID">處理ID</param>
/// <param name="NumItems">項個數</param>
/// <param name="ClientHandles">OPC用戶端的句柄</param>
/// <param name="Errors">錯誤個數</param>
private void KepGroup_AsyncWriteComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array Errors)
{
lblState.Text = "";
for (int i = 1; i <= NumItems; i++)
{
lblState.Text += "Tran:" + TransactionID.ToString() + " CH:" + ClientHandles.GetValue(i).ToString() + " Error:" + Errors.GetValue(i).ToString();
}
}
/// <summary>
/// 資料訂閱方法
/// </summary>
/// <param name="TransactionID">處理ID</param>
/// <param name="NumItems">項個數</param>
/// <param name="ClientHandles">OPC用戶端的句柄</param>
/// <param name="ItemValues">節點的值</param>
/// <param name="Qualities">節點的品質</param>
/// <param name="TimeStamps">時間戳</param>
private void KepGroup_DataChange(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps)
{
for (int i = 1; i <= NumItems; i++)//下标一定要從1開始,NumItems參數是每次事件觸發時Group中實際發生資料變化的Item的數量,而不是整個Group裡的Items
{
this.txtTagValue.Text = ItemValues.GetValue(i).ToString();
this.txtQualities.Text = Qualities.GetValue(i).ToString();
this.txtTimeStamps.Text = TimeStamps.GetValue(i).ToString();
}
}
/// <summary>
/// 選擇清單時觸發的事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListBox1_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
if (itmHandleClient != 0)
{
this.txtTagValue.Text = "";
this.txtQualities.Text = "";
this.txtTimeStamps.Text = "";
Array Errors;
OPCItem bItem = KepItems.GetOPCItem(itmHandleServer);
//注:OPC中以1為數組的基數
int[] temp = new int[2] { 0, bItem.ServerHandle };
Array serverHandle = (Array)temp;
//移除上一次選擇的項
KepItems.Remove(KepItems.Count, ref serverHandle, out Errors);
itmHandleClient = 1;//節點編号為1
KepItem = KepItems.AddItem(listBox1.SelectedItem.ToString(), itmHandleClient);//第一個參數為ItemID,第二個參數為節點編号,節點編号可自定義
itmHandleServer = KepItem.ServerHandle;//擷取該節點的伺服器句柄
}
else
{
itmHandleClient = 1;//節點編号為1
KepItem = KepItems.AddItem(listBox1.SelectedItem.ToString(), itmHandleClient);//第一個參數為ItemID,第二個參數為節點編号,節點編号可自定義
itmHandleServer = KepItem.ServerHandle;//擷取該節點的伺服器句柄
}
}
catch (Exception err)
{
//沒有任何權限的項,都是OPC伺服器保留的系統項,此處可不做處理。
itmHandleClient = 0;
txtTagValue.Text = "Error ox";
txtQualities.Text = "Error ox";
txtTimeStamps.Text = "Error ox";
MessageBox.Show("此項為系統保留項:" + err.Message, "提示資訊");
}
}
/// <summary>
/// 設定組屬性的按鈕點選事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnSetGroupPro_Click(object sender, EventArgs e)
{
SetGroupProperty();
}
/// <summary>
/// “寫入”按鈕點選事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnWrite_Click(object sender, EventArgs e)
{
OPCItem bItem = KepItems.GetOPCItem(itmHandleServer);
int[] temp = new int[2] { 0, bItem.ServerHandle };
Array serverHandles = (Array)temp;
object[] valueTemp = new object[2] { "", txtWriteTagValue.Text };
Array values = (Array)valueTemp;
Array Errors;
int cancelID;
KepGroup.AsyncWrite(1, ref serverHandles, ref values, out Errors, 2009, out cancelID);
//KepItem.Write(txtWriteTagValue.Text);//這句也可以寫入,但并不觸發寫入事件
GC.Collect();
}
/// <summary>
/// 關閉視窗事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (!opc_connected)
{
return;
}
if (KepGroup != null)
{
KepGroup.DataChange -= new DIOPCGroupEvent_DataChangeEventHandler(KepGroup_DataChange);
}
if (KepServer != null)
{
KepServer.Disconnect();
KepServer = null;
}
opc_connected = false;
}
}
}
至此,一個可讀可寫的用戶端就編寫好了,大家要是做得更好(比如多個節點的資料同時顯示在ListView控件上)可以分享出來大家一起學習。
源碼百度雲下載下傳↓
連結:https://pan.baidu.com/s/1BmCa622CAdUQRbW8ahXtMg 提取碼:yvdw
該分享連接配接期限為永久,送給愛學習的人。如果大家發現該源碼裡的BUG,或者有更好的解決方案可以在評論區說出來。最後由衷感謝那位不知名的大佬,在網上提供了初版代碼,讓我可以跌跌撞撞地入門學習。