天天看點

使用OPC與PLC通訊 一

總結自己在opc與自控開發的經驗。首先介紹OPC DA模式下的OPC各種操作。

在使用opc時需要引用到 OPCDAAuto.dll 這個類庫。

在項目引用後需要注冊這個類庫,否則程式跑起來會報錯,“未找到工廠類 。。。”

将該dll檔案放在任意目錄下,建議在引用程式的的同級目錄下。 

在 cmd 控制台  輸入regsvr32  Q:\PLCDataIntegration\packages\01OPCDaAuto\OPCDAAuto.dll 

注冊完成後電腦會提示注冊成功,這時,就可以使用工具類中的方法啦。

1.定義相關變量

private OPCServer opcServer;
        private OPCGroups opcGroups;
        private OPCGroup opcGroup;
        private List<int> itemHandleClient = new List<int>();
        private List<int> itemHandleServer = new List<int>();
        private List<string> itemNames = new List<string>();
        private List<model> modelValues = new List<model>();
        private OPCItems opcItems;
        private OPCItem opcItem;
        private Dictionary<string, string> itemValues = new Dictionary<string, string>();
      

  

2.使用opc從plc中讀取資料。這個是使用OPC DAAuto中的Connect方法。Connect之前要先

建立OPCServer 對象

opcServer = new OPCServer();       

OPCServer.StartTime:伺服器的啟動時間

OPCServer.CurrentTime:伺服器的目前時間,各個用戶端可以通過這個屬性值完成一些同步的操作

//strHostIP 主機IP,DA模式下通常為127.0.0.1;
//strHostName opc服務名,通常為字元串,例如kepsserver 的opc名稱為 Kepware.KepServerEX.V6      
private bool ConnectServer(string strHostIP, string strHostName)
        {
            try
            {
                opcServer = new OPCServer();
                opcServer.Connect(strHostName, strHostIP);
            }
            catch (Exception ex)
            {
                SaveCommand("連接配接到OPC伺服器失敗!" + ex.Message);
                return false;
            }
            txtLog.Text += "連接配接到OPC伺服器成功!" + "\r\n";
            return true;
        }      

3.連接配接成功後就可以用opcServer這個對象了。

根據自己開發OPCServer 和OPCClient的經驗,這裡的OPCGroups與OPCGroup可以用樹來了解,OPCGroups下可以有很多OPCGroup,OPCGroup下添加很多測點item。

OPC API 裡AddItem 就是往OPCGroup下添加測點

4.OPCGroups與OPCGroup

opcGroups = opcServer.OPCGroups;
  opcGroup = opcGroups.Add("YoursGroupName");      

這裡從OPCServer中擷取OPCGroups資訊,保持一緻。

而OPCGroups下的OPCGroup可自行添加,然後自己注冊的測點屬性都在這個組下面

可以給OPCGroup設定如下屬性,這将影響到這個組下面的測點資料采集頻率等。

private void SetGroupProperty(OPCGroup opcGroup, int updateRate)
        {
            opcGroup.IsActive = true;
            opcGroup.DeadBand = 0f;
            opcGroup.UpdateRate = updateRate;
            opcGroup.IsSubscribed = true;
        }      
UpdateRate是一個整型值,影響組中測點資料更新頻率。相關屬性如下      
OPCGroups.Count:下屬組(Group)的數量
DefaultGroupIsActive(新添加的OPC組的活動狀态的預設值。預設初始值是活動狀态)
DefaultGroupUpdateRate(新添加的OPC組的預設資料更新周期,預設初始值是1000亳秒)、
DefaultGrouPDeadband( 新添加的OPC組的預設不敏感區的預設值,即能引起資料變化的最小數值百分比,預設值是0%)
DefaultGroupLocaleID(新添加的OPC組區域辨別符的預設值)
DefaultGroupTimeBias(新添加的OPC組的時間偏差的預設值)等。
      

5.設定OPCGroup屬性

opcGroups = opcServer.OPCGroups;
  opcGroup = opcGroups.Add("MYOPCGROUP");
  SetGroupProperty(opcGroup, UpdateRateOPC);
  opcItems = opcGroup.OPCItems;      

6.注冊opc監控測點。根據項目實際情況将監測點取出,以字元串數組傳遞。

public void AddItems(string[] itemNamesAdded)
        {
            itemHandleServer.Clear();

            for (int i = 0; i < itemNamesAdded.Length; i++)
            {
                itemNames.Add(itemNamesAdded[i]);
                itemValues.Add(itemNamesAdded[i], "");
            }
            for (int j = 0; j < itemNamesAdded.Length; j++)
            {
                try
                {
                    itemHandleClient.Add((itemHandleClient.Count == 0) ? 1 : (itemHandleClient[itemHandleClient.Count - 1] + 1));
                    opcItem = opcItems.AddItem(itemNamesAdded[j], itemHandleClient[itemHandleClient.Count - 1]);
                    itemHandleServer.Add(opcItem.ServerHandle);
                }
                catch (Exception ex)
                {
                    SaveCommand(itemNamesAdded[j] + ex.Message);
                }
            }
        }      

7.注冊讀寫或資料變化事件,opc的事件很多,常用的有以下幾個。

opcGroup.AsyncWriteComplete //測點寫入完成後觸發

opcGroup.AsyncReadComplete //讀取指令完成後觸發

opcGroup.DataChange  //opcc測點數值變化後觸發,通常用于實時資料上報

