关于 matlab-find-pi-assets
这是一个 MATLAB 技能指南,帮助用户通过 Industrial Communication Toolbox 查询 PI Data Archive 标签及 PI Asset Framework 资产数据。它区分标签检索与 AF 层级导航,并要求在读取前确认服务器、数据库及匹配对象。
主要功能
- 01按名称或描述查找 PI 标签
- 02读取标签当前值和历史数据
- 03浏览 AF 元素、模板及属性
- 04读取 AF 属性时间序列
- 05确认服务器、数据库和匹配结果
适用场景
- 01根据 PI 标签查询当前读数
- 02检索指定时间范围的历史数据
- 03按名称或模板查找 AF 资产
- 04浏览资产属性并读取数据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ matlab/ matlab-agentic-toolkit --skill matlab-find-pi-assets安装后在 Claude Code 中输入/matlab-find-pi-assets确认已加载。
附带文件manifest.yamlreferences/key-functions.md
详细介绍
PI 资产识别与数据查询
通过 MATLAB 的 Industrial Communication Toolbox 封装连接 PI 系统、定位资产并读取历史数据,而非直接使用 .NET SDK 互操作。
发布要求
| 工作流 | 最低版本 | 函数 |
|---|---|---|
| A:PI Data Archive(标签查找) | R2022a+ | piclient、tags、read(piclient) |
| B:PI Asset Framework(AF 导航) | R2026a+ | afclient、findElementByName、getAttributes、readHistory 等 |
适用场景
- 用户想查找或查询 PI Asset Framework 元素或属性
- 用户提供 PI 标签名称或标签描述,并希望查找该标签
- 用户需要读取 PI AF 属性或 PI 标签的历史数据
- 用户提到 PI AF 服务、PI Data Archive、OSIsoft、AVEVA PI 或资产层级结构
- 用户想按模板列出元素(例如“所有发电机组”)
不适用场景
- 创建或修改 AF 元素/模板(管理操作)
- 实时流、事件帧或通知
- 访问 PI Vision 或 PI Web API
- 将数据写回 PI AF 或 PI Data Archive
决策逻辑
根据用户提供的信息选择工作流:
| 用户提供的信息 | 工作流 | 入口函数 |
|---|---|---|
PI 标签名称(例如,"GU1.ActivePower") | A:直接查找标签 | piclient → tags |
| 标签描述或部分标签名称 | A:直接查找标签 | piclient → tags |
| AF 中的元素名称、模板或属性 | B:AF 导航 | afclient |
| 浏览/探索资产层级结构的请求 | B:AF 导航 | afclient |
如果不清楚,请询问用户:“您有特定的 PI 标签名称,还是需要导航 Asset Framework 层级结构?”
工作流 A:直接查找标签(R2022a+)
当用户提供 PI 标签名称或描述时使用。
-
识别 PI Data Archive 服务(必须询问用户) — 如果用户未指定服务名称,请运行 PI 服务发现模式(参见发现可用的 PI Data Archive 服务),列出可用服务。如果未找到服务,请告知用户在主机上无法发现任何 PI Data Archive 服务,并请用户手动提供服务名称。如果找到服务,必须向用户展示列表并请其选择一个服务——绝不可自动选择或假定要使用哪个服务。请包含描述(如果有)以及“以上都不是”选项。如果用户选择“以上都不是”,请其手动提供服务名称。在用户确认服务之前,不要继续下一步。
-
连接 —
piObj = piclient('serverName') -
搜索标签 — 根据用户提供的名称或描述,使用
tagList = tags(piObj, Name='pattern*') -
展示结果 — 向用户展示匹配的标签以供确认。
tags()会返回一个包含Tags列(字符串数组)的表格。 -
读取数据 — 使用
tagName = tagList.Tags(N)提取标签名称,然后根据用户的请求选择:- 当前值或未指定时间范围:
data = read(piObj, tagName)— 返回标签的快照/当前值 - 指定时间范围的历史数据:
data = read(piObj, tagName, DateRange=[start end], Interval=..., AggregateFcn=...)
如果用户说“获取数值”“当前读数是多少”,或请求数据时未指定日期范围,请使用不带 DateRange 的
read(piObj, tagName)。 - 当前值或未指定时间范围:
工作流 B:Asset Framework 导航(R2026a+)
当用户需要浏览元素/属性层级结构时使用。
-
确定 AF 服务(必须询问用户) — 如果用户尚未指定服务器名称,请运行 AF 服务发现模式(参见发现可用的 AF 服务),列出可用服务器。如果未发现任何服务器,请告知用户主机上无法发现 AF 服务,并请用户手动提供服务器名称。如果发现了服务器,必须向用户展示列表并请用户选择一个服务器 — 绝不自动选择或假设要使用哪个服务器。请附上描述(如果有),并提供“以上都不是”选项。如果用户选择“以上都不是”,请他们手动提供服务器名称。在用户确认服务器之前,不要继续下一步。
-
连接 —
afObj = afclient('serverName') -
确认数据库(必须询问用户) — 使用
listDatabases(afObj)显示可用数据库,并且必须请用户选择一个 — 绝不自动选择或假设要使用哪个数据库,即使只有一个数据库也不例外。listDatabases仅返回名称的字符串数组 — 若要同时显示描述和名称,请使用 .NET SDK 后备方法(参见列出带描述的数据库)。如果用户不确定哪个数据库包含其元素,请运行跨数据库搜索模式(参见跨所有数据库搜索元素),找到正确的数据库。**在用户确认数据库之前,不要继续下一步。**确定后,使用selectDatabase(afObj, dbList(N))或afObj = afclient('serverName', Database='dbName')连接。 -
查找元素并消除歧义 — 始终使用通配符搜索来查找相关元素:
- 搜索:
results = findElementByName(afObj, 'baseIdentifier*'),其中baseIdentifier是用户提到的最短标识符(例如,从“GU1 generator”中提取'GU1') - 按模板搜索(按类型搜索时):
elems = findElementByTemplate(afObj, 'templateName') - 按路径搜索(已知完整路径时):
elem = findElementByPath(afObj, "\\server\database\path\to\element")— 必须使用字符串(双引号),绝不能使用字符向量(单引号) — 字符向量输入会静默失败,即使路径正确也会返回错误
**始终向用户展示结果以供确认:**无论返回多少个元素,都要向用户展示一个表格,列出 Name、Template、Description、使用箭头表示法的层级路径(例如,
Flynn River Hydro->Flynn I->GU1->GU1 Generator— 去掉\\server\database\前缀,并用->连接)以及 NumChildren。请用户确认他们指的是哪个元素。如果用户需要更多详情,请对特定元素使用getChildren或getAttributes,帮助他们导航。只有在用户确认后才能继续。 - 搜索:
-
获取属性并确认 — 使用
getAttributes(elem, 'attributeName*')和通配符名称筛选条件。如果只有一个属性匹配,且明显符合用户的请求,则直接继续。重要 如果有多个属性匹配或存在歧义(例如“Active Power”与“Active Power Generated”,或不同元素中有多个同名属性),请用表格列出匹配的属性,显示 Name、ElementName、ServerDataType、DefaultUnit、HasTimeSeriesData 和 Description,然后请用户选择正确的属性。元素名称和数据类型可帮助用户区分不同元素上同名的属性。 -
验证属性 — 用户确认属性后,读取数据前执行以下检查:
- 验证父元素: 确认
attr.ElementName与用户指定的资产相符。若要验证完整层级,请使用attr.Path(格式:\\server\database\...\element|attribute)——向用户显示时,去掉\\server\database\前缀,并用->连接(例如:Flynn River Hydro->Flynn I->GU1->GU1 Generator|Active Power)。如果元素指向的资产与用户要求的不同(例如,兄弟元素上存在名称相似的属性),请在继续之前告知用户。 - 检查时间序列可用性: 如果用户请求历史数据或指定时间范围的数据,而
HasTimeSeriesData为false,请告知用户所选属性不包含时间序列数据,并询问他们是否仍希望改为读取当前值。
- 验证父元素: 确认
-
读取数据 — 根据用户的请求选择:
- 当前值或未指定时间范围:
data = read(attr)— 返回属性的快照/当前值 - 历史数据并指定时间范围:
data = readHistory(attr, startTime, endTime, Interval=hours(1), AggregateFcn="average")
如果用户说“获取该值”“当前读数是多少”,或请求数据时未指定日期范围,请使用
read(attr)。 - 当前值或未指定时间范围:
-
验证 — 检查返回的数据状态是否有效(不能全部为“Bad”或 NaN)
关键函数
完整的函数表、属性及返回类型请参见 references/key-functions.md。
模式
发现可用的 AF 服务器
NET.addAssembly('OSIsoft.AFSDK');
ps = OSIsoft.AF.PISystems;
numServers = ps.Count;
if numServers == 0
fprintf('No AF servers discovered on this machine.\n');
else
fprintf('Available AF Servers:\n');
for i = 0:numServers-1
serverName = string(ps.Item(i).Name);
desc = string(ps.Item(i).Description);
fprintf(' %d. %s\n', i+1, serverName);
if strlength(desc) > 0
fprintf(' Description: %s\n', desc);
end
end
end
如果 numServers == 0,请让用户手动提供 AF 服务器名称——此主机上无法发现任何服务器。
发现可用的 PI Data Archive 服务器
NET.addAssembly('OSIsoft.AFSDK');
piServers = OSIsoft.AF.PI.PIServers;
numServers = piServers.Count;
if numServers == 0
fprintf('No PI Data Archive servers discovered on this machine.\n');
else
fprintf('Available PI Data Archive Servers:\n');
for i = 0:numServers-1
serverName = string(piServers.Item(i).Name);
desc = string(piServers.Item(i).Description);
fprintf(' %d. %s\n', i+1, serverName);
if strlength(desc) > 0
fprintf(' Description: %s\n', desc);
end
end
end
如果 numServers == 0,请让用户手动提供 PI Data Archive 服务器名称——此主机上无法发现任何服务器。
按名称直接查找标签
piObj = piclient('myPIServer');
tagList = tags(piObj, Name='GU1*');
disp(tagList)
tags() 返回一个包含 Tags 列(字符串数组)的表。在传入 read() 前,使用 tagList.Tags(N) 提取标签名称。
读取 PI 标签的当前值
当用户未指定时间范围,且请求 PI 标签的当前/快照值时使用。
piObj = piclient('myPIServer');
tagList = tags(piObj, Name='GU1.Active Power*');
tagName = tagList.Tags(1);
data = read(piObj, tagName);
disp(data)
读取 PI 标签的历史数据
piObj = piclient('myPIServer');
tagList = tags(piObj, Name='GU1.Active Power*');
tagName = tagList.Tags(1);
startTime = datetime(2025, 1, 1);
endTime = datetime(2025, 2, 1);
data = read(piObj, tagName, DateRange=[startTime endTime], ...
Interval=hours(1), AggregateFcn="average");
disp(data)
通过服务器和数据库澄清 AF 连接
afObj = afclient('myAFServer', Database='OSIDemo_PG_HydroPlant');
fprintf('Connected to: %s\n', afObj.ServerName);
fprintf('Database: %s\n', afObj.Database);
列出带描述的数据库
连接到 AF 服务器后,使用此方法向用户展示数据库。listDatabases 返回数据库名称的字符串数组(不含描述)。若要同时显示描述和名称,请使用下方的 .NET SDK 后备方案。
afObj = afclient('myAFServer');
dbList = listDatabases(afObj);
fprintf('Available Databases on %s:\n', afObj.ServerName);
for i = 1:numel(dbList)
fprintf(' %d. %s\n', i, dbList(i));
end
获取描述(需要 .NET AFSDK):
NET.addAssembly('OSIsoft.AFSDK');
ps = OSIsoft.AF.PISystems;
afServer = ps.Item('myAFServer');
afServer.Connect();
fprintf('Available Databases on %s:\n', string(afServer.Name));
for i = 0:afServer.Databases.Count-1
db = afServer.Databases.Item(i);
dbName = string(db.Name);
desc = string(db.Description);
fprintf(' %d. %s\n', i+1, dbName);
if strlength(desc) > 0
fprintf(' Description: %s\n', desc);
end
end
按模板查找元素
afObj = afclient('myAFServer', Database='OSIDemo_PG_HydroPlant');
genUnits = findElementByTemplate(afObj, 'GenerationUnit');
disp(genUnits)
使用通配符查找元素并确认
始终对基础标识符使用通配符,以发现父元素和子元素。将结果展示给用户确认,即使只找到一个元素也是如此。
afObj = afclient('myAFServer', Database='OSIDemo_PG_HydroPlant');
results = findElementByName(afObj, 'GU1*');
fprintf('Found %d element(s) matching "GU1*":\n', numel(results));
for i = 1:numel(results)
pathParts = split(results(i).Path, '\');
hierarchy = strjoin(pathParts(4:end), '->');
fprintf(' %d. %s [Template: %s] Path: %s\n', ...
i, results(i).Name, results(i).Template, hierarchy);
if strlength(results(i).Description) > 0
fprintf(' Description: %s\n', results(i).Description);
end
end
fprintf('Which element would you like to proceed with?\n');
用户确认后(例如,元素 2 = “GU1 Generator”),使用通配符获取属性并显示元数据:
selectedElem = results(2);
attrs = getAttributes(selectedElem, 'Active Power*');
fprintf('Matching attributes on "%s":\n', selectedElem.Name);
fprintf(' %-4s %-20s %-20s %-14s %-14s %-10s %s\n', ...
'#', 'Name', 'Element', 'ServerDataType', 'Unit', 'TimeSeries', 'Description');
for i = 1:numel(attrs)
desc = attrs(i).Description;
if strlength(desc) == 0
desc = "(none)";
end
fprintf(' %-4d %-20s %-20s %-14s %-14s %-10d %s\n', ...
i, attrs(i).Name, attrs(i).ElementName, attrs(i).ServerDataType, ...
attrs(i).DefaultUnit, attrs(i).HasTimeSeriesData, desc);
end
如果只匹配到一个属性,且它明显符合用户的请求,则直接继续。如果匹配到多个属性或存在歧义(包括不同元素具有多个同名属性),则展示上表并请用户选择。ElementName 和数据类型有助于区分同名属性。
注意:父元素和子元素的属性名称可能不同(例如,GU1 的属性为 “Active Power Generated”,而 GU1 Generator 的属性为 “Active Power”)。通配符查询也可能从多个同级元素中返回相同属性(例如,风电场中的所有发电机)。使用 ElementName 列消除歧义。
按名称查找元素(单个结果)
即使只返回一个匹配结果,也要先展示给用户确认,再继续操作。
afObj = afclient('myAFServer', Database='OSIDemo_PG_HydroPlant');
results = findElementByName(afObj, 'GU3 Turbine*');
fprintf('Found %d element(s) matching "GU3 Turbine*":\n', numel(results));
for i = 1:numel(results)
pathParts = split(results(i).Path, '\');
hierarchy = strjoin(pathParts(4:end), '->');
fprintf(' %d. %s [Template: %s] Path: %s\n', ...
i, results(i).Name, results(i).Template, hierarchy);
end
fprintf('Proceed with this element?\n');
确认后:
elem = results(1);
attr = getAttributes(elem, 'Active Power');
fprintf('Name: %s\n', attr.Name);
fprintf('Unit: %s\n', attr.DefaultUnit);
fprintf('Has Time Series: %d\n', attr.HasTimeSeriesData);
从 AF 属性读取当前值
当用户询问当前值、快照或数据,但未指定时间范围时使用。
afObj = afclient('myAFServer', Database='OSIDemo_PG_HydroPlant');
results = findElementByName(afObj, 'GU1*');
selectedElem = results(2);
attr = getAttributes(selectedElem, 'Active Power');
data = read(attr);
disp(data)
使用聚合读取历史数据
afObj = afclient('myAFServer', Database='OSIDemo_PG_HydroPlant');
elem = findElementByName(afObj, 'GU3 Turbine');
attr = getAttributes(elem, 'Cooling Water Output Temperature');
startTime = datetime(2025, 1, 1);
endTime = datetime(2025, 2, 1);
data = readHistory(attr, startTime, endTime, Interval=hours(1), AggregateFcn="average");
disp(data)
验证数据质量
readHistory 和 read 会返回一个包含 Status 列的表格。使用值之前,请检查是否存在状态为 “Bad” 的数据:
badRows = data.Status == "Bad";
if all(badRows)
warning('All returned data has "Bad" status — no valid values in this time range.');
elseif any(badRows)
fprintf('%d of %d rows have "Bad" status.\n', sum(badRows), height(data));
end
值列的类型因数据来源而异:
- AF(
readHistory、read(attr)):Value是一个元胞数组——使用cell2mat(data.Value)提取数值,或使用data.Value{i}索引 - PI Data Archive(
read(piObj, tagName, DateRange=...)):Value是一个double数组(可直接访问数值) - PI Data Archive 当前值(不带 DateRange 的
read(piObj, tagName)):Value是一个字符串(例如"Pt Created",或以文本表示的数值)
使用 getChildren 浏览层级结构
afObj = afclient('myAFServer', Database='OSIDemo_PG_HydroPlant');
roots = getRootElements(afObj);
disp(roots)
children = getChildren(roots(1));
disp(children)
在所有数据库中搜索元素
当用户不确定要查找的元素位于哪个数据库中时使用。
afObj = afclient('myAFServer');
dbList = listDatabases(afObj);
elementName = 'GU1';
foundIn = {};
for i = 1:numel(dbList)
selectDatabase(afObj, dbList(i));
results = findElementByName(afObj, elementName);
if ~isempty(results)
foundIn{end+1} = dbList(i); %#ok<SAGROW>
end
end
if isempty(foundIn)
fprintf('Element "%s" not found in any database.\n', elementName);
else
fprintf('Element "%s" found in:\n', elementName);
for i = 1:numel(foundIn)
fprintf(' %d. %s\n', i, foundIn{i});
end
end
如果在多个数据库中找到该元素,请展示列表并请用户选择正确的数据库。如果在所有数据库中都未找到,请让用户确认元素名称。
约定
- 访问 Asset Framework 时始终使用
afclient——绝不使用原始 .NETOSIsoft.AFSDK互操作(例外:可以通过OSIsoft.AF.PISystems/OSIsoft.AF.PI.PIServers发现服务器) - 访问 PI Data Archive 标签时始终使用
piclient——绝不使用原始 .NET 互操作(例外:发现服务器) - 必须向用户展示发现的服务器并请用户选择——绝不自动选择、擅自假设或跳过服务器选择。用户确认服务器前不得继续
- 展示服务器或数据库列表时,如果有描述,请同时显示名称和描述(描述需要回退到 .NET SDK 获取——
listDatabases只返回名称) - 必须向用户展示发现的数据库并请用户选择——绝不自动选择、擅自假设或跳过数据库选择(即使只有一个数据库也不例外)。用户确认数据库前不得继续
- 绝不假设服务器名称是
'localhost'——请用户指定,或从可用服务器中选择 findElementByPath和findAttributeByPath的路径参数必须使用字符串(双引号)——字符(单引号)会静默失败。应写为findElementByPath(afObj, "\\server\db\path"),绝不要写成findElementByPath(afObj, '\\server\db\path')- 使用带名称筛选条件的
getAttributes(elem, 'name')——绝不检索所有属性后再通过字符串比较 - 使用
findElementByTemplate按类型查找元素——绝不手动遍历层级来比较模板名称 - 使用通配符
findElementByName(afObj, 'baseIdentifier*')查找元素,以发现父元素和子元素——绝不只用精确名称进行搜索 - 继续操作前,始终向用户展示元素搜索结果(Name、Template、Description、层级路径)以供确认——即使只找到一个元素也不例外
- 当多个属性匹配通配符或存在歧义时,展示元数据表(Name、ElementName、ServerDataType、DefaultUnit、HasTimeSeriesData)并请用户选择——如果只有一个属性明显匹配,则直接继续
- 如果用户需要更详细的层级信息,使用
getChildren(elem)展示特定元素的子元素 - 用户请求“当前值”“获取数据”或任何未指定时间范围的内容时,使用
read(attr)或read(piObj, tagName)(不带 DateRange)——仅在指定日期范围时才使用readHistory或read(..., DateRange=...) - 用户已知标签名称时,优先使用
piclient+tags——无需绕道 AF
常见错误
| 错误 | 错误原因 | 正确做法 |
|---|---|---|
使用 NET.addAssembly('OSIsoft.AFSDK') 查询数据 | 冗长、脆弱且没有必要——已有 MATLAB 封装 | 使用 afclient 或 piclient(AFSDK 仅可用于服务器发现) |
使用 piclient 查询 AF 元素 | piclient 用于 PI Data Archive 标签,而非 AF 层级结构 | 使用 afclient 导航元素/属性 |
用户提供标签名称时使用 afclient | 对于直接查找标签,AF 导航过于复杂 | 使用 piclient + tags(..., Name=...) |
假设服务器是 'localhost' | 服务器名称因环境而异 | 向用户询问服务器名称 |
| 未先询问用户就扫描所有数据库 | 在大型系统上会消耗大量资源且速度缓慢 | 先询问用户;仅在用户不确定时扫描所有数据库 |
| 获取所有属性后再按字符串筛选 | 对于属性众多的元素,这种做法效率低下 | 使用 getAttributes(elem, 'name') 并按名称筛选 |
| 手动遍历层级结构来查找模板对应的元素 | 对于较深的层级结构,这种做法速度慢且容易出错 | 使用 findElementByTemplate(afObj, 'templateName') |
不向用户展示 findElementByName 的结果就直接选用 | 父元素和子元素通常具有相同的名称前缀(例如 GU1 与 GU1 Generator)——用户想找子元素时,第一个匹配项可能是父元素 | 始终使用通配符搜索('GU1*'),展示所有结果及其 Template/Path/Description,并请用户确认——即使只有一个结果也要确认 |
未确认属性就调用 readHistory | 不同元素的属性名称可能不同(例如 "Active Power" 与 "Active Power Generated"),通配符也可能返回多个匹配项——或者多个同级元素可能具有相同的属性名称(例如风电场中的所有发电机) | 当存在多个属性匹配项或有歧义时,展示元数据(Name、ElementName、ServerDataType、Unit、HasTimeSeriesData、Description),并请用户选择 |
用户询问当前值时使用 readHistory | readHistory 需要指定时间范围——对于快照值来说过于复杂 | AF 属性使用 read(attr);PI 标签使用不带 DateRange 的 read(piObj, tagName);仅在查询历史数据时使用 readHistory/DateRange |
在 findElementByPath 或 findAttributeByPath 中使用 char(单引号)路径 | 这些函数使用 char 输入时会静默失败——即使路径正确,也会返回 "Unable to find" 错误 | 始终使用 string(双引号):findElementByPath(afObj, "\\server\db\path") |
将 listDatabases 的结果当作 struct 处理(例如 dbList(i).Name) | listDatabases 返回的是字符串数组,而不是 struct——访问 .Name 会报错 | 直接使用 dbList(i):它已经是字符串。将其传递给 selectDatabase(afObj, dbList(i)) |
对属性使用 attrs(i).DataType | icomm.af.Attribute 中不存在此属性——运行时会报错 | 使用 attrs(i).ServerDataType 获取数据类型字符串 |
直接使用 AF 结果中的 data.Value 进行数学运算 | AF 的 readHistory/read(attr) 会将 Value 作为元胞数组返回——无法执行算术运算 | 使用 cell2mat(data.Value) 从 AF 数据中提取数值 |
版权所有 2026 The MathWorks, Inc.