15 · 自动化 API 总览

程序集:Hmi.Automation · 源码:src/Hmi.Automation/

什么时候你会需要它

如果你要"操作工程"——读它、改它、批量生成它——就是这里。

  • 批量导入几百个点
  • 自动生成一批结构相同的画面
  • 把工程信息同步到 MES / ERP
  • 给外部 AI 开一个组态入口(见第 18 章)
  • 写一个工程检查工具(校验、统计、生成报表)

💡 这一章是"我要用"的部分。方法名清单在第 16 章, 底层文件长什么样在第 06 章。

一句话概括

Hmi.Automation 是操作工程的唯一推荐入口 —— 它把"改工程"和"读工程"统一成一套方法,并且帮你做了校验和审计。

        界面(前端)       外部 AI(MCP)      你自己的程序
              │                 │                 │
              └────────┬────────┴────────┬────────┘
                       ▼                 ▼
              AutomationRpcDispatcher  ProjectMcpTools
                       │                 │
                       └────────┬────────┘
                                ▼
                    IProjectAutomation(同一套方法)
                                │
                                ▼
                        LoadedProject(内存工程)

关键点:三个出口共用同一套方法。 所以你在界面上能做的事,外部 AI 也能做 —— 行为完全一致。

它管什么、不管什么

管 不管
改工程(点、通道、画面、元素、脚本、配方…) ❌ 运行态写值
读工程(列出所有点、查元素属性…) ❌ PLC 下发
校验工程 ❌ 设备启停
编译检查脚本 ❌ 读实时值
导入导出、打包加密

⚠ 这条边界是刻意的:Automation 只碰"磁盘上的工程",不碰"运行中的设备"。 所以它敢开放给外部 AI —— 就算 AI 把工程改坏了,最坏结果是"工程文件不对",不会让现场设备乱动。 需要运行态能力请用 Hmi.Backend(见第 10 章)。

三步打开一个工程

using Hmi.Automation;

// 1. 打开(读 project.json,没有会抛异常)
ProjectAutomation auto = ProjectAutomation.Open(@"C:\我的工程");

// 2. 标记来源(进审计日志,便于追溯"这次改动是谁做的")
auto.Source = "MyTool";

// 3. 接上脚本编译器(不接的话,CheckScript / BuildScripts 会回"编译器未接入")
auto.AttachCompiler(new RoslynScriptChecker(auto.Loaded));

⚠ 第 3 步经常被忘。忘了的话脚本相关的方法会返回"编译器未接入"这种让人摸不着头脑的失败。 自己写工具时记得接上。

新建一个空工程:

AutomationResult r = ProjectAutomation.CreateBlank(@"C:\新工程", name: "测试工程");
if (!r.Ok) Console.Error.WriteLine(r.Message);

两类方法:组态 和 感知

IProjectAutomation 有 132 个方法(外加 Source / Audit 两个属性),但只有两大类:

组态类(改工程)

名字通常是动词开头:Add / Remove / Set / Update / Create / Delete / Move / Apply…

auto.AddTag(new TagDef { Name = "Temp1", Type = TagDataType.Float, … });
auto.SetElementProps("主画面", "按钮1", new() { ["Text"] = "启动" });
auto.CreateScreen("报警页");
auto.Save();

改完必须 Save() —— 改的是内存,不保存就白改。

感知类(只读查询)

名字通常是 Get / List / Describe / Validate 开头:

var screens  = auto.ListScreens();
var elements = auto.ListElements("主画面");
var props    = auto.DescribeElementProps("Button", "按钮1");   // 拿属性真名 + 当前值
var schema   = auto.DescribeProtocol("ModbusTcp");             // 拿协议支持的连接参数

感知类方法绝不会改任何东西,可以放心调。

统一返回:AutomationResult

所有方法都返回同一个结果类型,而且失败不抛异常:

public sealed class AutomationResult
{
    public bool Ok { get; }
    public string Message { get; }    // 失败原因(给人看的)
    public object? Data { get; }      // 成功时的数据
}
var r = auto.AddTag(newTag);
if (!r.Ok)
{
    Console.Error.WriteLine($"加失败了:{r.Message}");
    return;
}

💡 为什么要这样设计:这套 API 会被前端、脚本、外部 AI 调用。 抛异常的话,调用方得写一堆 try/catch;返回结果对象的话,一个 if (!r.Ok) 就够了。 而且错误信息是给人看的字符串,可以直接显示给用户或喂给 AI。

只有静态工厂才可能抛异常(Open 找不到 project.json 时抛 FileNotFoundException), 因为它们没法返回 AutomationResult。

审计:谁改了什么

