PLC通讯实现-C#访问OpcServer实现读写PLC(九)

PLC通讯实现-C#访问OpcServer实现读写PLC(九)

背景

由于工厂设备种类多、分阶段建设,工控程序开发通常面临对接多种PLC厂商设备和不同系列与型号。因此出现了一种专门与不同PLC通讯的软件协议-OPC(OLE for Process Control),进而有一些公司开发了基于OPC协议的软件,比如KEPServerEX(付费软件),目的是简化工控程序开发时与PLC通讯的过程,我们只需要按一种协议与OpcServer通讯就可以了。下面就介绍一下使用C#与OpcServer通讯的方法步骤。

依赖

我们通常不会从头写,可以基于OpcDaNet.dll库或Interop.OPCAutomation.dll库,基于OPCAutomation的例子有很多,本文我们就以OpcDaNet库为例讲解,而且附上OpcDaNet.dll的源代码。

配置OpcServer

首先我们需要配置OpcServer,本例我们使用了KEPServerExV5.14,因为是试用版,需要每隔一段时间重启一次服务,不过不影响我们学习和测试。
1、打开KEPServerEX,新建一个通道,此处我们命名为chnlSiemens。
2、在此chnlSiemens通道下新建一个设备,此处命名为S7-300,根据向导连接PLC,我使用的是西门子S7 300的PLC,并且在PLC中开了2个数据块,分别为DB4长度110个字、DB5长度122个字。
3、在S7-300设备下,按照PLC的实际数据块创建标签组,标签组的名称分别为DB4和DB5。
4、在DB4标签组下创建2个标签,第一个名称为0-99,地址为DB4DBW0.100,数据类型为Short,第二个名称为100-109,地址为DB4DBW100.10,数据类型为Short,即定义长度最长为100的Short数组。方便快速读取。
5、在DB5标签组下创建3个标签,第一个名称为0-99,地址为DB5DBW0.100,数据类型为Short,第二个名称为100-121, 地址为DB5DBW100.22,数据类型为Short,即定义长度最长为100的Short数组。方便快速读取。第三个标签名称为DB5DBW64,地址为DB5DBW64,数据类型为Short。
具体如下图:
在这里插入图片描述
6、可以点击工具栏上的Quick Client,打开客户端监视一下标签的值,如下图:
在这里插入图片描述

C#程序实现与OPCServer通讯

1、封装Equip.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using OPC;
using OPCDA;
using OPCDA.NET;
using Mesnac.Equips;

namespace Mesnac.Equip.OPC.OpcDaNet.OPC
{
    public class Equip : BaseEquip
    {
        #region 字段定义

        private bool _isOpen = false;
        private OpcServer myOPCServer;
        private DataChangeEventHandler dch;         //数据刷新委托对象
        private RefreshGroup asyncRefrGroup;        //数据刷新组,把要感知数据刷新的标签加入此组,这样标签值变化时才会触发DataChange事件
        private SyncIOGroup readWriteGroup;         //数据读写组,把要进行写入操作的标签放入词组,调用Write方法才会生效
        private Dictionary<string, object> readResult = null;       //设备标签数据缓存
        private int stepLen = 100;                  //标签变量的步长设置
        private string groupNamePrefix = "DB";      //数据块号前缀

        #endregion

        #region 属性定义

        /// <summary>
        /// OPCServer IP地址
        /// </summary>
        public string OpcServerIP
        {
            get
            {
                return "192.168.1.105";
            }
        }

        /// <summary>
        /// OPC服务名称
        /// </summary>
        public string OpcServerName
        {
            get
            {
                return "Kepware.KEPServerEX.V5";
            }
        }

        /// <summary>
        /// 通道名称
        /// </summary>
        public string ChannelName
        {
            get
            {
                return "chnlSiemens";
            }
        }

        /// <summary>
        /// 设备名称
        /// </summary>
        public string DeviceName
        {
            get
            {
                return "S7-300";
            }
        }

        #endregion

