【问题标题】:How do I query raw data from a Proficy Historian?如何从 Proficy Historian 查询原始数据?
【发布时间】:2008-11-20 19:59:20
【问题描述】:

如何从 Proficy Historian/iHistorian 检索原始时间序列数据?

理想情况下,我会在两个日期之间询问特定标签的数据。

【问题讨论】:

    标签: c# oledb proficy historian


    【解决方案1】:

    您可以尝试多种不同的采样模式。

    • 生的
    • 插值
    • 实验室
    • 趋势
    • 已计算

    使用以下所有 API 都可以使用这些模式。

    • 用户 API (ihuapi.dll)
    • SDK (ihsdk.dll)
    • OLEDB (iholedb.dll)
    • 客户端访问 API (Proficy.Historian.ClientAccess.API)

    其中趋势采样模式可能是您想要的,因为它是专门为图表/趋势设计的。不过,lab 和 interpolated 也可能有用。

    阅读电子书以获取有关每种采样模式的更多信息。在我的机器上,它存储为C:\Program Files\GE Fanuc\Proficy Historian\Docs\iHistorian.chm,我安装了 3.5 版。请特别注意以下部分。

    • 使用 Historian OLE DB 提供程序
    • 高级主题 |检索

    以下是构建 OLEDB 进行趋势采样的方法。

    set 
        SamplingMode = 'Trend',
        StartTime = '2010-07-01 00:00:00',
        EndTime = '2010-07-02 00:00:00',
        IntervalMilliseconds = 1h
    select 
        timestamp, 
        value, 
        quality 
    from 
        ihRawData 
    where 
        tagname = 'YOUR_TAG'
    

    使用用户 API 和 SDK 显示等效方法很复杂(使用用户 API 更是如此),因为它们需要在代码中进行大量检查才能进行设置。客户端访问 API 较新,并在后台使用 WCF。

    顺便说一下,OLEDB 方法有一些限制。

    • 尽管文档说我从来没有能够让本机查询参数正常工作。例如,如果您想将它与 SQL Server Reporting Services 一起使用,那将是一个很好的选择。
    • 您不能将样本写入存档或以任何方式更改 Historian 配置,包括添加/更改标签、写入消息等。
    • 在某些情况下可能会有点慢。
    • 它没有规定将多个标记名交叉制表到列中,然后继续采样,以便每个时间戳和标记组合都存在一个值。趋势采样模式让您走到了一半,但仍然没有交叉表,也没有实际加载原始样本。再说一次,用户 API 和 SDK 也无法做到这一点。

    【讨论】:

    • 我知道有点晚了,但迟到总比没有好。另外,无论如何,回答关于 SO 的老问题是我的风格。
    • 我现在没有时间对此进行测试,但这正是我开始时所寻找的。谢谢!您能否偶然添加关于电子书通常位于何处的注释?
    • 关于如何以这种方式使用像 PreviousValue 这样的内置历史函数的任何想法? oledb可以做到这一点吗?类似于 select "timestamp, PreviousValue(Time, tagname) from ..."
    • @roviuser:我不这么认为,但请发布另一个问题并清楚地描述您想要什么,我可能会提供帮助。
    • 看看我的回答布赖恩
    【解决方案2】:

    我的一个同事把这个放在一起:

    在 web.config 中:

    <add name="HistorianConnectionString" 
         providerName="ihOLEDB.iHistorian.1" 
         connectionString="
           Provider=ihOLEDB.iHistorian;
           User Id=;
           Password=;
           Data Source=localhost;"
    />
    

    在数据层:

    public DataTable GetProficyData(string tagName, DateTime startDate, DateTime endDate)
    {
        using (System.Data.OleDb.OleDbConnection cn = new System.Data.OleDb.OleDbConnection())
        {
            cn.ConnectionString = webConfig.ConnectionStrings.ConnectionStrings["HistorianConnectionString"];
            cn.Open();
    
            string queryString = string.Format(
                    "set samplingmode = rawbytime\n select value as theValue,Timestamp from ihrawdata where tagname = '{0}' AND timestamp between '{1}' and '{2}' and value > 0 order by timestamp",
                    tagName.Replace("'", "\""), startDate, endDate);
    
            System.Data.OleDb.OleDbDataAdapter adp = new System.Data.OleDb.OleDbDataAdapter(queryString, cn);
            DataSet ds = new DataSet();
    
            adp.Fill(ds);
            return ds.Tables[0];
        }
    }
    

    更新:

    这很有效,但我们遇到了标签不经常更新的问题。如果标签在请求的 startDate 和 endDate 的开始或结束附近没有更新,则趋势看起来很糟糕。更糟糕的是,在请求的窗口期间仍然没有明确的点——我们不会得到任何数据。

    我通过三个查询解决了这个问题:

    1. 上一个值开始日期之前
    2. startDate 和 endDate 之间的点
    3. 下一个值 endDate

    这可能是一种效率低下的方法,但它确实有效:

    public DataTable GetProficyData(string tagName, DateTime startDate, DateTime endDate)
    {
        DataSet ds = new DataSet();
        string queryString;
        System.Data.OleDb.OleDbDataAdapter adp;
    
        using (System.Data.OleDb.OleDbConnection cn = new System.Data.OleDb.OleDbConnection())
        {
            cn.ConnectionString = proficyConn.ConnectionString;
            cn.Open();
    
            // always get a start value
            queryString = string.Format(
                 "set samplingmode = lab\nselect value as theValue,Timestamp from ihrawdata where tagname = '{0}' AND timestamp between '{1}' and '{2}' order by timestamp",
                tagName.Replace("'", "\""), startDate.AddMinutes(-1), startDate);
            adp = new System.Data.OleDb.OleDbDataAdapter(queryString, cn);
            adp.Fill(ds);
    
            // get the range
            queryString = string.Format(
                 "set samplingmode = rawbytime\nselect value as theValue,Timestamp from ihrawdata where tagname = '{0}' AND timestamp between '{1}' and '{2}' order by timestamp",
                tagName.Replace("'", "\""), startDate, endDate);
            adp = new System.Data.OleDb.OleDbDataAdapter(queryString, cn);
            adp.Fill(ds);
    
            // always get an end value
            queryString = string.Format(
                 "set samplingmode = lab\nselect value as theValue,Timestamp from ihrawdata where tagname = '{0}' AND timestamp between '{1}' and '{2}' order by timestamp",
            tagName.Replace("'", "\""), endDate.AddMinutes(-1), endDate);
            adp = new System.Data.OleDb.OleDbDataAdapter(queryString, cn);
            adp.Fill(ds);
    
            return ds.Tables[0];
        }
    }
    

    是的,我知道,这些查询应该被参数化。

    【讨论】:

    • OLE 很慢。使用 API 的性能要好得多,您还可以编写扩展方法,让您做的事情远远超过 OLE 开箱即用的支持。
    • 关于不取回样品,您可以执行“ReadSamplesByCount”而不是时间,这样您至少可以取回 1 个样品。如果样本不多,则样本可能在您要显示的时间段后数年。
    【解决方案3】:

    Michael——在 IP21 中有一个“插值”表,以及“实际”数据点表。 Proficy 也有吗?

    【讨论】:

    • @reallyJim 我相信他们会这样做,但就我而言,我需要原始数据
    • 几年前我在使用 PI 系统时遇到过同样的事情,所以我确实感受到了你的痛苦!
    【解决方案4】:

    我们编写了一个如下所示的包装 DLL:

    [DllImport("IHUAPI.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "ihuReadRawDataByTime@24")]
    public static extern int ihuReadRawDataByTime(int serverhandle, string tagname, ref IHU_TIMESTAMP startTime, ref IHU_TIMESTAMP endTime, ref int noOfSamples, ref IHU_DATA_SAMPLE* dataValues);
    ...
    private int _handle;
    
    public HistorianTypes.ErrorCode ReadRawByTime(string tagName, DateTime startTime, DateTime endTime,
                                                  out double[] timeStamps, out double[] values, out IhuComment [] comments)
    {
        var startTimeStruct = new IhuApi.IHU_TIMESTAMP();  //Custom datetime to epoch extension method
        var endTimeStruct = new IhuApi.IHU_TIMESTAMP();
    
        int lRet = 0;
        int noOfSamples = 0;
        startTimeStruct = DateTimeToTimeStruct(dstZone.ToUniversalTime(startTime));
        endTimeStruct = DateTimeToTimeStruct(dstZone.ToUniversalTime(endTime));
        IhuApi.IHU_DATA_SAMPLE* dataSample = (IhuApi.IHU_DATA_SAMPLE*)new IntPtr(0);
    
        try {
            lRet = IhuApi.ihuReadRawDataByTime
                (
                    _handle, // the handle returned from the connect
                    tagName, // the single tagname to retrieve
                    ref startTimeStruct, // start time for query
                    ref endTimeStruct, // end time for query
                    ref noOfSamples, // will be set by API
                    ref dataSample // will be allocated and populated in the user API
                );
                ....
    

    一些注意事项是 iFIX 会在启动时检查 DLL 是否已加载,因此您需要执行动态加载/卸载 DLL 等操作,以免其他应用程序崩溃。我们通过动态删除/添加注册表项来做到这一点。

    另一种情况是,如果您轮询 10,000 个样本并且其中 1 个样本已损坏,它将丢弃所有 10,000 个样本。您需要实现一个不良数据处理程序,该处理程序将从不良数据的任一侧开始,并逐步递增以获取不良样本任一侧的所有数据。

    有几个 C 头文件包含所有错误代码和 DLL 的函数头。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-20
      相关资源
      最近更新 更多