foreach (var e in auto.Audit)
    Console.WriteLine($"{e.TimeUtc} [{e.Source}] {e.Action} → {(e.Ok ? "成功" : e.Detail)}");
public readonly record struct AuditEntry(
    DateTime TimeUtc, string Source, string Action, string Args, bool Ok, string Detail);

所有写操作都会留一条审计,包括来源(Source)、动作、参数、成功与否。 这对"工程被谁改坏了"这类问题非常有用。

💡 Source 就是你在第 2 步设的那个值。自己写工具时一定要设,不然审计里分不清是谁干的。

完整示例:批量生成 100 个点

using Hmi.Automation;
using Hmi.Core.Models;

var auto = ProjectAutomation.Open(@"C:\我的工程");
auto.Source = "批量导入工具";
auto.AttachCompiler(new RoslynScriptChecker(auto.Loaded));

// 先看看现在有多少点,因为新点的 Id 必须接着排
var existing = auto.ListTags();
int nextId = existing.Count;   // 点 Id 必须 0 起连续,不能跳号

int ok = 0, fail = 0;
for (int i = 1; i <= 100; i++)
{
    var tag = new TagDef
    {
        Id       = nextId++,
        Name     = $"Line1.Dev{i}.Temp",
        Type     = TagDataType.Float,
        ChannelId = 0,
        DeviceId  = 0,
        Address   = (40000 + i).ToString(),
        Unit      = "℃",
        Decimals  = 1,
    };

    var r = auto.AddTag(tag);
    if (r.Ok) ok++;
    else { fail++; Console.Error.WriteLine($"第 {i} 个失败:{r.Message}"); }
}

// 全部改完再校验一次,然后保存
var v = auto.Validate();
if (!v.Ok) { Console.Error.WriteLine($"工程校验没过:{v.Message}"); return; }

auto.Save();
Console.WriteLine($"完成:成功 {ok},失败 {fail}");

⚠ 注意 nextId 的处理:点 Id 必须 0 起连续无重复, 新点的 Id 要接着现有的往后排。跳号会让 Validate() 失败。

几个特别有用的方法

方法 为什么有用
DescribeElementProps(type, name?) 改元素属性前必调 —— 拿到属性真名和当前值,避免写错名字
DescribeProtocol(protocolId) 建设备前必调 —— 拿到这个协议支持的连接方式和参数
Validate() 改完自查一遍
CheckScript(code) 编译检查一段脚本,拿到带行列号的诊断
ListFunctionCatalog() 拿脚本函数库目录(就是第 13 章那份数据)
ExportProject / ImportProject 整工程备份恢复
PackProject 打加密运行包

为什么"改属性前必须先 Describe"

因为 SetElementProps 是按属性名反射写入的 —— 名字写错会被静默跳过(不报错,但也没生效)。

// ❌ 想当然写 TextAlignment,实际叫 TextAlign —— 静默失败
auto.SetElementProps("主画面", "标题", new() { ["TextAlignment"] = "Center" });

// ✅ 先问真名
var props = auto.DescribeElementProps("Text", "标题");
foreach (var p in props) Console.WriteLine($"{p.Name} = {p.Value}");
// 看到真名是 TextAlign,再写
auto.SetElementProps("主画面", "标题", new() { ["TextAlign"] = "Center" });

⭐ 这是踩过的坑:属性名写错不会报错,只会"看起来什么都没发生"。 所以改属性前先 DescribeElementProps 是硬规矩。 同理,建设备前先 DescribeProtocol。

踩过的坑

  1. 忘了 AttachCompiler —— CheckScript / BuildScripts 会回"编译器未接入"。
  2. 改了不 Save() —— 内存改了,磁盘没动。
  3. 属性名写错 —— 静默跳过。先 DescribeElementProps。
  4. 设备参数乱填 —— 先 DescribeProtocol 拿 schema。
  5. 点 Id 跳号 —— Validate() 会失败。
  6. 切换工程后没重挂编译器 —— 切工程会清空 compiler 并触发 ProjectSwitched 事件, 宿主必须按新工程重挂一次。
  7. 不设 Source —— 审计里全是空的,出了问题查不出是谁改的。

小结

  • Hmi.Automation 是操作工程的统一入口,界面 / MCP / 你自己的程序共用同一套方法
  • 初始化三步:Open → 设 Source → AttachCompiler
  • 方法分两类:组态(改) 和 感知(只读查询)
  • 所有方法返回 AutomationResult,失败不抛异常,检查 r.Ok
  • 改完必须 Save()
  • 改属性前先 DescribeElementProps,建设备前先 DescribeProtocol
  • 只碰工程文件,不碰运行态 —— 这是它能开放给 AI 的原因

下一步:第 16 章 自动化 API 方法速查 —— 需要哪个方法直接查。