mcpField
M
matlab-find-pi-assets

matlab-find-pi-assets

github.com/matlab数据与数据库1.2k51 次安装1 天前更新
代码仓库

关于 matlab-find-pi-assets

这是一个 MATLAB 技能指南,帮助用户通过 Industrial Communication Toolbox 查询 PI Data Archive 标签及 PI Asset Framework 资产数据。它区分标签检索与 AF 层级导航,并要求在读取前确认服务器、数据库及匹配对象。

主要功能

  1. 01按名称或描述查找 PI 标签
  2. 02读取标签当前值和历史数据
  3. 03浏览 AF 元素、模板及属性
  4. 04读取 AF 属性时间序列
  5. 05确认服务器、数据库和匹配结果

适用场景

  1. 01根据 PI 标签查询当前读数
  2. 02检索指定时间范围的历史数据
  3. 03按名称或模板查找 AF 资产
  4. 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 标签名称或描述时使用。

  1. 识别 PI Data Archive 服务(必须询问用户) — 如果用户未指定服务名称,请运行 PI 服务发现模式(参见发现可用的 PI Data Archive 服务),列出可用服务。如果未找到服务,请告知用户在主机上无法发现任何 PI Data Archive 服务,并请用户手动提供服务名称。如果找到服务,必须向用户展示列表并请其选择一个服务——绝不可自动选择或假定要使用哪个服务。请包含描述(如果有)以及“以上都不是”选项。如果用户选择“以上都不是”,请其手动提供服务名称。在用户确认服务之前,不要继续下一步。

  2. 连接 — piObj = piclient('serverName')

  3. 搜索标签 — 根据用户提供的名称或描述,使用 tagList = tags(piObj, Name='pattern*')

  4. 展示结果 — 向用户展示匹配的标签以供确认。tags() 会返回一个包含 Tags 列(字符串数组)的表格。

  5. 读取数据 — 使用 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+)

当用户需要浏览元素/属性层级结构时使用。

  1. 确定 AF 服务(必须询问用户) — 如果用户尚未指定服务器名称,请运行 AF 服务发现模式(参见发现可用的 AF 服务),列出可用服务器。如果未发现任何服务器,请告知用户主机上无法发现 AF 服务,并请用户手动提供服务器名称。如果发现了服务器,必须向用户展示列表并请用户选择一个服务器 — 绝不自动选择或假设要使用哪个服务器。请附上描述(如果有),并提供“以上都不是”选项。如果用户选择“以上都不是”,请他们手动提供服务器名称。在用户确认服务器之前,不要继续下一步。

  2. 连接 — afObj = afclient('serverName')

  3. 确认数据库(必须询问用户) — 使用 listDatabases(afObj) 显示可用数据库,并且必须请用户选择一个 — 绝不自动选择或假设要使用哪个数据库,即使只有一个数据库也不例外。listDatabases 仅返回名称的字符串数组 — 若要同时显示描述和名称,请使用 .NET SDK 后备方法(参见列出带描述的数据库)。如果用户不确定哪个数据库包含其元素,请运行跨数据库搜索模式(参见跨所有数据库搜索元素),找到正确的数据库。**在用户确认数据库之前,不要继续下一步。**确定后,使用 selectDatabase(afObj, dbList(N)) 或 afObj = afclient('serverName', Database='dbName') 连接。

  4. 查找元素并消除歧义 — 始终使用通配符搜索来查找相关元素:

    • 搜索: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,帮助他们导航。只有在用户确认后才能继续。

  5. 获取属性并确认 — 使用 getAttributes(elem, 'attributeName*') 和通配符名称筛选条件。如果只有一个属性匹配,且明显符合用户的请求,则直接继续。重要 如果有多个属性匹配或存在歧义(例如“Active Power”与“Active Power Generated”,或不同元素中有多个同名属性),请用表格列出匹配的属性,显示 Name、ElementName、ServerDataType、DefaultUnit、HasTimeSeriesData 和 Description,然后请用户选择正确的属性。元素名称和数据类型可帮助用户区分不同元素上同名的属性。

  6. 验证属性 — 用户确认属性后,读取数据前执行以下检查:

    • 验证父元素: 确认 attr.ElementName 与用户指定的资产相符。若要验证完整层级,请使用 attr.Path(格式:\\server\database\...\element|attribute)——向用户显示时,去掉 \\server\database\ 前缀,并用 -> 连接(例如:Flynn River Hydro->Flynn I->GU1->GU1 Generator|Active Power)。如果元素指向的资产与用户要求的不同(例如,兄弟元素上存在名称相似的属性),请在继续之前告知用户。
    • 检查时间序列可用性: 如果用户请求历史数据或指定时间范围的数据,而 HasTimeSeriesData 为 false,请告知用户所选属性不包含时间序列数据,并询问他们是否仍希望改为读取当前值。
  7. 读取数据 — 根据用户的请求选择:

    • 当前值或未指定时间范围: data = read(attr) — 返回属性的快照/当前值
    • 历史数据并指定时间范围: data = readHistory(attr, startTime, endTime, Interval=hours(1), AggregateFcn="average")

    如果用户说“获取该值”“当前读数是多少”,或请求数据时未指定日期范围,请使用 read(attr)。

  8. 验证 — 检查返回的数据状态是否有效(不能全部为“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——绝不使用原始 .NET OSIsoft.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),并请用户选择
用户询问当前值时使用 readHistoryreadHistory 需要指定时间范围——对于快照值来说过于复杂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).DataTypeicomm.af.Attribute 中不存在此属性——运行时会报错使用 attrs(i).ServerDataType 获取数据类型字符串
直接使用 AF 结果中的 data.Value 进行数学运算AF 的 readHistory/read(attr) 会将 Value 作为元胞数组返回——无法执行算术运算使用 cell2mat(data.Value) 从 AF 数据中提取数值

版权所有 2026 The MathWorks, Inc.


评论

支持 Markdown