//注冊事件,然後在自己的方法裡實作相關操作
opcGroup.AsyncWriteComplete += new DIOPCGroupEvent_AsyncWriteCompleteEventHandler(opcGroup_AsyncWriteComplete);                
                
opcGroup.DataChange += new DIOPCGroupEvent_DataChangeEventHandler(KepGroup_DataChange);         
                
opcGroup.AsyncReadComplete += new DIOPCGroupEvent_AsyncReadCompleteEventHandler(GroupAsyncReadComplete);

      

//實作方法 KepGroup_DataChange

/// <summary>
        /// 資料變動轉儲
        /// </summary>
        /// <param name="TransactionID"></param>
        /// <param name="NumItems"></param>
        /// <param name="ClientHandles"></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)
        {
            string itemID = string.Empty;

            try
            {
                string text = "hello rabbit";
                int i = 0;
                for (i = 1; i <= NumItems; i++)
                {
                    //SetLogInfo($"{ItemValues.GetValue(i)}", Color.Green);

                    if (ItemValues.GetValue(i) == null) { continue; }

                    text = ItemValues.GetValue(i).ToString();

                    //SetLogInfo($"本次釋出消息{text}", Color.Green);

                    itemID = opcItems.GetOPCItem(itemHandleServer[(int)ClientHandles.GetValue(i) - 1]).ItemID;

                    string message = itemID + ":" + text;

                     MessageBox.Show(mesage);
                }
                
            }
            catch (Exception ex)
            {                
                SaveCommand(ex.Message + itemID);
            }
        }      

實作方法 GroupAsyncReadComplete

private void GroupAsyncReadComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps, ref Array Errors)
        {

            //SaveCommand("**********GroupAsyncReadComplete***********");

            string itemID = string.Empty;
            string empty = string.Empty;

            try
            {

                int i = 0;

                for (i = 1; i <= NumItems; i++)
                {
                    //非空判斷,防止程式異常中斷
                    if (ItemValues.GetValue(i) != null)
                    {
                        empty = ItemValues.GetValue(i).ToString();
                    }

                    //擷取監測點id
                    itemID = opcItems.GetOPCItem(itemHandleServer[(int)ClientHandles.GetValue(i) - 1]).ItemID;

                    string text = itemID + ":" + empty;

                    MessageBox.Show("讀取完成,結果為"+text);
                }

                MessageBox.Show($"本次讀取完成,共{i}個", Color.Blue, 2);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"GroupAsyncReadComplete:{ex.Message},itemID:{ itemID }");
            }
        }      

7.使用監測資料,在成功讀取到資料後就可以按自己的需求使用資料了,可以存到資料庫,放到Redis,或者推到Rabbit等消息隊列都沒問題。

8.通過opc寫入測點值實作,遠端控制plc

其實遠端控制,在代碼層面來看并沒有那麼複雜,因為前面的工作都做好後,opc通道已經打開的情況下,控制隻是一個寫入操作。把指定的值寫到指定的測點中,就是這麼簡單。實作控制難點不在控制本身,重點難點在于保證通信網絡的穩定,和監測狀态的

實時傳回。總而言之,控制本身不複雜,複雜在安全性和穩定型的保證上。使用opc多為IT與OT融合的情景之下,IT平台本身缺乏對底層裝置的有效監管,需要借助于opc用戶端充當代理中介角色。這時OPC用戶端本身的穩定性,和時效型顯得尤為重要。這點

需要研發人員多多考慮。這點大家有好的方法可以評論分享下。

//調用示例      

          string[] array2 = {"LCU1.PLC_GATE1_OPEN"};//點号開啟1号閘門

          string[] array3 = {"1"};

AsyncWrite(array2, array3);      

        /// <summary>
        /// 異步寫方法,支援批量操作也可以調用opc單點讀寫方法
        /// </summary>
        /// <param name="writeItemNames"></param>
        /// <param name="writeItemValues"></param>
        public void AsyncWrite(string[] writeItemNames, string[] writeItemValues)
        {
            try
            {
                OPCItem[] array = new OPCItem[writeItemNames.Length];
                for (int i = 0; i < writeItemNames.Length; i++)
                {
                    for (int j = 0; j < itemNames.Count; j++)
                    {
                        if (itemNames[j] == writeItemNames[i])
                        {
                            array[i] = opcItems.GetOPCItem(itemHandleServer[j]);
                            break;
                        }
                    }
                }
                int[] array2 = new int[writeItemNames.Length + 1];
                array2[0] = 0;
                for (int k = 1; k < writeItemNames.Length + 1; k++)
                {
                    array2[k] = array[k - 1].ServerHandle;
                }
                Array ServerHandles = array2;
                object[] array3 = new object[writeItemNames.Length + 1];
                array3[0] = "";
                for (int l = 1; l < writeItemNames.Length + 1; l++)
                {
                    array3[l] = writeItemValues[l - 1];
                }
                Array Values = array3;
                opcGroup.AsyncWrite(writeItemNames.Length, ref ServerHandles, ref Values, out Array _, 2009, out int _);
                GC.Collect();
            }
            catch (Exception ex)
            {                 MessageBox.Show(ex.Message);
            }
        }

      

至此OPC基本操作都完成了,OPC提供的官方API很深奧,有很多可以學的東西。OPC現在已經是自動化領域,跨域提供資料的潮流了,尤其時OPC UA釋出後,自動化(OT)與資訊化(IT)日趨融合,opc在傳統自動化企業轉型中應該會擔任比較重要的角色。

是以OPC還是很值得一學的。

分享一首好聽的歌 錯都錯了 ~~~