最近,我們Ndolls工作室承接了山大某個自動化控制項目,主要做了一套工控信息化系統,其中有一個功能模塊是將系統管理的一部分數據參數發送至OPC服務器,由OPC服務器接收數據后執行相應工控操作。第一次接觸OPC的項目,有點頭大,與大家分享一點經驗,希望對大家有所幫助。
一、開發調試環境
1、系統環境:win7 64位
2、開發工具:Microsoft Visual Studio 2010(.Net4.0)
3、OPC組件:Interop.OPCAutomation.dll
下載地址:http://www.ndolls.net/doc/OPCAutomation.zip
4、OPC Server仿真軟件:ICONICS Simulator OPC Server 3.12
下載地址(需要登陸后下載):http://www.ndolls.net/doc/ICONICSSimulatorOPCServer3.12.zip
二、注意事項
1、Interop.OPCAutomation.dll需要在系統進行注冊,可以直接運行如下命令:
regsvr32 Interop.OPCAutomation.dll存放路徑
如因為win7系統權限問題無法注冊成功,請使用如下命令:
runas /user:administrator regsvr32 Interop.OPCAutomation.dll存放路徑
2、請將項目設置成X86模式,不然Interop.OPCAutomation.dll會調用失敗。
3、使用仿真軟件ICONICS Simulator OPC Server需要開啟Distributed Transaction Coordinator服務。
4、在調用Interop.OPCAutomation.dll過程中,如遇到返回錯誤碼,可參考以下地址進行分析解決:
https://www.cnblogs.com/heroius/p/7401026.html
5、Interop.OPCAutomation.dll中文文檔可從以下地址下載:
http://www.ndolls.net/doc/OPC_API.pdf
三、Demo界面以及代碼分析(下載地址:http://www.ndolls.net/doc/OPCManage.zip)
Demo主要實現了向OPC服務器指定的分組和標簽定向發送數據。
下圖為demo的winform界面
下圖為仿真軟件界面
下面貼上代碼,請注意首先需要添加引用using OPCAutomation
#region 變量
//OPCServer的IP
private String strHostIP;
//OPCServer的主機名
private String strHostName;
//是否和OPCServer建立連接
private Boolean opc_connected;
//建立的OPCServer對象
private OPCServer myServer;
//OPCServer節點瀏覽器
private OPCBrowser myOPCBrowser;
//分組集合
private OPCGroups myGroups;
//分組實例
private OPCGroup myGroup;
//分組的TAG節點
private OPCItems myItems;
//服務端句柄
int itmHandleServer = 0;
//要寫入的葉子節點
private OPCItem[] myItemArray;
#endregion
#region 觸發事件
/// <summary>
/// 每當項數據有變化時執行的事件
/// </summary>
/// <param name="TransactionID">處理ID</param>
/// <param name="NumItems">項個數</param>
/// <param name="ClientHandles">項客戶端句柄</param>
/// <param name="ItemValues">TAG值</param>
/// <param name="Qualities">品質</param>
/// <param name="TimeStamps">時間戳</param>
void myGroup_DataChange(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps)
{
for (int i = 1; i <= NumItems; i++)
{
txtValue.Text = ItemValues.GetValue(i).ToString();
}
//為方便測試,顯示到狀態欄輸出
lblState.Text = "TransactionID:" + TransactionID.ToString() + "--" + "NumItems:" + NumItems.ToString();
}
/// <summary>
/// 寫入TAG值時執行的事件
/// </summary>
/// <param name="TransactionID">處理ID</param>
/// <param name="NumItems">項個數</param>
/// <param name="ClientHandles">項客戶端句柄</param>
/// <param name="Errors">服務器返回的錯誤信息</param>
void myGroup_AsyncWriteComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array Errors)
{
//為方便測試,顯示到狀態欄輸出
for (int i = 1; i <= NumItems; i++)
{
lblState.Text = "TransactionID:" + TransactionID.ToString() + "--" + "ClientHandle:" + ClientHandles.GetValue(i).ToString() + "--" + "ErrorValue: " + Errors.GetValue(i).ToString();
}
}
#endregion
#region 按鈕事件
/// <summary>
/// 連接服務器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnConIP_Click(object sender, EventArgs e)
{
//根據輸入獲取OPC服務器IP地址
strHostIP = txtIP.Text;
//通過IP來獲取OPC服務器主機名
IPHostEntry ipHostEntry = Dns.GetHostEntry(strHostIP);
strHostName = ipHostEntry.HostName.ToString();
try
{
//實例化OPC服務
myServer = new OPCServer();
//獲取OPCServer列表
object serverList = myServer.GetOPCServers(strHostName);
//將OPCServer展示到ComboBox
foreach (string turn in (Array)serverList)
{
cmbServer.Items.Add(turn);
}
cmbServer.SelectedIndex = 0;
//開啟OPC連接按鈕
btnOPC.Enabled = true;
}
catch (Exception err)
{
MessageBox.Show("枚舉OPC服務出錯:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
/// <summary>
/// 連接OPC服務器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnOPC_Click(object sender, EventArgs e)
{
try
{
//根據選擇的OPCServer進行連接
myServer.Connect(cmbServer.Text, strHostIP);
//根據連接成功與否輸出狀態信息
if (myServer.ServerState == (int)OPCServerState.OPCRunning)
{
lblState.Text = "已連接到:" + myServer.ServerName;
//顯示服務器信息
lblState.Text += "----開始時間:" + myServer.StartTime.ToString();
lblState.Text += "----版本:" + myServer.MajorVersion.ToString() + "." + myServer.MinorVersion.ToString() + "." + myServer.BuildNumber.ToString();
}
else
{
lblState.Text = "狀態:" + myServer.ServerState.ToString();
}
//已連接標記
opc_connected = true;
//開啟獲取標簽按鈕
btnGetGrps.Enabled = true;
MessageBox.Show("鏈接OPC成功");
}
catch (Exception err)
{
MessageBox.Show("初始化出錯:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
/// <summary>
/// 獲取OPC服務器所有分組和標簽
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnGetGrps_Click(object sender, EventArgs e)
{
//實例化Tag瀏覽器
myOPCBrowser = myServer.CreateBrowser();
//展開分組
myOPCBrowser.ShowBranches();
//展開標簽
myOPCBrowser.ShowLeafs(true);
//將所有分支和葉子節點顯示到ListBox
lstItems.Items.Clear();
foreach (object turn in myOPCBrowser)
{
lstItems.Items.Add(turn.ToString());
}
//開啟定位標簽按鈕
btnSetItem.Enabled = true;
}
/// <summary>
/// 根據列表中選中的當前標簽,定位到需要發送數據的標簽
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSetItem_Click(object sender, EventArgs e)
{
try
{
/**
* 需要注意,不同的OPC服務器的標簽格式也是不同的
* 測試時,使用的是ICONICS Simulator OPC Server,標簽格式如:Textual.Memory
* 生產環境時,使用的是SimaticNet_V13Sp1,標簽格式如:S7:[S7_Connection_1]MReal120
* **/
//根據ListBox選中的標簽,處理得到分組名稱
string groupName = lstItems.Text;
//實例化組
myGroups = myServer.OPCGroups;
myGroup = myGroups.Add(groupName);
//設置缺省的組屬性
myServer.OPCGroups.DefaultGroupIsActive = true;
myServer.OPCGroups.DefaultGroupDeadband = 0;
myGroup.UpdateRate = 250;
myGroup.IsActive = true;
myGroup.IsSubscribed = true;
//定位需要發送數據的目標項
myItems = myGroup.OPCItems;
//實例化組內標簽
myItemArray = new OPCItem[1];
//填充項目組
myItemArray[0] = myItems.AddItem(lstItems.Text, 1);
//獲取服務端句柄
itmHandleServer = myItemArray[0].ServerHandle;
//監聽組內數據變化
myGroup.DataChange += new DIOPCGroupEvent_DataChangeEventHandler(myGroup_DataChange);
myGroup.AsyncWriteComplete += new DIOPCGroupEvent_AsyncWriteCompleteEventHandler(myGroup_AsyncWriteComplete);
//開啟發送參數按鈕
btnWrite.Enabled = true;
}
catch (Exception err)
{
MessageBox.Show("創建組出現錯誤:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
/// <summary>
/// 寫入值
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnWrite_Click(object sender, EventArgs e)
{
//獲取之前定位的標簽
OPCItem bItem = myItems.GetOPCItem(itmHandleServer);
//根據用戶界面輸入生成數據對象
int[] temp = new int[2] { 0, bItem.ServerHandle };
Array serverHandles = (Array)temp;
object[] valueTemp = new object[2] { "", txtMyValue.Text };
Array values = (Array)valueTemp;
Array Errors;
int cancelID;
//異步寫入到OPC服務器
myGroup.AsyncWrite(1, ref serverHandles, ref values, out Errors, 2009, out cancelID);
//回收資源
GC.Collect();
}
/// <summary>
/// 關閉窗體時,關閉鏈接
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OPCManage_FormClosing(object sender, FormClosingEventArgs e)
{
if (!opc_connected)
{
return;
}
if (myGroup != null)
{
myGroup.DataChange -= new DIOPCGroupEvent_DataChangeEventHandler(myGroup_DataChange);
}
if (myServer != null)
{
myServer.Disconnect();
}
opc_connected = false;
}
#endregion
四、生產環境
真實的生產環境中,工控終端采用西門子數控系統(SINUMERIK 808),OPC服務器采用SimaticNet_V13Sp1。由工控人員預先在OPC服務的S7:[S7_Connection_1]M分組下,創建MReal120—MReal140共6個寄存節點,用於接收軟件寫入的工藝參數。本系統用戶根據需要從我們的軟件中檢索工藝參數,並定向寫入到OPC服務器的S7:[S7_Connection_1]M節點下,由數控人員控制數控系統從OPC服務器獲取到最新參數進行生產。