15 · 自动化 API 总览
程序集:Hmi.Automation · 源码:src/Hmi.Automation/
什么时候你会需要它
如果你要"操作工程"——读它、改它、批量生成它——就是这里。
- 批量导入几百个点
- 自动生成一批结构相同的画面
- 把工程信息同步到 MES / ERP
- 给外部 AI 开一个组态入口(见第 18 章)
- 写一个工程检查工具(校验、统计、生成报表)
一句话概括
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。
踩过的坑
- 忘了
AttachCompiler——CheckScript/BuildScripts会回"编译器未接入"。 - 改了不
Save()—— 内存改了,磁盘没动。 - 属性名写错 —— 静默跳过。先
DescribeElementProps。 - 设备参数乱填 —— 先
DescribeProtocol拿 schema。 - 点
Id跳号 ——Validate()会失败。 - 切换工程后没重挂编译器 —— 切工程会清空 compiler 并触发
ProjectSwitched事件, 宿主必须按新工程重挂一次。 - 不设
Source—— 审计里全是空的,出了问题查不出是谁改的。
小结
Hmi.Automation是操作工程的统一入口,界面 / MCP / 你自己的程序共用同一套方法- 初始化三步:
Open→ 设Source→AttachCompiler - 方法分两类:组态(改) 和 感知(只读查询)
- 所有方法返回
AutomationResult,失败不抛异常,检查r.Ok - 改完必须
Save() - 改属性前先
DescribeElementProps,建设备前先DescribeProtocol - 只碰工程文件,不碰运行态 —— 这是它能开放给 AI 的原因
下一步:第 16 章 自动化 API 方法速查 —— 需要哪个方法直接查。