由于项目的需要,上位机(工控机)需要与PLC通讯(AB的PLC)通过PLC控制相关的设备;另一部分需要做一个数据采集程序从一个OPC服务器(此服务器作为多个PLC数据的服务端用的是AB的RsLinx)采集一部分PLC的数据供另一个应用程序用。
由于之前没有接触过PLC,也没有听说过什么OPC,所以从网上找些资料,由于做的时候实际的环境可以调试,很多东西在写的时候都没啥底,最后在现场调试的时候有些东西才搞清楚,整个过程还是费了些周折,有些东西虽然还是没有完全弄懂,但基于这个项目的基本功能算是完成了。所以记录下来,以便以来有可能用得着,也给初次接触这块的同学提供些参考。
上位机基于C#写的,PLC是AB的,C#程序通过串口(RS232-485转换器)与PLC通讯,协议用的是标准modbus,程序发指令给PLC,PLC来控制电磁阀,继电器等工作。由于PLC本身没有采集数据,上位机还要把采集到的数据写到PLC,数据原本是float的,但传给PLC时转成small int(小于65536)的整数,PLC内部再作除法来还原float数据.
这部分基于串口通读,modbus协议倒时没费啥事。串口通讯可以用串口调试助手测试;
至于写数据有没有写成功可以用 ModScan32来看,连上对应的串口,设置好波特率,从机地址就可以看到默认100个寄存器的值。
PLC还有一个I/O口是编程用的,连接后用rslogix可以查看实时的状态,不过不大会用,看到PLC编程的工程师是这么用的。
数据采集程序与OPC服务器在同一局域网,但不同网段,可以Ping通,但在程序里就是连不上OPC服务器,提示RPC服务器不可用。在网上找资料说是要配置服务端及客户端的DCOM,于是照网上的方式把客户端的机器设置DCOM,但服务端控制不了,对方的工程师说已经配置好了,但死活还是连不上,提示还是一样。(所以远程访问的方式还是没有走通)
后来经过协商把采集程序安装到OPC服务器所在的机器上,在同一网段内果真可以连上,数据采集也没有问题,估计开始还是网络及权限方面的问题;采集程序也是根据网上找的改的,后面附上源码。数据采集到后,通过socket发到另一台机器。
采集程序源码如下:(C# vs2010)
引用 OpcNetApi.dll
OpcNetApi.Com.dll
这两个dll在安装Rslinx后安装目录下可以找到 ***\Rockwell Software\RSLinx\下
-----------------code begin--------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Opc;
using Opc.Da;
using OpcCom;
namespace OPC
{
///
/// opc服务器数据采集类
///
public class OPCSampling
{
private Opc.Da.Server m_server = null;//定义数据存取服务器
private Opc.Da.Subscription subscription1 = null;//定义组对象(订阅者)
private Opc.Da.SubscriptionState state1 = null;//定义组(订阅者)状态,相当于OPC规范中组的参数
private Opc.IDiscovery m_discovery = new OpcCom.ServerEnumerator();//定义枚举基于COM服务器的接口,用来搜索所有的此类服务器。
///
/// 类的启动函数,供外部调用
///
public void Work(string strHost,string strOPCServerName)
{
try
{
//查询服务器
Opc.Server[] servers = m_discovery.GetAvailableServers(Specification.COM_DA_20, strHost, null);
//daver表示数据存取规范版本,Specification.COMDA_20等于2.0版本。
//host为计算机名,null表示不需要任何网络安全认证。
if (servers != null)
{
//连上后在这里看下OPC服务的名称,记下来在系统配置里更改opc服务器服务名,重启程序再运行
SharedLibrary.Gloary.OPCServerList = "";
foreach (Opc.Da.Server server in servers)
{
SharedLibrary.Gloary.OPCServerList += server.Name + " \r\n";
//server即为需要连接的OPC数据存取服务器。
if (String.Compare(server.Name, strOPCServerName, true) == 0)//为true忽略大小写
{
m_server = server;//建立连接。
break;
}
}
if (SharedLibrary.Gloary.OPCServerList.Length < 1)
{
SharedLibrary.Gloary.OPCServerList = "未连接上OPC服务器或没有找到相应的OPC服务";
}
}
//连接服务器
if (m_server != null)//非空连接服务器
{
m_server.Connect();
}
else
{
return;
}
#region 组1
//设定组1状态
state1 = new Opc.Da.SubscriptionState();//组(订阅者)状态,相当于OPC规范中组的参数
state1.Name = "OPCDataSampling1";//组名
state1.ServerHandle = null;//服务器给该组分配的句柄。
state1.ClientHandle = Guid.NewGuid().ToString();//客户端给该组分配的句柄。
state1.Active = true;//激活该组。
state1.UpdateRate = 30000;//刷新频率为1秒。
state1.Deadband = 0;// 死区值,设为0时,服务器端该组内任何数据变化都通知组。
state1.Locale = null;//不设置地区值。
//添加组
subscription1 = (Opc.Da.Subscription)m_server.CreateSubscription(state1);//创建组1
//定义Item列表
//对应类型为:{Byte,Byte,Char,Short,String,Word,Boolean}
string[] itemName = { "[27]F8:8", "[27]F8:9", "[21]F8:1", "[21]F8:2", "[24]N9:0", "[21]F8:4", "[25]F8:1", "[25]F8:0", "[25]F8:5" };
//定义item列表
Item[] items = new Item[9];//定义数据项,即item
int i;
for (i = 0; i < items.Length; i++)//item初始化赋值
{
items[i] = new Item();//创建一个项Item对象。
items[i].ClientHandle = Guid.NewGuid().ToString();//客户端给该数据项分配的句柄。
items[i].ItemPath = null; //该数据项在服务器中的路径。
items[i].ItemName = itemName[i]; //该数据项在服务器中的名字。
}
//添加Item
subscription1.AddItems(items);
//注册回调事件
subscription1.DataChanged += new Opc.Da.DataChangedEventHandler(this.OnDataChange1);
////以下测试同步读
////以下读整个组
//ItemValueResult[] values = subscription1.Read(subscription1.Items);
////以下遍历读到的全部值
//foreach (ItemValueResult value in values)
//{
// SharedLibrary.Gloary.OPCServerList += "ItemName=" + value.ItemName + "\r\n";
// SharedLibrary.Gloary.OPCServerList += "ItemValue=" + value.Value.ToString() + "\r\n";
// SharedLibrary.LogMs.Debug("ItemName=" + value.ItemName);
// SharedLibrary.LogMs.Debug("ItemValue=" + value.Value);
//}
#endregion
////取消回调事件
//subscription.DataChanged -= new Opc.Da.DataChangedEventHandler(this.OnDataChange);
////移除组内item
//subscription.RemoveItems(subscription.Items);
////结束:释放各资源
//m_server.CancelSubscription(subscription);//m_server前文已说明,通知服务器要求删除组。
//subscription.Dispose();//强制.NET资源回收站回收该subscription的所有资源。
//m_server.Disconnect();//断开服务器连接
}
catch (Exception ex1)
{
SharedLibrary.Gloary.OPCServerList += " 异常:"+ex1.Message.ToString();
}
}
///
/// DataChange回调
///
///
///
///
public void OnDataChange1(object subscriptionHandle, object requestHandle, ItemValueResult[] values)
{
//OnDataChangeDeal("1", subscriptionHandle, requestHandle, values);
string siteID = "";
MonitorCenterWS60.BLL.SiteBaseEMData bll = new MonitorCenterWS60.BLL.SiteBaseEMData();
foreach (ItemValueResult item in values)
{
try
{
MonitorCenterWS60.Model.SiteBaseEMData model = new MonitorCenterWS60.Model.SiteBaseEMData();
#region 针对itemname作数据处理
switch (item.ItemName)
{
case "[27]F8:8":
//f8:8 送水余氯
siteID = "01618200210001";
model.ItemCode = "160"; //待指定
model.ItemValue = decimal.Parse(decimal.Parse(item.Value.ToString()).ToString("0.000"));
model.Memo = item.ItemName;
break;
case "[27]F8:9":
//f8:9 送水PH
siteID = "01618200210001";
model.ItemCode = "161"; //待指定
model.ItemValue = decimal.Parse(decimal.Parse(item.Value.ToString()).ToString("0.00"));
model.Memo = item.ItemName;
break;
//部分略
}
#endregion
#region 发送数据
if (SharedLibrary.Gloary.DataTransType == "1")
{
DataComm.DataSend.SendEMDataReal(model);
}
#endregion
}
catch
{ }
}
}
///
/// ReadComplete回调
///
///
///
public void OnReadComplete(object requestHandle, Opc.Da.ItemValueResult[] values)
{
/*Console.WriteLine("发生异步读name:{0},value:{1}", values[0].ItemName, values[0].Value);
if ((int)requestHandle == 1)
Console.WriteLine("事件信号句柄为{0}", requestHandle);*/
}
///
/// WriteComplete回调
///
///
///
public void OnWriteComplete(object requestHandle, Opc.IdentifiedResult[] values)
{
/*Console.WriteLine("发生异步写name:{0},value:{1}", values[0].ItemName, values[0].GetType());
if ((int)requestHandle == 2)
Console.WriteLine("事件信号句柄为{0}", requestHandle);*/
}
}
}
-----------------code end-----------------
需要注意的是服务名与item标签名一定要对,这个也可以用opc client来测试下,Rslinx提供了这个工具
在RxLinx/tools 里面有个 OPC Test Clent 打开后,添加组,再添加标签,也可以从下方的里面选择现有的数据项,添加后可以查看到实时数据;
疑问一:因为没有用RsLinx配置过OPC服务器,没明白OPC服务器上的数据项(server下面的group下的item)在哪配置,待搞清楚后再来更新,如果有知道的也请不吝赐教!!!谢谢!