资讯动态

C#读取PI系统数据:PI SDK、AF SDK与REST API实战指南

发布时间:2026/10/21 8:59:38 来源:尧图企业网站定制
1. 先理清思路PI系统到底提供了哪些数据访问方式搞工业数据的人都知道PI系统Plant Information系统在流程行业里几乎是标配从DCS、PLC、SCADA采集上来的实时数据最终都会汇到PI服务器的历史库里。我们做上位机开发、报表系统或者数据中台的时候经常要用C#把PI里的数据拉出来做趋势分析、报警统计、KPI计算甚至是喂给机器学习模型做预测。这里我先泼一盆冷水PI系统的数据接口没有你想的那么“现代”。它不像MySQL、PostgreSQL那样给你一个标准的驱动而是提供了好几套面向不同场景的API有些人第一次接触就被这一堆SDK搞晕了。我见过不少同事在PI SDK和AF SDK之间犹豫半天最后选错了方向代码写了一堆却发现性能或者功能不匹配返工重来。简单梳理一下目前C#读取PI数据的主流途径有四种PI SDK老牌COM组件封装了连接PI Server、读取点Point、读取快照值Snapshot、读取历史值History的基本能力。它依赖PI家的“点表”概念适合传统的PI 3.x架构很多老项目都在用它。AF SDK这是后来推出的、面向PI Asset FrameworkAF架构的SDK比PI SDK“高级”很多。它把数据组织成资产模型比如某条产线、某个设备节点下的测点集合更贴近业务逻辑性能也更好。AVEVA收购之后AF SDK基本是官方主推的方向。PI OLEDB / PI JDBC这俩是给SQL系开发者准备的把PI数据映射成关系型数据库的表结构可以用SQL查询。但说实话功能覆盖有限性能也不是最优适合临时做数据导出、报表工具不适合高频实时读取。PI Web APIRESTful API通过HTTP方式暴露数据接口用JSON返回结果C#里用HttpClient就能调用。这个方案跨平台、跨语言而且不需要在客户端装PI的SDK现在越来越多人倾向于用它做轻量集成。我个人的建议是如果是新项目优先用AF SDK或者直接走PI Web API如果公司老系统里大量逻辑都是围绕传统PI点表写的那PI SDK还得继续用毕竟兼容性摆在那里。下面我会把最常用的几种读取方式全部展开附上我自己实际跑过的代码和踩坑记录。2. 环境准备先把PI SDK和AF SDK的依赖关系理顺2.1 引用与安装没你想的那么难PI SDK是一个COM组件安装PI SDK或者PI Server客户端工具之后系统里会注册这个COM组件。C#项目里添加引用选择COM选项卡找到“PISDK”相关项即可。注意PI SDK有32位和64位版本之分如果你的程序集目标是x86比如很多WinForm老项目为了兼容旧Excel组件就必须确保PI SDK的32位版本已经安装反过来如果你的程序是x64最好装64位的SDK否则进程里加载COM会出现CLSID不匹配或者“类未注册”的尴尬错误。AF SDK则是一个.NET程序集安装完成后在GAC里注册。C#项目里直接添加引用或者在NuGet上搜索“OSIsoft.AF”之类的包实际包名以官方发布为准。AF SDK内部也会依赖部分COM组件但多数情况下你不需要关心因为它已经帮你封装好了底层的连接细节。我自己的习惯是在开发机上把PI SDK和AF SDK全装上这样写代码的时候想用哪个就用哪个不用被环境卡住。但部署到生产服务器的时候就要按程序实际用到的API来裁剪安装避免服务器上出现不必要的组件和潜在的DLL冲突。2.2 连接字符串和权限80%的问题都出在这不管是PI SDK还是AF SDK连接PI数据库都需要三个核心信息服务器名、用户名、密码。这里有个容易误会的点PI Server的登录账密不一定等于Windows域账密PI有自己的安全模型PI Trust、映射表、身份验证默认可以用“域\用户名”的格式登录也可以配置成Windows集成认证。PI SDK里面连接服务器的经典写法是using PISDK; PISDKCommon sdk new PISDKCommon(); PISDKServer server sdk.Servers.DefaultServer;然后通过server.Open(服务器名, 用户名, 密码)进行登录。如果是域集成环境用户名那栏可以传空串或者你当前登录的Windows用户具体要看PI服务器上的Trust配置。AF SDK里面则是用PISystems集合using OSIsoft.AF.PI; PISystems systems new PISystems(); PISystem piServer systems[我的PIServer]; piServer.Connect();如果连接失败多半是账号在PI里没有对应的映射或者映射的权限不够只能读快照不能读历史之类的。我的排查经验是先到PI的数据库管理工具里看这个用户有没有对应MAP再看它关联的Identity有没有勾选“读”相关的权限这两步能解决大部分登录失败问题。2.3 64位/32位进程的选择这里值得单独拎出来说。PI SDK这个老COM组件有个很烦人的特性同一个服务器上如果同时注册了32位和64位两个版本那么你的C#程序编译目标Platformx86/x64/AnyCPU会直接影响能否正确实例化SDK。我踩过的坑是开发的时候AnyCPU编译本机跑得好好的因为开发机是64位Windows默认优先加载64位COM。但把程序放到32位环境下测试直接报“Retrieving the COM class factory for component with CLSID {...} failed”原因是客户机器上只装了32位SDK而程序用了AnyCPU在64位系统上会以64位进程运行导致CLSID找不到。所以我的经验法则是项目里用了PI SDK就把编译目标固定成和你部署环境一致x86就x86x64就x64不要用AnyCPU如果客户环境不确定宁可目标x86因为绝大多数Windows Server上32位COM兼容层都能跑而且x86进程里可以加载32位和64位两套COM前提是SDK装了32位。AF SDK就没这么闹心它是托管的.NET程序集基本上AnyCPU和x64都能正常加载。3. 核心实操C#读取PI实时数据的五种典型写法3.1 用PI SDK读取实时快照值先说最传统的PI SDK读实时值这是很多老项目里最基础也是最常见的需求给一个PI点PI Tag名称立刻返回当前值。using PISDK; PISDKCommon sdk new PISDKCommon(); PISDKServer server sdk.Servers.DefaultServer; server.Open(PIServer01, domain\\user, password); PIPoint point server.PIPoints[TAG_UPS_POWER_01]; PIPointValue value point.CurrentValue; object rawValue value.Value; DateTime timeStamp value.TimeStamp; int quality value.Quality // 或 value.Status Console.WriteLine($当前值: {rawValue}, 时间戳: {timeStamp}, 质量: {quality});这段代码看起来简单但有几个坑。point.CurrentValue返回的PIPointValue包含值、时间戳、质量戳Quality。质量戳很重要很多DCS设备断线或手动置坏值的时候PI里会存一个IMPUTED推算或BAD状态值如果你不做质量判断就直接拿来做业务判断很可能在设备故障的时候给出一个错误的“正常”数据。我习惯的做法是拿到值之后先判断value.IsGood或者对比value.Status是否为“Good”不是Good就直接丢弃或打标。生产项目里我遇到过把PI里的Bad值当成真实值写入报表的情况结果一条产线的关键指标被算偏了排查了大半天最后发现是某个仪表信号断了PI做了零值保持但质量戳是Bad而我们的报表逻辑没过滤质量。另外PIPoint的名称要注意大小写和全名。PI点表里的名称可能是TAG_UPS_POWER_01也可能是CDE:MEASUREMENT.POWER这种带CDE前缀的。找不到点的时候直接PIPoints[xxx]会抛异常所以取点前可以先PIPoint.FindPIPoint并捕获异常或者用PIPoints集合的Contains方法判断一下虽然会多一次网络交互但提高健壮性还是值得的。3.2 用PI SDK读取历史数据区间读取历史数据是更常见的业务需求比如查某个测点过去24小时的变化趋势。using PISDK; PISDKCommon sdk new PISDKCommon(); PISDKServer server sdk.Servers.DefaultServer; server.Open(PIServer01, domain\\user, password); PIPoint point server.PIPoints[TAG_REACTOR_TEMP]; DateTime startTime DateTime.Now.AddHours(-24); DateTime endTime DateTime.Now; // 按时间区间获取记录RetrievalMode 可以控制是快照还是归档值 PIPointValues values point.RetrieveByTime(startTime, endTime, singleExpTime, RetrievalModeConstants.rtmInterpolated, 0);RetrieveByTime的参数值得掰开讲一下。第一个和第二个是起止时间第三个参数singleExpTime是期望的时间分辨率单位秒主要配合插值模式使用第四个参数RetrievalModeConstants有几种模式rtmSnapshot返回区间内的所有快照变化但不落盘的值变化记录和归档数据点。rtmInterpolated按指定的时间间隔第三个参数做线性插值返回规整后的数据序列。适合画趋势曲线因为DCS数据采样间隔不固定直接画点会参差不齐。rtmPlot按时间区间返回适合绘图的“优化”数据类似趋势图画的点和线这种模式下返回的数据量会比插值小因为PI引擎会自动抽稀。rtmRecorded只返回归档记录不包含插值和推算值。我的建议画趋势图优先用rtmPlot做统计计算比如求平均值、累积量用rtmInterpolated做离线分析拿原始快照用rtmRecorded。如果你拿rtmInterpolated去补点第三个参数singleExpTime一定要跟你的业务需求匹配。比如你希望每5分钟一个点那singleExpTime就是300秒你希望每1秒一个点就是1秒但要注意PI Server上内含的插值数据量会暴涨如果区间过大、间隔过小性能会显著下降甚至返回超时。还有一个容易被忽视的问题RetrieveByTime返回的数据边界是“包括起止时间点”的这个和很多人的直觉不一样。如果你要做“上月1号到上月月底”这种左闭右开区间必须在代码里对结束时间做微调比如减去1秒或1毫秒否则容易把下个月的0点数据也算进来。我在做月报统计时就吃过这个亏首月和次月的数据重叠了一个点月度总量差了不少追踪半天才发现是区间边界问题。3.3 用AF SDK读取实时值AF SDK的思路跟PI SDK差异很大。AF SDK强调“数据资产”的概念不只是裸点读值而是通过AFElement、AFAttribute这样的模型来组织数据。刚接触的人可能会觉得绕但用顺手之后你会发现它的API写起来更自然尤其适合面向工厂对象建模的场景。一个最简单的AF SDK读实时值例子using OSIsoft.AF.PI; using OSIsoft.AF.Asset; PISystems systems new PISystems(); PISystem piServer systems.DefaultPISystem; piServer.Connect(); AFDatabase database piServer.Databases[我的数据库]; AFElement element database.Elements[生产线A]; AFAttribute attribute element.Attributes[反应釜温度]; AFValue current attribute.GetValue(); Console.WriteLine($值: {current.Value}, 时间: {current.Timestamp}, 状态: {current.Status});注意AF SDK里的GetValue()拿到的AFValue其实不只是“点值”它可以关联到AF属性上的PI点数据源也可以关联到手动录入的静态配置值。如果你的AF属性配置得比较完整这里拿到的数据就是有业务语义的。比如你要展示“三号反应釜的温度”你用PI SDK要先去查这个反应釜对应哪个PI Tag但AF SDK直接按设备层级往下找属性就行对业务人员友好很多。AF SDK读取PI点对应的实时值也支持直接指定PIPoint去读PIPoint piPoint PIPoint.FindPIPoint(piServer, TAG_REACTOR_TEMP); AFValue afVal piPoint.CurrentValue();这里CurrentValue()返回的也是AFValue它的.Value属性可能是null尤其是点存在但没有任何数据的时候。处理的时候要养成判空习惯不要一上来就Convert.ToDouble()我见过不少同事在这里被NullReferenceException折磨。3.4 用AF SDK读取历史快照与时间区间AF SDK读历史区间比PI SDK更直接AFAttribute有一个RecordedValues方法可以直接按时间窗口拿归档数据using OSIsoft.AF.Asset; using OSIsoft.AF.Time; AFTime start new AFTime(2024-01-01 00:00:00); AFTime end new AFTime(2024-01-02 00:00:00); AFValues recorded attribute.RecordedValues(start, end, AFBoundaryType.Inside, null, false); foreach (AFValue v in recorded) { Console.WriteLine(${v.Timestamp}: {v.Value}); }RecordedValues的边界类型AFBoundaryType有Inside、Outside、Interpolated等。Inside意思是不包含边界值取区间内部的记录Outside会外扩到区间前后的最近点Interpolated则是按指定时间间隔插值。AF SDK在插值这块跟PI SDK的差异也要注意。AF SDK的InterpolatedValues方法是独立于RecordedValues的AFTime start new AFTime(2024-01-01 00:00:00); AFTime end new AFTime(2024-01-02 00:00:00); TimeSpan interval TimeSpan.FromMinutes(5); AFValues interp attribute.InterpolatedValues(start, end, interval, AFInterpolationMode.Continuous, null, false);AFInterpolationMode.Continuous用于连续变化量流量、温度Step用于离散量开关状态、计数器。选错模式会导致拐点处的数值不准确尤其是处理开关量状态变化的时候——你用Continuous去插值一个0到1跳变的开关量中间可能会插出0.4、0.7这种根本不存在的值业务上完全没法用。这个地方我专门给做报表系统的同事提过醒统计某个阀门开关状态在一天内的累计开启时长一定要用Step模式而且最好用RecordedValues先拿原始状态变化记录再在内存里做时间区间累加别指望插值函数给你精确结果。3.5 用REST API做跨平台轻量读取如果你不想在目标机器上装一堆SDK也不想处理COM和.NET程序集的版本纷争PI Web API是最省心的方案。它基于OData协议直接用HTTP请求就能读数据C#端只需要System.Net.Http。一个典型的读实时数据HTTP请求长这样using System.Net.Http; using System.Net.Http.Headers; string baseUrl https://piserver.example.com/piwebapi; string user admin; string password password; string webIdOrPath /points/PIPE_TAG_123; using (HttpClient client new HttpClient()) { // Basic认证或者基于Token的认证 var authToken Encoding.ASCII.GetBytes(${user}:{password}); client.DefaultRequestHeaders.Authorization new AuthenticationHeaderValue(Basic, Convert.ToBase64String(authToken)); string url ${baseUrl}/streams/{webIdOrPath}/value; HttpResponseMessage response await client.GetAsync(url); string json await response.Content.ReadAsStringAsync(); }REST API响应是JSON格式里面包含Timestamp、Value、Good等字段。解析JSON你既可以用System.Text.Json也可以用Newtonsoft.Json看项目现有依赖情况。用PI Web API要注意几个事情URL里的路径可以传WebIdPI系统内部唯一标识也可以传带反斜杠的路径表达式但路径表达式的URL编码要做对否则会返回404。认证方式有三种Basic认证、Anonymous、基于Token的OAuth2。企业内部系统图省事很多人直接上Basic但Basic认证每次请求都会把密码明文编码发出去尽量用HTTPS别用HTTP明文。为了安全其实更推荐申请一个ClientId和ClientSecret用OAuth2拿Bearer Token这样服务端流转会更安全。PI Web API有默认的数据返回条数和性能限制比如一次最多返回多少条历史记录、历史区间最大跨度是多少。如果查询区间太大可以直接通过recordedValues的swa参数微调或者把查询拆成多段并行请求。我自己最近一年的新项目中凡是做Web后端或者数据中台的项目都直接用PI Web API了因为不需要在每个容器或服务器上装SDK镜像构建也简单起一个HttpClient实例就能通。不过如果你要读高频率、大批量的数据比如几千个点同时取实时值REST的JSON序列化开销还是比AF SDK直连要大不少这时候我会推荐回到AF SDK做批量读取。4. 性能调优与稳定性真实项目里怎么把读取速度提上去4.1 连接对象别反复创建会话要复用这个问题我看到太多次了。有人写了个方法每次调用都new PISDKCommon()或new PISystems()用完不释放或者释放了下次再new。PI Server端每个连接都会建立会话和授权上下文频繁建立和断开连接不仅慢还会给PI服务器造成无谓的负担甚至会因为服务器端连接数限制而导致后续连接被拒。正确做法是进程生命周期内维护一个全局的连接单例。PI SDK的模式是PISDKCommon常驻PISDKServer打开后保持AF SDK里则是PISystem对象常驻通过AddRef()和Release()或者依赖.NET的Dispose机制来管理。实际项目里我通常是写一个静态类来管理连接并且在程序退出时显式调用server.Close()或piServer.Disconnect()。还有一点PI SDK和AF SDK的连接都是“会话级”的如果你在ASP.NET这种多线程环境里使用要注意并发访问。PI SDK的PISDKCommon不建议直接跨线程共享无锁调用最好加锁或者用连接池。AF SDK线程安全性稍好一些但PISystem内部某些操作也不是完全线程安好稳妥做法是每个线程持有自己的PISystem实例但连接开销会增大所以一般建议使用单例锁的折中方案。我在一个上位机服务里就是用ConcurrentDictionary维护多个PI Server的连接实例配合锁来控制并发读写跑了大半年PI服务器端没报过连接泄漏。4.2 批量读取比循环单点读取快一个数量级很多人写代码喜欢这样一个for循环遍历50个点每个点分别调用ReadValue。这个习惯在PI数据读取上是非常糟糕的。PI SDK提供了ReadData或者PIPointList配合Server.ReadData这种批量接口AF SDK则提供了对AFValues的批量读取方法比如AFListData或者AttributeList的同步读取。一次批量读取可以一次请求拿到多个点的数据网络往返次数从N次降到1次性能提升非常明显。我之前做过一个对比读取100个点每个点的24小时历史数据循环单点读取大约需要50秒用批量读取之后只用了3秒。这个量级的差距在需要频繁刷新的大屏展示项目里直接影响用户体验。AF SDK里批量读取一个典型写法如下using OSIsoft.AF.Asset; IListAFAttribute attrs new ListAFAttribute(); attrs.Add(element.Attributes[温度1]); attrs.Add(element.Attributes[温度2]); attrs.Add(element.Attributes[压力1]); AFTime time AFTime.Now; IDictionaryAFAttribute, AFValue values AFAttribute.GetValue(attrs, time);这种批量API返回一个字典键是AFAttribute值是AFValue拿到之后直接按属性名去取值。再强调一下批量读取不是把代码写整齐就行关键在减少PI Server端的请求上下文切换。PI数据库引擎对单请求的处理能力是有上限的你把请求合并服务器端就可以用内部的批量查询优化器统一处理性能自然就上去了。4.3 数据质量判断别把坏值当宝前面我提到了质量戳这里再展开说一下。PI的数据质量戳不是只有Good和Bad两种常见的还有Good正常采集值。Bad传感器断线、超量程、手动置坏。Uncertain采集质量不确定比如仪表刚重新上电。IMputed推算值可能是PI引擎在数据缺失时用相邻值插值出来的。如果你的业务对数据真实性要求高比如做KPI考核、能耗审计建议在读取后只保留Good质量的数据对Uncertain和IMputed的数据做特殊标注。质量戳在PI SDK里对应value.Status或value.Quality在AF SDK里对应value.Status或AFQuality的枚举。我的一个客户案例是设备状态分析系统常常把PI的推算值当成真实运行状态导致停机时长统计虚高。最后我们在读取层加了一个质量过滤只统计Good的状态结果数字反而跟现场记录对上了。这个教训说明数据质量过滤不是可选项是必备项。4.4 时区问题PI的时间语义和你本地时间不一样PI Server存储的时间戳是服务器本地时间还是UTC取决于PI Server的配置。多数情况下PI Server的时间是服务器本地时区时间而你的客户端可能在另一台机器上、另一个时区。如果不做时区转换查询历史数据你会遇到“差8小时”的怪异现象。我的习惯是查询前先读取PI服务器的当前时间和客户端当前时间的差值也就是做一个时间偏移量校正。AF SDK里PISystem.CurrentTime返回服务器时间你可以据此计算偏移。有些项目直接用UTC时间做接口约定那就更清晰了——客户端把本地时间转成UTC传给PI拿回来的时间戳也按UTC解析展示的时候再转回本地时区。还有夏令时问题如果你们公司有海外工厂PI服务器在夏令时切换时会有“重复一小时”或“缺失一小时”处理历史区间时要做容错。这个坑比较隐蔽但真实存在。5. 常见问题排查我把踩过的坑都列出来免得你再踩一遍5.1 连接失败报“Unable to open PI server”或者登录失败先检查网络通不通用PI SMTPI System Management Tools测试连接同一台服务器能通说明网络和PI服务正常再看账号在PI安全映射里是否存在看看映射的Identity有没有对应权限排查PI Server端的Trust列表有没有禁止你这个IP。很多时候是PI的Trust管理太严格限制了某个网段的访问。如果开发环境能连生产环境不能连多半是生产环境的PI服务器没有配置开发账号的映射需要在PI数据库管理工具里手动添加。这类问题我遇到的次数比代码问题还多。5.2 找不到点报“PIPoint not found”或者AF属性取不到值先确认点全名称和源服务器前缀比如CDE:TAG这种带CDE的不能只写TAG。检查PI点的类型有的PI点被配置成String或者Blob你拿数值类型去接收会报类型不匹配。用AF SDK时先确认AF数据库名和元素路径很多人搞混了“数据库Database”和“库元素Element”路径写错也会导致找不到属性。5.3 时间范围报错查询时间超出PI Archiving范围PI的归档数据不是无限期的磁盘空间、归档策略、压缩归档都会限制可查时间范围。如果查询区间太远古PI会返回空数据或直接报错。这种情况如果业务必须查可以联系PI管理员确认是否有长期存储的辅助归档库或者把数据导出为历史文件再做离线查询。5.4 进程异常退出PI SDK的COM对象没有正确释放COM引用计数管理是很多人容易忽略的。PIPoint、PIPointValue这些COM对象用完了要Marshal.ReleaseComObject或者在finally块里做释放。AF SDK是托管对象但内部也可能引用COM对象长时间运行的程序如果发现内存不断增长要检查是不是每隔一段时间创建了新的PISystem没有释放。写Windows服务项目时尤其要小心服务常驻运行一点内存泄漏都会被放大。我在一个服务里就是每5分钟建一个PISystem连接跑了半年之后内存占用从300MB涨到2GB最后加了Dispose和GC.Collect才控制住。5.5 32位/64位引发的问题我前面提过PI SDK有32位和64位版本。报错信息里如果出现“Class not registered”或“CLSID”基本可以判断是位数不匹配。解决办法是要么在目标机器上安装对应位数的SDK要么把程序编译目标改到和SDK一致的位数。注意一台机器上32位和64位的PI SDK可以共存但COM选择加载哪个版本取决于进程位数。5.6 并发冲突PI的锁和事务冲突如果你在同一个进程里多线程操作同一个PIPoint或同一个PISystem有时会碰到“数据库被锁”之类的错误。PI Server有内部的并发控制但客户端自己如果不加锁还是会出现一些诡异的异常。解决方案很简单线程访问PI资源时加一个全局锁或者用单独的读取线程队列来串行化所有PI请求。高并发场景下我会用一个独立的PI访问模块内部维护一个请求队列主线程只管发请求底层串行或者用轻量并发池处理。6. 选型建议SDK、AF SDK和REST API到底怎么选我把我的选型逻辑总结成表格供参考方案适用场景优缺点PI SDK老项目维护、传统PI点表操作稳定、API直接但架构老文档晦涩COM组件部署麻烦AF SDK新项目、面向资产模型、高性能批量读建模灵活、性能好、官方主推但学习成本稍高PI Web API跨平台、轻量集成、前后端分离无需SDK、部署简单但性能和批量能力弱于SDKOLEDB临时SQL查询、报表工具对SQL开发者友好但功能有限不推荐高频调用如果你是一个新工控项目的负责人我建议优先用AF SDK配合PI Web API的组合主数据通道用AF SDK对外暴露服务或者做Web接口时用PI Web API。这样既有性能保障又不至于把客户端绑定死。如果你是在老项目里做增量开发那PI SDK还是绕不开但你要做好COM环境管理的心理准备。从团队角度说选型的时候还要考虑团队的技能储备。如果你团队里的人都只会写SQL那PI OLEDB很可能比SDK更落地如果你的团队是.NET Core技术栈那PI Web API几乎是唯一选择因为PI SDK在.NET Core里兼容性真的不好AF SDK在新版里其实也主要面向.NET Framework跨平台支持很有限。这点在做Linux容器化部署时尤其关键——要么用PI Web API要么就得用第三方兼容方案千万不要在项目中期才发现这个坑。7. 一个完整的C#示例AF SDK批量读取多个测点的历史数据我贴一个我自己项目里用过的、相对完整的代码片段包含了连接、批量读取、质量过滤、时区处理你可以直接当作脚手架去改。using System; using System.Collections.Generic; using System.Linq; using OSIsoft.AF.PI; using OSIsoft.AF.Asset; using OSIsoft.AF.Time; public class PiDataReader { private PISystem _system; public PiDataReader(string serverName, string username, string password) { PISystems systems new PISystems(); _system systems[serverName]; _system.Server.User username; _system.Server.Password password; _system.Connect(); } public void Close() { if (_system ! null) { _system.Disconnect(); } } public Dictionarystring, ListDataPoint ReadHistory(string elementPath, Liststring attrNames, DateTime startUtc, DateTime endUtc, TimeSpan interval) { AFElement element AFElement.FindElement(_system, elementPath); if (element null) { throw new Exception($找不到元素: {elementPath}); } ListAFAttribute attrs new ListAFAttribute(); foreach (string attrName in attrNames) { AFAttribute attr element.Attributes[attrName]; if (attr ! null) { attrs.Add(attr); } } var result new Dictionarystring, ListDataPoint(); AFTime start new AFTime(startUtc); AFTime end new AFTime(endUtc); // 批量插值查询返回的字典按属性维度组织 IDictionaryAFAttribute, AFValues interpValues AFAttribute.InterpolatedValues(attrs, start, end, interval, AFInterpolationMode.Continuous, null, false); foreach (var kvp in interpValues) { var list new ListDataPoint(); foreach (AFValue v in kvp.Value) { // 过滤质量只保留Good值 if (v.Status.IsGood) { list.Add(new DataPoint { Timestamp v.Timestamp.LocalTime, Value v.Value }); } } result[kvp.Key.Name] list; } return result; } } public class DataPoint { public DateTime Timestamp { get; set; } public object Value { get; set; } }这段代码有几个地方我特意做了处理AFElement.FindElement用的是元素路径而不是数据库内的显示名要求你明确给定如\\PIServer01\Database\LineA\Reactor这样的路径批量插值查询一次性处理多属性极大减少了请求次数质量戳判断放在最里层循环确保每个数据点都是可信的。值得说明的是AFAttribute.InterpolatedValues的重载版本很多我用的这个签名是一次性传多个属性、统一时间范围和统一插值间隔适合多个测点用同一个时间网格的场景。如果你每个测点需要不同的时间间隔那就要分开查或者按属性分组循环查。另外这里的AFTime既支持DateTime传参也支持字符串DateTime.Now和DateTime.UtcNow看你PI服务器时区匹配。我建议在仓储层统一用UTC时间传参展示层再转本地时间能有效避免时区混乱。8. 从“能跑”到“好用”我在生产环境里沉淀的经验数据读取这事看起来就是“连一下取个值”但真正到了生产环境你会发现需要处理的全是边界问题连接断了怎么重连、PI服务器重启了怎么自动恢复、点表改了名称怎么兼容、历史数据迁移了怎么适配。关于重连机制我强烈建议不要自己裸写循环重连而是利用AF SDK提供的事件和状态自动恢复。PI SDK和AF SDK在连接断开时访问对象的方法会抛出异常你可以捕获特定异常类型然后等待几秒重连。生产环境里PI服务器偶尔会维护重启如果你没有一个健壮的重连机制整个服务可能就卡死在异常里了。我实际用的重连策略是每次请求前检查连接状态IsConnected如果断开则重新Connect()并且做最多3次退避重试间隔1秒、2秒、4秒。如果仍然失败就记录日志并抛出业务异常让上层调度知道当前数据不可用。这样能避免长时间阻塞在连接等待上。关于点表兼容如果你读取的PI点名称是配置表里的字符串当PI点被重命名或迁移到新服务器时程序会找不到点。我的做法是定义一个数据字典层把“业务信号名如反应釜温度”映射到“真实PI点全名如TAG_RCT_TEMP_XXX”当点表变化时只改配置不用改代码。这个映射可以放数据库、配置文件或者PI系统的AF模型里我偏好放AF模型里因为AF属性本身就是干这个用的。关于日志每笔PI读取请求都应该记录读取的PI点/属性、时间范围、读到的记录条数、耗时、是否有质量异常。在正式排查问题时这些日志简直是无价之宝。我见过太多项目出了问题只知道“查不到数据”但没有日志无法判断是权限问题、网络问题还是PI点本身无数据。关于性能监控你最好在PI读取模块里加一个计数器比如每分钟读取次数、平均响应时间、失败率。这个在用PI Web API或者AF SDK批量读取时很容易实现。一旦PI服务器响应变慢你能第一时间从指标图上看到异常而不是等用户投诉了才去排查。最后如果你想长期维护这套PI读取程序我建议你把数据读取层独立成一个类库对外暴露简洁的接口不要让业务代码到处直接调PI SDK。这样当PI系统升级比如从PI 3.x升级到PI AF或者你需要把数据源从PI换成另一套历史库时只需要改内部实现业务层完全不用动。这种抽象在工控系统里尤其重要因为DCS品牌和PI系统版本升级是迟早的事。我参与了不止一个工厂信息化项目几乎每个项目都会经历“数据源变了代码要改”的痛苦。但凡是当初把读取层做独立的改造都很快凡是业务代码里穿插着几十处PI SDK调用的最后都改得痛不欲生。所以请务必把“数据读取模块”当成一个正经的上游接口来设计而不是随手写几个工具类就完事。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