        public override bool Open()
        {
            lock (this)
            {
                if (this._isOpen == true && this.myOPCServer != null)
                {
                    return true;
                }
                this.State = false;
                this.myOPCServer = new OpcServer();
                int res = this.myOPCServer.Connect(this.OpcServerIP, this.OpcServerName);      //连接OPCServer
                if (HRESULTS.Failed(res))
                {
                    this.myOPCServer = null;
                    Console.WriteLine("OPC连接失败:" + res);
                    this.State = false;
                    return false;
                }
                else
                {
                    this.State = true;
                    this._isOpen = true;
                    Console.WriteLine("OPC连接成功!");
                    this.readWriteGroup = new SyncIOGroup(this.myOPCServer);

                    dch = new DataChangeEventHandler(DataChangeHandler);
                    this.asyncRefrGroup = new RefreshGroup(myOPCServer, dch, this.Main.ReadHz);

                    #region 初始化读取结果

                    this.readResult = new Dictionary<string, object>();

                    foreach (Equips.BaseInfo.Group group in this.Group.Values)
                    {
                        int tagCount = group.Len % this.stepLen == 0 ?  group.Len / this.stepLen : group.Len / this.stepLen + 1;

                        int currLen = 0;

                        for(int i = 0;i < tagCount ;i++)
                        {
                            string tagName = String.Empty;
                            if (tagCount == 1)
                            {
                                tagName = String.Format("{0}-{1}", group.Start, group.Start + group.Len - 1);
                                currLen = group.Len;
                            }
                            else if (i == tagCount - 1)
                            {
                                tagName = String.Format("{0}-{1}", group.Start + (i * this.stepLen), group.Start + (i * this.stepLen) + group.Len % this.stepLen - 1);
                                currLen = group.Len % this.stepLen;
                            }
                            else
                            {
                                tagName = String.Format("{0}-{1}", group.Start + (i * this.stepLen), group.Start + (i * this.stepLen) + this.stepLen - 1);
                                currLen = this.stepLen;
                            }

                            string tagFullName = String.Format("{0}.{1}", group.Name, tagName);

                            if (!this.readResult.ContainsKey(tagFullName))
                            {
                                short[] groupData = new short[currLen];
                                this.readResult[tagFullName] = groupData;

                                this.Add2RefrGroup(String.Format("{0}.{1}.{2}", this.ChannelName, this.DeviceName, tagFullName));
                            }
                        }
                    }

                    #endregion
                }
                return this.State;
            }
        }


        public override bool Read(string block, int start, int len, out object[] buff)
        {
            lock (this)
            {
                buff = null;
                try                               
                {
                    if (!Open())
                    {
                        return false;
                    }
                    string startTag = String.Empty;
                    string groupName = String.Format("{0}{1}", this.groupNamePrefix, block);        //要读取的OPCServer块
                    List<short> groupData = new List<short>();
                    foreach (string key in this.readResult.Keys)
                    {
                        if (key.StartsWith(groupName) && key.Replace(String.Format("{0}.", groupName), String.Empty).Contains("-"))
                        {
                            if (String.IsNullOrEmpty(startTag))
                            {
                                startTag = key.Replace(String.Format("{0}.", groupName), String.Empty);
                            }
                            short[] values = this.readResult[key] as short[];
                            groupData.AddRange(values);
                        }
                    }
                    buff = new object[len];
                    int startIndex = 0;
                    string strStartIndex = startTag.Substring(0, startTag.IndexOf("-"));
                    int.TryParse(strStartIndex, out startIndex);
                    startIndex = start - startIndex;
                    Array.Copy(groupData.ToArray(), startIndex, buff, 0, buff.Length);
                    return true;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(this.Name + "读取失败:" + ex.Message);
                    this.State = false;
                    return false;
                }
            }
        }

