在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
[摘要]:
[关键词]: 运用VC#编程通过OPC方式实现PC机与西门子PLC通讯(下) --异步通讯篇 吴向阳 ,可编程序控制器,自动化软件
/// /// /// /// public bool AddGroup(string groupName,int bActive,int updateRate,out string error) { error="";bool success=true; int dwLCID = 0x407; //本地语言为英语 int pRevUpdateRate; float deadband = 0; // 处理非托管COM内存 GCHandle hDeadband; IntPtr pTimeBias = IntPtr.Zero; hDeadband = GCHandle.Alloc(deadband,GCHandleType.Pinned); try { pIOPCServer.AddGroup(groupName, //组名 bActive, //创建时,组是否被激活 updateRate, //组的刷新频率,以ms为单位 hClientGroup, //客户号 pTimeBias, //这里不使用 (IntPtr)hDeadband, dwLCID, //本地语言 out nSvrGroupID, //移去组时,用到的组ID号 out pRevUpdateRate, //返回组中的变量改变时的最短通知时间间隔 ref iidRequiredInterface, out pobjGroup1); //指向要求的接口 hClientGroup=hClientGroup+1; groupStru grp=new groupStru(); grp.groupID=nSvrGroupID;grp.groupObj=pobjGroup1; this.hashGroup.Add(groupName,grp);//储存组信息 // 对异步操作设置回调,初始化接口 pIConnectionPointContainer = (IConnectionPointContainer)pobjGroup1; Guid iid = typeof(IOPCDataCallback).GUID; pIConnectionPointContainer.FindConnectionPoint(ref iid,out pIConnectionPoint); pIConnectionPoint.Advise(this,out dwCookie); } catch (System.Exception err) //捕捉失败信息 { error="错误信息:"+err.Message;success=false; } finally { if (hDeadband.IsAllocated) hDeadband.Free(); } return success; } 6、 编写激活、或者取消激活组的函数 在同步编程中对于组的激活或者取消激活没有实质的意义,但在异步通讯编程中却异常重要,这是因为OPC服务器只对当前处于活动状态的组中的变量进行监控,同时这也是很有必要的,因为我们可以把不同界面中的变量编程不同的组,即同一界面中的变量规成一个组,而在某一时刻提供给用户的只有一个界面,让该界面中用到的组处于活动状态,这样执行委托调用时只会执行于该界面中有关的变量检测,而如果让所有的组处于活动状态,则当前没有显示给用户的界面用到的变量若发生变化也会触发对委托函数的调用,这根本是没有必要的,同时会大大降低程序的性能,请严格控制组的激活。 /// /// 激活或者取消激活组 /// /// /// /// /// public bool AciveGroup(string groupName,bool toActive,out string error) { error="";bool success=true; //通过名称获取组 object grp=((groupStru)hashGroup[groupName]).groupObj; IOPCGroupStateMgt groupStateMgt=(IOPCGroupStateMgt)grp; //初始化传递参数 IntPtr pRequestedUpdateRate = IntPtr.Zero; //由客户指定的Item更新间隔时间 int nRevUpdateRate = 0; //由服务器返回的能够更新的最短时间间隔 IntPtr hClientGroup = IntPtr.Zero; //客户组 IntPtr pTimeBias = IntPtr.Zero; IntPtr pDeadband = IntPtr.Zero; IntPtr pLCID = IntPtr.Zero; // 激活或者取消激活组 int nActive = 0; GCHandle hActive = GCHandle.Alloc(nActive,GCHandleType.Pinned); if(toActive) hActive.Target = 1; else hActive.Target = 0; try { groupStateMgt.SetState(pRequestedUpdateRate,out nRevUpdateRate,hActive.AddrOfPinnedObject(),pTimeBias,pDeadband,pLCID,hClientGroup); } catch(System.Exception err) { error="错误信息:"+err.Message;success=false; } finally { hActive.Free(); } return success; } 7、 向指定的组中添加变量的函数 /// /// 向指定的组添加一系列项 /// /// /// /// /// public bool AddItems(string groupName,string[] itemsName,int[] itemsID) { bool success=true; OPCITEMDEF[] ItemDefArray=new OPCITEMDEF[itemsName.Length]; for(int i=0;i hClientItem=hClientItem+1; //客户项自动加1 ItemDefArray[i].szAccessPath = ""; // 可选的通道路径,对于Simatiic Net不需要。 ItemDefArray[i].szItemID = itemsName[i]; // ItemID, see above ItemDefArray[i].bActive = 1; // item is active ItemDefArray[i].hClient = hClientItem; // client handle ,在OnDataChange中会用到 ItemDefArray[i].dwBlobSize = 0; // blob size ItemDefArray[i].pBlob = IntPtr.Zero; // pointer to blob ItemDefArray[i].vtRequestedDataType = 4; //DWord数据类型 } //初始化输出参数 IntPtr pResults = IntPtr.Zero; IntPtr pErrors = IntPtr.Zero; try { // 添加项到组 object grp=((groupStru)hashGroup[groupName]).groupObj; ((IOPCItemMgt)grp).AddItems(itemsName.Length,ItemDefArray,out pResults,out pErrors); int[] errors = new int[itemsName.Length]; IntPtr pos = pResults; Marshal.Copy(pErrors, errors, 0,itemsName.Length); for(int i=0;i if (errors[i] == 0) { OPCITEMRESULT result = (OPCITEMRESULT)Marshal.PtrToStructure(pos, typeof(OPCITEMRESULT)); itemsID[i] = result.hServer; pos = new IntPtr(pos.ToInt32() + Marshal.SizeOf(typeof(OPCITEMRESULT))); } else { String pstrError; pIOPCServer.GetErrorString(errors[0],0x407,out pstrError); success=false; break; } } SetItenClient(groupName,itemsID,itemsID); //要求始终只有一个组被激活,才不会引起冲突。 } catch (System.Exception err) // catch for error in adding items. { success=false; //error="错误信息:"+error+err.Message; } finally { // 释放非托管内存 if(pResults != IntPtr.Zero) { Marshal.FreeCoTaskMem(pResults); pResults = IntPtr.Zero; } if(pErrors != IntPtr.Zero) { Marshal.FreeCoTaskMem(pErrors); pErrors = IntPtr.Zero; } } return success; } 说明:使用该函数时,在类的开头,应该先声明整数数据,以用于保存由本函数返回的服务器对每一项分配的Item ID号: 8、 下面编写的是一个最重要的重载函数,当检测到当前活动组中的某个变量发生变化时,就会调用委托。 //数据变化时处理的问题 public virtual void OnDataChange ( Int32 dwTransid , Int32 hGroup , Int32 hrMasterquality , Int32 hrMastererror , Int32 dwCount , int[] phClientItems , object[] pvValues , short[] pwQualities , OpcRcw.Da.FILETIME[] pftTimeStamps , int[] pErrors ) { dtChange(pvValues,phClientItems); } 该函数的代码只有一句,即调用委托函数。 以上编写的是需要实现监控的最重要的方法,当然不完善,还有许多方法和重载函数可以编写,这里就不详细介绍。 9、 编写基本的测试程序,用于检测上面编写的异步类AsynServer <1>、 重新创建一个工程,添加对上面编写的异步类的引用,并在类的开头部分添加变量声明: //声明委托 private S7Connection.DataChange dt; //声明服务器 S7Connection.AsynServer server; <2>、初始化服务器数据 dt=new S7Connection.DataChange(DataChange); server =new AsynServer(S7Connection.ServerType.OPC_SimaticNET,dt); string err; server.Open(out err); server.AddGroup("maiker",1,300,out err); server.AddItems("maiker",m1,nt1); server.AddGroup("maiker1",1,300,out err); server.AddItems("maiker1",m2,nt2); nt[0]=nt1[0];nt[1]=nt1[1]; <3>、添加两个单选按钮,用于选择某个组,并编写相应的程序 string err,err1; if(server==null) return; if(radioButton1.Checked) { nt[0]=nt1[0];nt[1]=nt1[1]; server.AciveGroup("maiker",true,out err); server.AciveGroup("maiker1",false,out err1); } else { nt[0]=nt2[0];nt[1]=nt2[1]; server.AciveGroup("maiker1",true,out err); server.AciveGroup("maiker",false,out err1); } <4>、添加文本框、按钮等,并编写委托执行函数: private void DataChange(object[] obj,int[] itemsID) { for(int j=0;j if(itemsID[j]==nt[0]) this.textBox1.Text=obj[j].ToString(); if(itemsID[j]==nt[1]) this.textBox4.Text=obj[j].ToString(); } } 其中参数obj用于返回当前发生变化的变量的结果值,而itemsID返回当前发生变化的变量的ID号,其与添加变量时服务器返回的ID号对应。以上就是一个基本的测试函数,其相对同步编程来说,应该还简单一些。 3、 同步编程与异步编程的使用场合 一般来讲,同步编程需要使用定时器来循环检测变量,而异步编程则不需要,当服务器检测到数据发生变化时,可以直接调用传入的函数,从这方面来讲,使用异步编程更简单一些,但同步编程使用外部的定时器控制,编程则会更加灵活,一般只监控变量时可以使用异步编程,而当需要写入数据时可以使用同步编程,但这也不是绝对的,我曾编写了一个标准监控程序,没有使用异步编程。 4、 关于开发监控界面的说明 毫无疑问,我们应该开发一系列控件,用于简化界面的设计,否则工作量会异常大。设计一个标准模块,用于第一次运行监控软件时添加变量,并可以设定当前已经组态的界面中的各控件元素与之关联,这样在以后再运行该软件时,不需要再设定,就可以直接连接变量,并进行相应的变化。否则若在编程时编写代码进行关联,其工作量将会异常大。 其实该类我早已经开发了,但一直没有时间写成文章,本来想开发一系列标准控件和标准模块,但由于换到上海工作,可能不会再有时间搞这方面的研究了。 作者:吴向阳 [email protected] |
请发表评论