最近项目中需要 C#通过OPC方式获取数据,折腾了一段时间,第三方提供OPCServer虚拟环境,我创建C#客户端。
服务端是力控电子系统,第三方软件,不多做赘述,本文主要讲解OPC客户端的搭建和OPC连接步骤。
调用OPCAutomation类访问OPCServer端,并进行可控制读取间隔的OPC数据读取、存储工作。
本文主要介绍OPCAutomation类的使用。
简单流程如下:
1.创建OPC server的连接
2.创建OPC组对象并初始化设置
3.获取组的OPCItems对象,为读取数据做准备
4.opcltem的操作
5.退出程序的资源释放
创建连接很简单,需要指定OPCServer所在的服务器(内网可以指定IP或者计算机名),指定OPC服务的名称(同一服务器可能运行多个OPC服务以适配不同的下位机)
//通过timer获取OPCServer端数据
void TimerTick(object sender, EventArgs e)
{
//连接OPCServer
KepServer.Connect(remoteServerName, remoteServerIP);
if (KepServer.ServerState == (int)OPCServerState.OPCRunning)
{
label1.Text = "已连接到-" + KepServer.ServerName + " ,starttime:" + KepServer.StartTime.ToString();
//创建并设置组
bool bSucce = this.CreateAndSetGroup();
//goup添加items
AddGroupItems();
//采集opc数据
GetOPCData();
}else{
label1.Text = "OPC服务器状态异常: " + KepServer.ServerState.ToString();
}
}
其中KepServer.Connect(remoteServerName, remoteServerIP);即OPCAutomation类提供的连接方法。
需要注意的是,在实际配置时,需要完全对OPC服务端所在服务器上配置防火墙出入站规则后,OPC服务才能够被其它服务器上的中间件访问到。
创建组相当于读取OPC上特定的项目,而具体的数据值是在每个项目下根据开发人员的定义而确定。
//创建组并设置组信息
private bool CreateAndSetGroup()
{
try
{
KepGroups = KepServer.OPCGroups;
KepGroup = KepGroups.Add("PCAuto.OPCServer");
KepServer.OPCGroups.DefaultGroupIsActive = true;//激活组
KepServer.OPCGroups.DefaultGroupDeadband = 0;// 死区值,设为0时,服务器端该组内任何数据变化都通知组
KepGroup.IsSubscribed = true;//使用订阅功能,即可以异步,默认false
//绑定写操作的事件
KepGroup.AsyncReadComplete += new DIOPCGroupEvent_AsyncReadCompleteEventHandler(GroupAsyncReadComplete);
KepItems = KepGroup.OPCItems;
}
catch (Exception ex)
{
}
return true;
}
这段代码是为OPCGroup对象进行初始化。PCAuto.OPCServer为组名,激活组,使用订阅功能,绑定写操作的时间等等。
异步读完成 运行时,Array数组从下标1开始而非0!
//异步读完成 运行时,Array数组从下标1开始而非0!
void GroupAsyncReadComplete(int TransactionID, int NumItems, ref System.Array ClientHandles, ref System.Array ItemValues, ref System.Array Qualities, ref System.Array TimeStamps, ref System.Array Errors)
{
try
{
//C# Dictionary 字典
Dictionary<string, string> tagValueMap = new Dictionary<string, string>();
for (int i = 1; i <= NumItems; i++)
{
string clientHandle = ClientHandles.GetValue(i).ToString();
string tag = MAP_CLIENTHANDLE_TAG[clientHandle];
string val = ItemValues.GetValue(i).ToString();
//C# Dictionary 字典 添加数据
tagValueMap.Add(tag, val);
}
//构建json
string json = ParseOPCData(tagValueMap);
}
catch (Exception ex)
{
}
}
为全局变量OPCGroup添加items,在OPC中,每个opcltem会被分配一个客户端句柄值,同时会被配置上服务器句柄。几乎所有有关获取指定Opcltem对象的方法均要求指定客户端句柄值。
上图中有一C# Dictionary 字典,暂不做赘述,待下回分解。
//goup添加items
string[] sensorIdArray;
void AddGroupItems()
{
List<string> l_str = new List<string>();
//读取配置文件,获取需要的传感器ID
string sensorIdList = getSensorId();
sensorIdArray = sensorIdList.Replace("\r\n","").Split(',');
foreach (string sensorId in sensorIdArray){
l_str.Add(sensorId+"_COS");
l_str.Add(sensorId+"_DL1");
l_str.Add(sensorId+"_DL2");
l_str.Add(sensorId+"_DL3");
l_str.Add(sensorId+"_DL4");
l_str.Add(sensorId+"_IA");
l_str.Add(sensorId+"_IB");
l_str.Add(sensorId+"_IC");
l_str.Add(sensorId+"_P");
l_str.Add(sensorId+"_Q");
l_str.Add(sensorId+"_UAB");
l_str.Add(sensorId+"_UBC");
l_str.Add(sensorId+"_UCA");
}
List<OPCItem> ItemsAdded = new List<OPCItem>();
int n = 0;
MAP_CLIENTHANDLE_TAG = new Dictionary<string, string>();
foreach (string tag in l_str){
ItemsAdded.Add(KepItems.AddItem(tag + ".PV", n));
//clientHandle tag关系
MAP_CLIENTHANDLE_TAG.Add(n + "", tag);
n++;
}
OPC_ITEMS_ADDED = ItemsAdded.ToArray();
}
OPCServer服务端数据源item
这段代码设计为了获取到全部的OPCltem所添加到所创建的OPCGroup对象的opcitem集合中,得到添加到goup中的item(OPC_ITEMS_ADDED)。
通过OPC_ITEMS_ADDED采集opc数据
//采集opc数据
private void GetOPCData()
{
try
{
//异步读opc数据
int[] temp = new int[OPC_ITEMS_ADDED.Length + 1];
temp[0] = 0;
for (int i = 1; i <= OPC_ITEMS_ADDED.Length; i++)
{
temp[i] = OPC_ITEMS_ADDED[i - 1].ServerHandle;
}
Array serverHandles = (Array)temp;
Array Errors;
int cancelID;
Random rd = new Random();
int TransactionID = rd.Next(1, 10);
KepGroup.AsyncRead(serverHandles.Length - 1, ref serverHandles, out Errors, TransactionID, out cancelID);//第一参数为item数量
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
解析OPC数据并以json格式存储在本地
//解析opc数据
private string ParseOPCData(Dictionary<string, string> tagValueMap)
{
string filename = "E:\\mine\\下峪口\\data\\electric_data.txt";
StringBuilder builder = new StringBuilder();
string rowStr = "";
string time = ConvertDateTimeToInt(DateTime.Now).ToString();
rowStr += "[";
foreach (string sensorId in sensorIdArray){
rowStr += "{\r\n\"id\":"+ "\"" + sensorId + "\",\r\n";
rowStr += "\"substation\":"+"\"假变电所\""+",\r\n";
rowStr += "\"timestamp\":"+time+",\r\n";
foreach (var item in tagValueMap)
{
string key = item.Key;
string[] data = item.Key.Split('_');
string title = data[0];
string word = data[1];
if(title.Equals(sensorId)){
rowStr += "\""+ word + "\""+ ":" + item.Value + ",\r\n";
}
}
rowStr += "},";
}
rowStr += "]";
rowStr = rowStr.Replace(",\r\n}","\r\n}").Replace(",]","]");
builder.Append(rowStr);
File.WriteAllText(@filename, builder.ToString(), Encoding.GetEncoding("gb2312"));
return "";
}
来源:oschina
链接:https://my.oschina.net/u/4006148/blog/2872746