/// <summary> /// 写入单个标签点的历史数据 /// 这个方法主要用于从csv文件导入历史数据到golden数据库中。从csv读取的数据是字符串型二维数组,因此这里的历史数据入参是字符串型二维数组,而不是List<PValue> /// </summary> /// <param name="tagname"></param> /// <param name="data"></param> /// <returns></returns> public int PutArchivedValues(string tagname, string[][] data) { //注意,只能向数据库第一个有效数据的时间之后,插入历史数据。 //——如果插入的值得历史时刻在第一个有效值前,则插入不返回错误,但是数据写不进去。 //准备数据 PISDK.PIValues pivalues = new PISDK.PIValues(); //新建PIValues,ReadOnly属性为true pivalues.ReadOnly = false; //只有将PIValues的readonly属性置为false,才能写入。 //数据点的描述,是一个NamedValues类型。该类型是一个集合。可以添加不同的描述项,并给项设定值 NamedValues nvatts = new NamedValues(); nvatts.Add("questionable", true); //将数据写入pivalues //——只使用第1个值即实时值,第2个值时间。 int i = 0; //try //{ for (i = 0; i < data.Length; i++) { try { //在PI导出的数据中,value有可能是字符串。有时候表示服务器启停,有时候表示“IO timeout”等,这些值都无法转换成数值型,必须跳过。 if (data[i] != null && data[i].Length != 0) { pivalues.Add(Convert.ToDateTime(data[i][2]), Convert.ToDouble(data[i][1]), nvatts); } } catch { continue; } } //} //catch (Exception ex) //{ // //将pi的异常PgimDataException记录在_exception中,并以PgimDataException的名称继续抛出异常 // this._exception = "写历史数据时,转换数据格式错误!错误行数"+i.ToString(); // throw ex; //将pgim的异常继续向外传递。 //} //使用piPoint.Data.UpdateValues更新点历史数据 //this._piPoint = new PISDK.PIPoint(); try { this._piPoint = this._piServer.PIPoints[tagname]; PISDKCommon.PIErrors pierror = this._piPoint.Data.UpdateValues(pivalues); //处理写入结果 return(pierror.Count); } catch (Exception ex) { //将pi的异常PgimDataException记录在_exception中,并以PgimDataException的名称继续抛出异常 this._exception = "PI其他未知错误。通常是服务器故障、标签中对应的服务器名称不正确导致!"; throw ex; //将pgim的异常继续向外传递。 return(-1); } }
public PValue GetActValue(string tagname) { List <PValue> pvalues = new List <PValue>(); //准备读值列表PIValues PISDK.PIValue pival; this._piPoint = this._piServer.PIPoints[tagname]; pival = this._piPoint.Data.Snapshot; //PI的快照值为只读 //未做完。待续 return(pvalues[0]); }
//历史值的读写 #region 读取历史数据原始值:public List<PValue> GetRawValues(string tagname, DateTime startdate, DateTime enddate)。重点函数,概化计算引擎用。 /// <summary> /// GetRawValues是实时库读取历史数据接口的对外封装。无论何种实时数据库,读取历史数据功能都是指,读取某一个指定标签(tagname)在一段时间内(startdate、enddate)的历史值(浮点型) /// 而不同的实时数据库,真正的读取方法和返回值的数据类型略有区别 /// 要求起始时刻和截止时刻,必须为精确的实时值。如果恰好有这两个时刻点的值,就直接取得。如果没有就采用插值。 /// 对于PI数据库,读取历史数据接口,当第三个参数设定为btInterp时,接口会自动的获取起始时刻和截止时刻的插值。 /// </summary> /// <param name="tagname">标签名</param> /// <param name="startdate">起始时间</param> /// <param name="enddate">结束时间</param> /// <returns>历史数据,PValue集合</returns> public List <PValue> GetRawValues(string tagname, DateTime startdate, DateTime enddate) { //经过测试,PI数据库读取原始数据时,在起始时刻点,和截止时刻点,可以通过参数设置实现以下几种方式: //在piPoint.Data.RecordedValues()中的第三个参数,BoundaryType,可以选择: //——auto,自动决定边界点 //——btInside,只取时间戳大于起始时间的点和时间戳小于截止时刻的点。 //——btOutside,取得实时数据包含起始时刻前一个有效时刻的值,和截止时刻后一个有效时刻的值。 //——btInterp,在起始时刻和截止时刻,进行插值。 //对于计算引擎,需要的就是btInterp这种方式,即在起始时刻和截止时刻,进行精确的插值 //如果已经取到数据库最早的时间值,则不再向前补值。比如数据最早是2018-1-2 00:00:00的值。取值周期起始时刻是2018-1-1。RecordedValues返回的第一个值是2018-1-2 00:00:00的,不会向前补值。 //为了在使用长连接时保险起见,这里仅检查是否连接。如果未连接就直接返回空。 if (this._piServer == null || !this._piServer.Connected) { return(null); //如果未登陆,直接返回空值 } //准备读值列表PIValues PIValues pivalues = new PIValues(); if (enddate <= startdate) { return(null); } try { this._piPoint = this._piServer.PIPoints[tagname]; pivalues = this._piPoint.Data.RecordedValues(startdate, enddate, BoundaryTypeConstants.btOutside); //BoundaryTypeConstants.btInterp采用边界取外值的办法,起始时刻取前值,截止时刻取后值 } catch (Exception ex) { //将pi的异常记录在_exception中,并以PgimDataException的名称继续抛出异常 this._exception = "PI其他未知错误。通常是服务器故障、标签中对应的服务器名称不正确导致!详细错误信息:" + ex.ToString(); throw ex; //将PI的异常继续向外传递。 } //PI的RecordedValues()接口,返回的数据,起始时刻和截止时刻的值,均为根据前后值插值得到。除此以外,PI还会返回其他标志值,比如停止,IO超驰等。 //所以,不能依据pivalues>=2,来判断是否包含了完整的起始时刻和截止时刻的值。 //如果count为0,直接返回空。外部程序可以根据pvalues==null来处理 if (pivalues == null || pivalues.Count == 0) { return(null); } //将pivalues转换为Pvalue值 //——返回的pvalues,由于采用了BoundaryTypeConstants.btOutside读法,正常情况应该包含一个等于或早于起始时刻的首值,包含一个早于、等于或者晚于截止时刻的后值。 //——pivalues中可能有非数值数据,转换后的pivalues只包含数值型数据 List <PValue> pvalues = PIValues2PValuesHist(pivalues); if (pvalues == null || pvalues.Count == 0) { return(null); } //处理首值 if (pvalues[0].Timestamp < startdate) { pvalues[0].Timestamp = startdate; //如果首值的时刻小于起始时刻,说明btOutside方式正确取到了前点。则将前点时刻改为起始时刻 } else if (pvalues[0].Timestamp == startdate) { //如果首值的时刻恰好等于起始时刻,则什么也不做 } else { //将pi的异常记录在_exception中,并以PgimDataException的名称继续抛出异常 this._exception = "PI接口读取原始数据错误。未正常读取到起始时刻前值!"; throw new Exception(this._exception); //将XIANDB的异常继续向外传递。 } //处理尾值 if (pvalues[pvalues.Count - 1].Timestamp < enddate) { //如果最后一个点的时间戳小于截止时刻,则说明没有找到截止时刻后值 pvalues[pvalues.Count - 1].Endtime = enddate; pvalues.Add(new PValue(pvalues[pvalues.Count - 1].Value, enddate, enddate, pvalues[pvalues.Count - 1].Status)); //添加截止时刻点 } else if (pvalues[pvalues.Count - 1].Timestamp == enddate) { //恰好有截止时刻点,将该值作为截止时刻值 pvalues[pvalues.Count - 1].Endtime = enddate; } else { //取到截止时刻点后值 pvalues[pvalues.Count - 2].Endtime = enddate; //修改前一个值的结束时刻到enddate //将最后一个值,变为截止时刻点 pvalues[pvalues.Count - 1].Value = pvalues[pvalues.Count - 2].Value; pvalues[pvalues.Count - 1].Timestamp = enddate; pvalues[pvalues.Count - 1].Endtime = enddate; pvalues[pvalues.Count - 1].Status = pvalues[pvalues.Count - 2].Status; } return(pvalues); }
/// <summary> /// 查询间隔数据 /// </summary> /// <param name="tagName">查询条件</param> /// <param name="startTime">起始时间</param> /// <param name="endTime">结束时间</param> /// <param name="sampleInterVal">间隔时间:1h,2h,3h,4h</param> /// <param name="fvc">过滤方式</param> /// <param name="filter">过滤</param> /// <returns></returns> public virtual DataTable GetInterpolatedValues(string[] tagName, DateTime startTime, DateTime endTime, object sampleInterVal, string filter) { if (this.server == null) { //log4net.ILog log = log4net.LogManager.GetLogger(this.GetType()); //log.Debug("PIServer未初始化"); logs.writelog("PIServer未初始化"); throw new Exception("PIserver未初始化。"); } //PIValues pivalues = null; DataSet ds = new DataSet(); //声明一个数据表,表名为LoadValue DataTable dt = new DataTable("LoadValue"); //该表有3列,分别为piID,ptValue,ptTime dt.Columns.Add("ptID", typeof(System.String)); dt.Columns.Add("ptValue", typeof(System.String)); dt.Columns.Add("ptTime", typeof(DateTime)); //StringBuilder sb = new StringBuilder(); //由于历史数据可能过多,所以选择用datatable返回 try { if (!this.server.Connected) { this.server.Open(this.piConnectionString); } //分别对pointID取数 for (int i = 0; i < tagName.Length; i++) { //pisdk的pipoint为pointID对应的原子点名 PISDK.PIPoint piPoint = this.server.PIPoints[tagName[i].ToString()]; //取2min内的2个点 PISDK.PIValues piValues = piPoint.Data.InterpolatedValues( System.DateTime.Now.AddMinutes(-2), System.DateTime.Now.AddMinutes(-1), 3, "1=1", PISDK.FilteredViewConstants.fvShowFilteredState, new PISDKCommon.PIAsynchStatus()); if (piValues.Count > 0) { //将每个点对应的数据和事件加入到数据表LoadValue中 for (int j = 1; j <= piValues.Count; j++) { DataRow dr = dt.NewRow(); dr["ptID"] = tagName[i]; dr["ptValue"] = piValues[j].Value.ToString(); dr["ptTime"] = piValues[j].TimeStamp.LocalDate; dt.Rows.Add(dr); } } } } catch (Exception ex) { //log4net.ILog log = log4net.LogManager.GetLogger(this.GetType()); //log.Error(ex); logs.writelog("按照时间间隔查询数据发生错误:" + ex); throw new Exception("按照时间间隔查询数据发生错误:" + ex.Message); } return(dt); }