您好,欢迎来到微智科技网。
搜索
您的当前位置:首页上位机与PLC通讯及OPC数据采集实践一

上位机与PLC通讯及OPC数据采集实践一

来源:微智科技网
上位机与PLC通讯及OPC数据采集实践一

背景

由于项目的需要,上位机(工控机)需要与PLC通讯(AB的PLC)通过PLC控制相关的设备;另一部分需要做一个数据采集程序从一个OPC服务器(此服务器作为多个PLC数据的服务端 用的是AB的RsLinx)采集一部分PLC的数据供另一个应用程序用。 由于之前没有接触过PLC,也没有听说过什么OPC,所以从网上找些资料,由于做的时候实际的环境可以调试,很多东西在写的时候都没啥底,最后在现场调试的时候有些东西才搞清楚,整个过程还是费了些周折,有些东西虽然还是没有完全弄懂,但基于这个项目的基本功能算是完成了。所以记录下来,以便以来有可能用得着,也给初次接触这块的同学提供些参考。

上位机与PLC的通讯

上位机基于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服务器的通讯与数据采集

数据采集程序与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\

//定义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

Opc.Da.DataChangedEventHandler(this.OnDataChange1);

+= new

////以下测试同步读 ////以下读整个组

//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 -= Opc.Da.DataChangedEventHandler(this.OnDataChange); ////移除组内item

//subscription.RemoveItems(subscription.Items);

new

////结束:释放各资源

//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\ 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}\ }

///

/// 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}\ } } }

-----------------code end-----------------

需要注意的是服务名与item标签名一定要对,这个也可以用opc client来测试下,Rslinx提供了这个工具

在RxLinx/tools 里面有个 OPC Test Clent 打开后,添加组,再添加标签,也可以从下方的里面选择现有的数据项,添加后可以查看到实时数据;

疑问一:因为没有用RsLinx配置过OPC服务器,没明白OPC服务器上的数据项(server下面的group下的item)在哪配置,待搞清楚后再来更新,如果有知道的也请不吝赐教!!!谢谢!

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- 7swz.com 版权所有 赣ICP备2024042798号-8

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务