上篇讲到了OPC的基础知识,了解到OPC分为3个版本。本篇将介绍如何通过OPC Foundation提供的opc库实现客户端访问DA服务器(例子代码来源于外文blog,特此声明)。
准备工作
下载
Opc.NET Api,可以从
OPC Foundation网站,也可以google,csdn上也有。下载到安装包之后,安装即可,相应的文件会拷贝到
“C:\Windows\assembly\GAC_MSIL\OpcNetApi\2.0.2.0__9a40e993cbface53\OpcNetApi.dll”
“C:\Windows\assembly\GAC_MSIL\OpcNetApi.Com\2.0.1.0__9a40e993cbface53\OpcNetApi.Com.dll”
...
这样在这台电脑上的所有.net项目都可以使用
Opc.NET Api了。但这也有不方便的地方,如果是团队开发,那么就要求团队所有人都必须按照这个库。那么你也可以把这些dll从GAC里面放到你工程的第三方库中,然后让工程的引用指向那里就可以了。
BTW:只要在本地新建个指向本机”C$\Windows
“的网络映射,然后通过这个映射就可以任性的操作这些文件了。
新建工程
打开VS,新建c#工程,并添加OpcNetApi.dll,OpcNetApi.Com.dll的引用
读写数据项(Item)
static void Main( string [] args)
{
// Create a server object and connect to the TwinCATOpcServer
Opc. URL url = new Opc. URL ( "opcda://localhost/BECKHOFF.TwinCATOpcServerDA" );
Opc.Da. Server server = null ;
OpcCom. Factory fact = new OpcCom. Factory ();
server = new Opc.Da. Server (fact, null );
server.Connect(url, new Opc. ConnectData ( new System.Net. NetworkCredential ()));
// Create a group
Opc.Da. Subscription group;
Opc.Da. SubscriptionState groupState = new Opc.Da. SubscriptionState ();
groupState.Name = "Group" ;
groupState.Active = false ;
group = (Opc.Da. Subscription )server.CreateSubscription(groupState);
// add items to the group
Opc.Da. Item [] items = new Opc.Da. Item [3];
items[0] = new Opc.Da. Item ();
items[0].ItemName = "PLC1.Value1" ;
items[1] = new Opc.Da. Item ();
items[1].ItemName = "PLC1.Value2" ;
items[2] = new Opc.Da. Item ();
items[2].ItemName = "PLC1.Value3" ;
items = group.AddItems(items);
// write items
Opc.Da. ItemValue [] writeValues = new Opc.Da. ItemValue [3];
writeValues[0] = new Opc.Da. ItemValue ();
writeValues[1] = new Opc.Da. ItemValue ();
writeValues[2] = new Opc.Da. ItemValue ();
writeValues[0].ServerHandle = group.Items[0].ServerHandle;
writeValues[0].Value = 0;
writeValues[1].ServerHandle = group.Items[1].ServerHandle;
writeValues[1].Value = 0;
writeValues[2].ServerHandle = group.Items[2].ServerHandle;
writeValues[2].Value = 0;
Opc. IRequest req;
group.Write(writeValues, 321, new Opc.Da. WriteCompleteEventHandler (WriteCompleteCallback), out req);
// and now read the items again
group.Read(group.Items, 123, new Opc.Da. ReadCompleteEventHandler (ReadCompleteCallback), out req);
Console .ReadLine();
}
static void WriteCompleteCallback( object clientHandle, Opc. IdentifiedResult [] results)
{
Console .WriteLine( "Write completed" );
foreach (Opc. IdentifiedResult writeResult in results)
{
Console .WriteLine( "\t{0} write result: {1}" , writeResult.ItemName, writeResult.ResultID);
}
Console .WriteLine();
}
static void ReadCompleteCallback( object clientHandle, Opc.Da. ItemValueResult [] results)
{
Console .WriteLine( "Read completed" );
foreach (Opc.Da. ItemValueResult readResult in results)
{
Console .WriteLine( "\t{0}\tval:{1}" , readResult.ItemName, readResult.Value);
}
Console .WriteLine();
}
这里有个要注意的地方,在创建
Opc.Da.Subscription
的时候,会设置其Name属性,这个名称仅仅是给
Subscription
个名字,与OPC服务器中的组(group)名是两回事。
同时,在创建Item的时候,如果它是属于某个group的话,在设置其ItemName属性的时候,需要加上group的名字。
下面以MatrikonOPC Server for Simulation为例,如下图。当中有个名为"Group"的组,其下面有名为“Item”数据项,那么连接这个服务器,创建Item的时候,Item的名字应该是“
New_Intouch_000.Item
”。否则连接不上的,但是大小写貌似不区分的。
监视数据项变化(subscriptions)
所谓监视数据项变化,就是添加关心的数据项,也可设定更新频率,然后当这些数据项的值变化时就会通知你(事件机制)。
static void Main(string[] args)
{
// Create a server object and connect to the TwinCATOpcServer
Opc.URL url = new Opc.URL("opcda://localhost/BECKHOFF.TwinCATOpcServerDA");
Opc.Da.Server server = null;
OpcCom.Factory fact = new OpcCom.Factory();
server = new Opc.Da.Server(fact, null);
server.Connect(url, new Opc.ConnectData(new System.Net.NetworkCredential()));
// Create a group
Opc.Da.Subscription group;
Opc.Da.SubscriptionState groupState = new Opc.Da.SubscriptionState();
groupState.Name = "Group";
groupState.Active = true;
group = (Opc.Da.Subscription)server.CreateSubscription(groupState);
// add items to the group.
Opc.Da.Item[] items = new Opc.Da.Item[3];
items[0] = new Opc.Da.Item();
items[0].ItemName = "PLC1.dyn_R8[1]";
items[1] = new Opc.Da.Item();
items[1].ItemName = "PLC1.dyn_R4[1]";
items[2] = new Opc.Da.Item();
items[2].ItemName = "PLC1.dyn_I4[1]";
items = group.AddItems(items);
group.DataChanged += new Opc.Da.DataChangedEventHandler(OnTransactionCompleted);
Console.ReadLine();
}
static void OnTransactionCompleted( object group, object hReq, Opc.Da.ItemValueResult[] items)
{
Console.WriteLine("------------------->");
Console.WriteLine("DataChanged ...");
for (int i = 0; i < items.GetLength(0); i++)
{
Console.WriteLine("Item DataChange - ItemId: {0}", items[i].ItemName);
Console.WriteLine(" Value: {0,-20}", items[i].Value);
Console.WriteLine(" TimeStamp: {0:00}:{1:00}:{2:00}.{3:000}",
items[i].Timestamp.Hour,
items[i].Timestamp.Minute,
items[i].Timestamp.Second,
items[i].Timestamp.Millisecond);
}
Console.WriteLine("-------------------<");
}
如果需要撤销监视,可以通过“Array.Clear(subscription.Items,0,subscription.Items.Length)”的方式。此外添加监视必须调用Subscription.AddItems函数,通过直接修改Subscription.Items的方式貌似无效。
“OnTransactionCompleted”第一次被调用会传回所有监视项(很好理解,如果都没有变化,你怎么知道当前是什么值),第二次及以后
传回的是监视的数据项中变化项。