        public override bool Write(int block, int start, object[] buff)
        {
            lock (this)
            {
                try
                {
                    if (!Open())
                    {
                        return false;
                    }

                    bool isWrite = false;

                    #region 按标签变量写入

                    string itemId = "";
                    foreach (Equips.BaseInfo.Group group in this.Group.Values)
                    {
                        if (group.Block == block.ToString())
                        {
                            foreach (Equips.BaseInfo.Data data in group.Data.Values)
                            {
                                if (data.Start == start && data.Len == buff.Length)
                                {
                                    itemId = String.Format("{0}.{1}.{2}{3}.{4}", this.ChannelName, this.DeviceName, this.groupNamePrefix, block, data.Name);
                                    break;
                                }
                            }
                        }
                    }
                    if (!String.IsNullOrEmpty(itemId))
                    {
                        if (this.AddItem(itemId) == 0)
                        {
                            ItemDef itemData = this.readWriteGroup.Item(itemId);
                            if (itemData != null)
                            {
                                int res = 0;
                                if (buff.Length == 1)
                                {
                                    res = this.readWriteGroup.Write(itemData, buff[0]);
                                }
                                else
                                {
                                    res = this.readWriteGroup.Write(itemData, buff);
                                }
                                string error = readWriteGroup.GetErrorString(res);
                                if (res != 0)
                                {
                                    Console.WriteLine(String.Format("标签变量[{0}]写入失败:{1}", itemId, error));
                                    return false;
                                }
                                else
                                {
                                    isWrite = true;
                                }
                            }
                        }
                    }

                    if (isWrite)
                    {
                        return true;
                    }

                    #endregion

                    #region 按块写入

                    #region 先读取相应标签数数据

                    string startTag = String.Empty;
                    string groupName = String.Format("{0}{1}", this.groupNamePrefix, block);        //要读取的OPCServer块
                    List<short> groupData = new List<short>();
                    foreach (string key in this.readResult.Keys)
                    {
                        if (key.StartsWith(groupName) && key.Replace(String.Format("{0}.", groupName), String.Empty).Contains("-"))
                        {
                            if (String.IsNullOrEmpty(startTag))
                            {
                                startTag = key.Replace(String.Format("{0}.", groupName), String.Empty);
                            }

                            string[] beginEnd = key.Replace(String.Format("{0}.", groupName), String.Empty).Split(new char[] { '-' });
                            if (beginEnd.Length != 2)
                            {
                                Console.WriteLine(String.Format("标签变量[{0}]未按约定方式命名,请按[起始字-结束字]方式标签变量进行命名!", String.Format("{0}.{1}.{2}", this.ChannelName, this.DeviceName, key)));
                                return false;
                            }

                            int begin = 0;
                            int end = 0;

                            int.TryParse(beginEnd[0], out begin);
                            int.TryParse(beginEnd[1], out end);

                            #region 写入之前,先读取一下PLC的值

                            if ((start >= begin && start <= end) || ((start + buff.Length - 1) >= begin && (start + buff.Length - 1) <= end))
                            {
                                ItemDef itemData = this.readWriteGroup.Item(String.Format("{0}.{1}.{2}", this.ChannelName, this.DeviceName, key));
                                if (itemData == null)
                                {
                                    Console.WriteLine(String.Format("标签变量[{0}]未添加到数据读写组中!", String.Format("{0}.{1}.{2}", this.ChannelName, this.DeviceName, key)));
                                    return false;
                                }
                                OPCItemState itemState = null;
                                int res = this.readWriteGroup.Read(OPCDATASOURCE.OPC_DS_DEVICE, itemData, out itemState);
                                if (HRESULTS.Failed(res))
                                {
                                    string error = this.readWriteGroup.GetErrorString(res);
                                    Console.WriteLine(String.Format("读取标签变量[{0}]的值失败:{1}", String.Format("{0}.{1}.{2}", this.ChannelName, this.DeviceName, key), error));
                                    return false;
                                }

                                if (itemState.DataValue is Array)
                                {
                                    groupData.AddRange(itemState.DataValue as short[]);
                                }
                                else
                                {
                                    Console.WriteLine(String.Format("标签变量[{0}]的长度未指定!", String.Format("{0}.{1}.{2}", this.ChannelName, this.DeviceName, key)));
                                }
                            }

                            #endregion
                        }
                    }

                    #endregion

                    #region 更新标签中对应的数据后,再写回OPCServer

                    int startIndex = 0;
                    string strStartIndex = startTag.Substring(0, startTag.IndexOf("-"));
                    int.TryParse(strStartIndex, out startIndex);
                    startIndex = start - startIndex;
                    short[] newDataBuffer = groupData.ToArray();
                    for (int i = 0; i < buff.Length; i++)
                    {
                        short svalue = 0;
                        short.TryParse(buff[i].ToString(), out svalue);
                        newDataBuffer[startIndex + i] = svalue;
                    }

                    int index = 0;
                    foreach (string key in this.readResult.Keys)
                    {
                        if (key.StartsWith(groupName) && key.Replace(String.Format("{0}.", groupName), String.Empty).Contains("-"))
                        {
                            string[] beginEnd = key.Replace(String.Format("{0}.", groupName), String.Empty).Split(new char[] { '-' });
                            if (beginEnd.Length != 2)
                            {
                                Console.WriteLine(String.Format("标签变量[{0}]未按约定方式命名,请按[起始字-结束字]方式标签变量进行命名!", String.Format("{0}.{1}.{2}", this.ChannelName, this.DeviceName, key)));
                                return false;
                            }

                            int begin = 0;
                            int end = 0;

                            int.TryParse(beginEnd[0], out begin);
                            int.TryParse(beginEnd[1], out end);

                            if ((start >= begin && start <= end) || ((start + buff.Length - 1) >= begin && (start + buff.Length - 1) <= end))
                            {
                                ItemDef itemData = this.readWriteGroup.Item(String.Format("{0}.{1}.{2}", this.ChannelName, this.DeviceName, key));
                                if (itemData == null)
                                {
                                    Console.WriteLine(String.Format("写入失败:标签变量[{0}]未添加到数据读写组中!", String.Format("{0}.{1}.{2}", this.ChannelName, this.DeviceName, key)));
                                    return false;
                                }
                                if (!(this.readResult[key] is Array))
                                {
                                    Console.WriteLine(String.Format("标签变量[{0}]的长度未指定!", String.Format("{0}.{1}.{2}", this.ChannelName, this.DeviceName, key)));
                                    return false;
                                }
                                int len = (this.readResult[key] as short[]).Length;
                                short[] tagDataBuff = new short[len];
                                Array.Copy(newDataBuffer, index, tagDataBuff, 0, tagDataBuff.Length);
                                index += tagDataBuff.Length;
                                int res = this.readWriteGroup.Write(itemData, tagDataBuff);
                                if (HRESULTS.Failed(res))
                                {
                                    string error = this.readWriteGroup.GetErrorString(res);
                                    Console.WriteLine(String.Format("向标签变量[{0}]中写入值失败:{1}", String.Format("{0}.{1}.{2}", this.ChannelName, this.DeviceName, key), error));
                                    return false;
                                }
                                else
                                {
                                    Console.WriteLine("写入...");
                                }
                            }
                        }
                    }

                    #endregion

                    #endregion

                    return true;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(this.Name + "写入失败:" + ex.Message);
                    return false;
                }
            }
        }

