通过关键词优化提升企业网站百度入驻
PLC通讯实现-C#访问RSLinx OPC Server实现读写PLC(十二)
- 背景
- 依赖
- 配置RSLinx OPC Server
- C#程序实现与OPCServer通讯
背景
由于工厂设备种类多、分阶段建设,工控程序开发通常面临对接多种PLC厂商设备和不同系列与型号。因此出现了一种专门与不同PLC通讯的软件协议-OPC(OLE for Process Control),进而有一些公司开发了基于OPC协议的软件,比如KEPServerEX(付费软件),目的是简化工控程序开发时与PLC通讯的过程,我们只需要按一种协议与OpcServer通讯就可以了。下面就介绍一下使用C#与OpcServer通讯的方法步骤。
依赖
我们通常不会从头写,可以基于OpcDaNet.dll库或Interop.OPCAutomation.dll库,基于OPCAutomation的例子有很多,本文我们就以OpcDaNet库为例讲解,而且附上OpcDaNet.dll的源代码。
配置RSLinx OPC Server
参考【RSLinx配置OPCServer图文教程】
Topic为demo1,可以认为是通道。
此处PLC中配置了2个标签N100和N101,都是长度为100的INT类型的数组。
规则定义:
N-定义为PLC中数据块的前缀
100/101-定义为PLC中数据库的块号
C#程序实现与OPCServer通讯
1、封装Equip.cs
using System;
using System.Collections.Generic;
using System.Linq;
using OPC;
using OPCDA;
using OPCDA.NET;
using Mesnac.Equips;namespace Mesnac.Equip.OPC.OpcRSLinx.RSLinxOPC
{/// <summary>/// 通过RSLinx OPC进行PLC读写的设备对象,请把应用程序发布的目标平台设置为x86,否则无法正常连接/// </summary>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 = "N"; //数据块号前缀#endregion#region 属性定义/// <summary>/// OPCServer IP地址/// 例:192.168.1.105/// </summary>public string OpcServerIP{get{//return (this.Main.ConnType as Mesnac.Equips.Connection.RSLinxOPC.ConnType).OpcServerIP;return "192.168.1.124";}}/// <summary>/// OPC服务名称/// 例:Kepware.KEPServerEX.V5/// </summary>public string OpcServerName{get{//return (this.Main.ConnType as Mesnac.Equips.Connection.RSLinxOPC.ConnType).OpcServerName;return "RSLinx OPC Server";}}/// <summary>/// 通道名称Topic/// 例:chnlSiemens/// </summary>public string ChannelName{get{//return (this.Main.ConnType as Mesnac.Equips.Connection.RSLinxOPC.ConnType).ChannelName;return "demo1";}}#endregionpublic 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); //连接OPCServerif (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);int rate = this.Main.ReadHz / 2 > 0 ? this.Main.ReadHz / 2 : 1;this.asyncRefrGroup = new RefreshGroup(myOPCServer, dch, rate);#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);tagName = String.Format("[{0}],L{1},C1", group.Start, group.Len);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 == 0 ? this.stepLen : group.Len % this.stepLen) - 1);tagName = String.Format("[{0}],L{1},C1", group.Start + (i * this.stepLen), group.Len % this.stepLen == 0 ? this.stepLen : group.Len % this.stepLen);currLen = group.Len % this.stepLen;}else{//tagName = String.Format("{0}-{1}", group.Start + (i * this.stepLen), group.Start + (i * this.stepLen) + this.stepLen - 1);tagName = String.Format("[{0}],L{1},C1", group.Start + (i * this.stepLen), this.stepLen);currLen = this.stepLen;}//N100[0],L10,C1string tagFullName = String.Format("{0}{1}{2}", groupNamePrefix, group.Block, tagName);//[demo1]N100[0],L10,C1if (!this.readResult.ContainsKey(tagFullName)){Console.WriteLine(tagFullName);short[] groupData = new short[currLen];this.readResult[tagFullName] = groupData;this.Add2RefrGroup(String.Format("[{0}]{1}", this.ChannelName, 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块,例如:N100或者DB100List<short> groupData = new List<short>();//N100[0],L10,C1string[] keys = this.readResult.Keys.Where(o => o.StartsWith(groupName) && o.Replace(String.Format("{0}", groupName), String.Empty).StartsWith("[")).OrderBy(c => c).ToArray<string>(); ;foreach (string key in keys){if (String.IsNullOrEmpty(startTag)){startTag = key.Replace(String.Format("{0}", groupName), String.Empty);}short[] values;if (this.readResult[key] is short[]){values = this.readResult[key] as short[];}else{values = new short[] { (short)this.readResult[key] };}groupData.AddRange(values);}buff = new object[len];int startIndex = 0;string strStartIndex = startTag.Substring(1, 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){//[demo1]N100[0],L10,C1itemId = String.Format("[{0}]{1}", this.ChannelName, 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;}#endregionConsole.WriteLine("-----------------------1");#region 按块写入#region 先读取相应标签数数据string startTag = String.Empty;string groupName = String.Format("{0}{1}", this.groupNamePrefix, block); //要读取的OPCServer块List<short> groupData = new List<short>();//N100[0],L10,C1string[] keys = readResult.Keys.Where(o => o.StartsWith(groupName) && o.Replace(String.Format("{0}", groupName), String.Empty).StartsWith("[")).OrderBy(c => c).ToArray<string>();foreach (string key in keys){if (this.readResult[key] is Array){groupData.AddRange(this.readResult[key] as short[]);}//Console.WriteLine(key);if (String.IsNullOrEmpty(startTag)){startTag = key.Replace(String.Format("{0}", groupName), String.Empty);}string[] beginEnd = new string[2];string tagName = key.Replace(String.Format("{0}", groupName), String.Empty);beginEnd[0] = tagName.Substring(1, tagName.IndexOf("]") - 1);beginEnd[1] = tagName.Replace(",C1", String.Empty).Substring(tagName.IndexOf("L") + 1);int begin = 0;int end = 0;int.TryParse(beginEnd[0], out begin);int.TryParse(beginEnd[1], out end);end += begin - 1;#region 写入之前,先读取一下PLC的值if ((start >= begin && start <= end) || ((start + buff.Length - 1) >= begin && (start + buff.Length - 1) <= end) || (begin > start && (start + buff.Length - 1) < end)){ItemDef itemData = this.readWriteGroup.Item(String.Format("[{0}]{1}", this.ChannelName, key));if (itemData == null){Console.WriteLine(String.Format("标签变量[{0}]未添加到数据读写组中!", String.Format("[{0}]{1}", this.ChannelName, 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}", this.ChannelName, key), error));return false;}if (itemState.DataValue is Array){//groupData.AddRange(itemState.DataValue as short[]);short[] source = itemState.DataValue as short[];for (int i = begin; i <= end; i++){groupData[i] = source[i - begin];}}else{Console.WriteLine(String.Format("标签变量[{0}]的长度未指定!", String.Format("[{0}]{1}", this.ChannelName, key)));}}#endregion}#endregion#region 更新标签中对应的数据后,再写回OPCServerint startIndex = 0;string strStartIndex = startTag.Substring(1, startTag.IndexOf("]") -1);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;}string[] keys2 = readResult.Keys.Where(o => o.StartsWith(groupName) && o.Replace(String.Format("{0}", groupName), String.Empty).StartsWith("[")).OrderBy(c => c).ToArray<string>();foreach (string key2 in keys2){int begin = 0;int end = 0;string[] beginEnd = new string[2];string tagName = key2.Replace(String.Format("{0}", groupName), String.Empty);beginEnd[0] = tagName.Substring(1, tagName.IndexOf("]") - 1);beginEnd[1] = tagName.Replace(",C1", String.Empty).Substring(tagName.IndexOf("L") + 1);int.TryParse(beginEnd[0], out begin);int.TryParse(beginEnd[1], out end);end += begin - 1;if ((start >= begin && start <= end) || ((start + buff.Length - 1) >= begin && (start + buff.Length - 1) <= end) || (begin > start && (start + buff.Length - 1) < end)){ItemDef itemData = this.readWriteGroup.Item(String.Format("[{0}]{1}", this.ChannelName, key2));if (itemData == null){Console.WriteLine(String.Format("写入失败:标签变量[{0}]未添加到数据读写组中!", String.Format("[{0}]{1}", this.ChannelName, key2)));return false;}if (!(this.readResult[key2] is Array)){Console.WriteLine(String.Format("标签变量[{0}]的长度未指定!", String.Format("[{0}]{1}", this.ChannelName, key2)));return false;}int len = (this.readResult[key2] as short[]).Length;short[] tagDataBuff = new short[len];Array.Copy(newDataBuffer, begin, tagDataBuff, 0, 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}", this.ChannelName, key2), error));return false;}else{Console.WriteLine("写入...");return true;}}}#endregion#endregionreturn 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}]", this.ChannelName), 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.OPC.OpcRSLinx.RSLinxOPC;namespace TestWinApp
{public partial class FrmOpcUaTester : Form{private Equip thisEquip = new Equip();public FrmOpcUaTester(){InitializeComponent();}private void button1_Click(object sender, EventArgs e){this.thisEquip.Main.ReadHz = 1000;this.thisEquip.Group.Clear();Mesnac.Equips.BaseInfo.Group group100 = new Mesnac.Equips.BaseInfo.Group();group100.Name = "N100";group100.Block = "100";group100.Start = 0;group100.IsAutoRead = true;group100.Len = 100;this.thisEquip.Group.Add(group100.Name, group100);Mesnac.Equips.BaseInfo.Group group101 = new Mesnac.Equips.BaseInfo.Group();group101.Name = "N101";group101.Block = "101";group101.Start = 0;group101.IsAutoRead = true;group101.Len = 100;this.thisEquip.Group.Add(group101.Name, group101);bool result = this.thisEquip.Open();Console.WriteLine("Connect result = " + result);}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 button4_Click(object sender, EventArgs e){this.thisEquip.Close();}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 FrmOpcUaTester_Load(object sender, EventArgs e){}private void FrmOpcUaTester_FormClosing(object sender, FormClosingEventArgs e){this.thisEquip.Close();}private void button8_Click(object sender, EventArgs e){}}
}
3、运行界面如下