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";
}
}
#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);
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,C1
string tagFullName = String.Format("{0}{1}{2}", groupNamePrefix, group.Block, tagName);
//[demo1]N100[0],L10,C1
if (!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或者DB100
List<short> groupData = new List<short>();
//N100[0],L10,C1
string[] 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,C1
itemId = 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;
}
#endregion
Console.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,C1
string[] 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 更新标签中对应的数据后,再写回OPCServer
int 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
#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}]", 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、运行界面如下