        public override void Close()
        {
            lock (this)
            {
                if (this.myOPCServer != null)
                {
                    if (this.asyncRefrGroup != null)
                    {
                        this.asyncRefrGroup.Dispose();
                    }
                    if (this.readWriteGroup != null)
                    {
                        this.readWriteGroup.Dispose();
                    }
                    this.myOPCServer.Disconnect();
                    System.Threading.Thread.Sleep(2000);
                    this.myOPCServer = null;
                }
            }
        }

        #region 辅助方法

        /// <summary>
        /// OPCServer数据更新事件处理方法
        /// </summary>
        /// <param name="sender">事件源,一般为标签组</param>
        /// <param name="e">事件参数</param>
        private void DataChangeHandler(object sender, OPCDA.NET.DataChangeEventArgs e)
        {
            OPCDA.NET.OPCItemState[] itemStates = e.sts;
            foreach (OPCDA.NET.OPCItemState itemState in itemStates)
            {
                OPCDA.NET.ItemDef itemDef = this.asyncRefrGroup.FindClientHandle(itemState.HandleClient);
                if (itemDef != null)
                {
                    this.readResult[itemDef.OpcIDef.ItemID.Replace(String.Format("{0}.{1}.", this.ChannelName, this.DeviceName), String.Empty)] = itemState.DataValue;          //把最新数据放入读取结果中
                }
            }
        }

        /// <summary>
        /// 向数据读写组和数据刷新组中添加标签变量
        /// </summary>
        /// <param name="itemId">变量ID</param>
        /// <returns>成功返回0,失败返回-1</returns>
        private int Add2RefrGroup(string itemId)
        {
            if (AddItem(itemId) == 0)                                           //数据读写组
            {
                ItemDef itemData = this.readWriteGroup.Item(itemId);
                int res = this.asyncRefrGroup.Add(itemData.OpcIDef.ItemID);          //数据刷新组
                if (HRESULTS.Failed(res))
                {
                    Console.WriteLine(String.Format("向数据更新组中添加标签变量[{0}]失败,请检查OPCServer中有没有配置此标签!", itemId));
                    return -1;
                }
                return 0;
            }
            return -1;
        }
        /// <summary>
        /// 向数据读写组添加标签变量
        /// </summary>
        /// <param name="itemId">变量ID</param>
        /// <returns>成功返回0, 失败返回-1</returns>
        private int AddItem(string itemId)
        {
            ItemDef itemData = this.readWriteGroup.Item(itemId);
            if (itemData == null)
            {
                this.readWriteGroup.Add(itemId);
                itemData = this.readWriteGroup.Item(itemId);
                if (itemData == null)
                {
                    Console.WriteLine(String.Format("向数据读写组中添加标签变量[{0}]失败,请检查OPCServer中有没有配置此标签!", itemId));
                    return -1;
                }
            }
            return 0;
        }

