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

来源:互联网 发布:矩阵扰动 编辑:程序博客网 时间:2024/04/30 11:10

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

背景

         由于项目的需要,上位机(工控机)需要与PLC通讯(ABPLC)通过PLC控制相关的设备;另一部分需要做一个数据采集程序从一个OPC服务器(此服务器作为多个PLC数据的服务端用的是ABRsLinx)采集一部分PLC的数据供另一个应用程序用。

 

         由于之前没有接触过PLC,也没有听说过什么OPC,所以从网上找些资料,由于做的时候实际的环境可以调试,很多东西在写的时候都没啥底,最后在现场调试的时候有些东西才搞清楚,整个过程还是费了些周折,有些东西虽然还是没有完全弄懂,但基于这个项目的基本功能算是完成了。所以记录下来,以便以来有可能用得着,也给初次接触这块的同学提供些参考。

上位机与PLC的通讯

上位机基于C#写的,PLCAB的,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

{

    /// <summary>

    /// opc服务器数据采集类

    /// </summary>

    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服务器的接口,用来搜索所有的此类服务器。

       

        /// <summary>

        /// 类的启动函数,供外部调用

        /// </summary>

        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();

            }

        }

 

        /// <summary>

        /// DataChange回调

        /// </summary>

        /// <param name="subscriptionHandle"></param>

        /// <param name="requestHandle"></param>

        /// <param name="values"></param>

        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

                { }

            }

        }

        /// <summary>

        /// ReadComplete回调

        /// </summary>

        /// <param name="requestHandle"></param>

        /// <param name="values"></param>

        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);*/

        }

 

        /// <summary>

        /// WriteComplete回调

        /// </summary>

        /// <param name="requestHandle"></param>

        /// <param name="values"></param>

        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)在哪配置,待搞清楚后再来更新,如果有知道的也请不吝赐教!!!谢谢!

 

 

 

 

0 0