        #endregion
    }
}

2、测试代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
//using Mesnac.Equip.OMRON.HostLink.COM;
//using Mesnac.Equip.Mitsubishi.MXComponent.Default;
//using Mesnac.Equip.Siemens.S7_300.Net;
//using Mesnac.Equip.Siemens.S7_1500.Ethernet;
using Mesnac.Equip.OPC.OpcDaNet.OPC;

namespace TestWinApp
{
    public partial class Form1 : Form
    {
        private Equip thisEquip = new Equip();

        private OPCDA.NET.OpcServer opcServer = new OPCDA.NET.OpcServer();
        private OPCDA.NET.DataChangeEventHandler dch;
        private OPCDA.NET.RefreshGroup refreshGroup = null;
        private OPCDA.NET.SyncIOGroup readWriteGroup = null;
        private Dictionary<string, object> lastData = new Dictionary<string,object>();

        public Form1()
        {
            InitializeComponent();
        }
		//连接设备
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                this.thisEquip.Main.ReadHz = 1000;

                this.thisEquip.Group.Clear();

                Mesnac.Equips.BaseInfo.Group group4 = new Mesnac.Equips.BaseInfo.Group();
                group4.Name = "DB4";
                group4.Block = "4";
                group4.Start = 0;
                group4.Len = 110;

                this.thisEquip.Group.Add(group4.Name, group4);

                Mesnac.Equips.BaseInfo.Group group5 = new Mesnac.Equips.BaseInfo.Group();
                group5.Name = "DB5";
                group5.Block = "5";
                group5.Start = 0;
                group5.Len = 122;

                Mesnac.Equips.BaseInfo.Data DB5DBW64 = new Mesnac.Equips.BaseInfo.Data();
                DB5DBW64.Name = "DB5DBW64";
                DB5DBW64.Start = 32;
                DB5DBW64.Len = 1;

                group5.Data.Add("DB5DBW64", DB5DBW64);

                this.thisEquip.Group.Add(group5.Name, group5);



                this.thisEquip.Open();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                MessageBox.Show(ex.StackTrace);
            }
        }
		//读取测试
        private void button2_Click(object sender, EventArgs e)
        {
            try
            {
                string block = this.textBox1.Text;
                int start = 0;
                int.TryParse(this.textBox2.Text,out start);
                int len = 0;
                int.TryParse(this.textBox3.Text,out len);
                object[] buff = new object[len];
                bool result = this.thisEquip.Read(block, start, len, out buff);
                if (result)
                {
                    StringBuilder sb = new StringBuilder();
                    foreach(object obj in buff)
                    {
                        sb.Append(obj.ToString()).Append(",");
                    }
                    this.textBox5.Text = sb.ToString();
                    MessageBox.Show("读取成功!");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                MessageBox.Show(ex.StackTrace);
            }
        }
		//写入测试
        private void button3_Click(object sender, EventArgs e)
        {
            try
            {
                int block = 0;
                int.TryParse(this.textBox1.Text, out block);
                int start = 0;
                int.TryParse(this.textBox2.Text, out start);
                int len = 0;
                int.TryParse(this.textBox3.Text, out len);
                object[] buff = new object[len];
                for (int i = 0; i < len; i++)
                {
                    buff[i] = Convert.ToInt32(this.textBox4.Text);
                }
                bool result = this.thisEquip.Write(block, start,buff);
                if (result)
                {
                    MessageBox.Show("写入成功!");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                MessageBox.Show(ex.StackTrace);
            }
        }
		//关闭连接
        private void button4_Click(object sender, EventArgs e)
        {
            try
            {
                this.thisEquip.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
    }
}

3、运行界面如下
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/zlbdmm/article/details/88032228