13 · 脚本函数库速查
相关:第 12 章 脚本运行时与脚本形态

这一章是什么
脚本里能直接调用的全部函数,一共 15 个库、153 个函数。

写脚本时不用背 —— 找到你要做的事对应的库,查一下函数名和参数就行。 组态器里也有「快速插入函数」面板,内容和这里同源。

💡 每个库开头都写了"这个库是干什么的、什么时候用",先看那一段,再看具体函数。

怎么用
脚本里直接写 库名.函数名(...),不用 using、不用 new:

// 读一个温度点,超过 80 就报警并写日志
if (Tag.IsGood("Oven.Temp") && Tag.GetFloat("Oven.Temp") > 80)
{
    Log.Warn($"烤箱温度偏高:{Tag.GetFloat(\"Oven.Temp\")}℃");
    Alarm.Trigger("烤箱温度过高", level: 2);
}
15 个库总览
库    干什么    函数数    可用
Tag    点位读写(最常用)    13    ✅
Math2    工控数学    30    ✅
Str    字符串处理    28    ✅
Bit    位运算与字节拆装    18    ✅
Fs    受限文件读写    13    ✅
Csv    CSV 整表读写    4    ✅
Alarm    触发/确认/清除报警    6    ✅
History    查历史数据    4    ✅
Report    报表 / 实时表    19    ✅
Log    写日志    3    ✅
Sys    时间与当前操作者    3    ✅
Timer    命名定时器    2    ✅
Comm    查设备在线/重连通道    2    ✅
Ui    开关画面、改元素属性    5    ⚠ 需运行态
Db    参数化 SQL    3    ⚠ 需子系统
⚠ 标"需运行态/需子系统"的库,在对应能力就绪前调用会抛 ScriptApiUnavailableException。 这个异常的意思是"宿主没接上这个能力",不是你的脚本写错了。

Tag —— 点位读写
什么时候用:任何要读现场数据、或者写值的场合。这是用得最多的库。

读值有四个类型化方法,别用 Tag.Get(那个不转换,一般用不上)。

函数    干什么
Tag.GetFloat(name) → double    读浮点值(温度/压力/液位这类模拟量)
Tag.GetInt(name) → int    读整数(计数、档位、状态码)
Tag.GetBool(name) → bool    读开关量(true=通)
Tag.GetString(name) → string    读成文本(拼提示语、下发字符串)
Tag.Get(name) → object    读原始值不转换(很少用)
Tag.Write(name, value)    写一个点(过写安全校验)
Tag.WriteMany(("点1",v1), ("点2",v2))    一次写多个点
Tag.GetFloats("点1","点2") → double[]    一次读多个点成数组
Tag.IsGood(name) → bool    质量好不好(联锁判断前必查)
Tag.QualityOf(name) → Quality    读质量枚举(要区分坏在哪时用)
Tag.Exists(name) → bool    点是否存在(拼动态点名时先探)
Tag.TimeOf(name) → DateTime    最后一次更新时刻(UTC,判断数据新鲜度)
Tag.Info(name) → TagMeta    读点定义(单位/量程/可写性)
两个必须养成的习惯:

// ① 读值前先查质量,否则坏值会静默返回 0
if (Tag.IsGood("温度") && Tag.GetFloat("温度") > 80) { … }

// ② 动态拼的点名先探存在,否则拼错名字也会返回 0
string name = $"设备{i}.温度";
if (Tag.Exists(name)) { … }
⚠ Tag.Write 写采集点在运行态是允许的(界面按钮就这么干),但脚本里写采集点会被门禁拦下 —— 见第 12 章的"写值边界"。

Math2 —— 工控数学
什么时候用:算工程量、做限幅保护、三角函数、统计一组数。全是纯函数,越界不抛错(比如 Sqrt(-1) 返回 0,Log(0) 返回 0)。

量程与保护(最常用)
函数    干什么
Math2.Scale(v, inMin, inMax, outMin, outMax) → double    线性量程变换。Scale(raw, 0, 27648, 0, 100) 把 PLC 原始码转成 0~100%
Math2.Clamp(v, lo, hi) → double    限幅到 [lo,hi]。下发设备前必做,防止算出界的数
Math2.Round(v, n) → double    四舍五入到 n 位小数
Math2.Abs(v) / Math2.Sign(v)    绝对值 / 符号(正1、负-1、零0)
取整
函数    干什么
Math2.Floor(v)    向下取整(Floor(3.9)=3,Floor(-1.1)=-2)
Math2.Ceil(v)    向上取整(算"够不够分几批"常用)
Math2.Truncate(v)    截断小数向零取整(和 Floor 对负数不同)
幂与对数
函数    干什么
Math2.Sqrt(v)    平方根(流量按差压开方线性化常用;负数返回 0)
Math2.Sqr(v) / Math2.Pow(b,e)    平方 / 任意次方
Math2.Exp(v) / Math2.Log(v) / Math2.Log10(v)    自然指数 / 自然对数 / 常用对数
三角(注意是弧度)
函数    干什么
Math2.Sin(rad) / Cos / Tan    三角函数,参数是弧度
Math2.Atan(v)    反正切,返回弧度,范围 ±90°
Math2.Atan2(y, x)    二参反正切,能区分四个象限(由 X/Y 分量求方向角用这个)
Math2.Deg2Rad(deg) / Math2.Rad2Deg(rad)    角度 ↔ 弧度换算
⚠ 现场习惯说角度,但 Sin/Cos/Tan 吃弧度 —— 先 Math2.Deg2Rad(30) 再传进去。

统计一组数
函数    干什么
Math2.Avg(a,b,c)    平均值(可直接接 Tag.GetFloats(...))
Math2.Sum(a,b,c)    求和(累计流量、多路合计)
Math2.MaxOf(a,b,c) / Math2.MinOf(a,b,c)    一组数的最大/最小值
Math2.Min(a,b) / Math2.Max(a,b)    两个数的较小/较大者
Math2.Mod(a,b)    取余(b=0 返回 0)。"每 N 次做一件事"用 Mod(N)==0
常量
Math2.Pi(π)、Math2.E(自然常数)

Str —— 字符串处理
什么时候用:拼提示语、解析设备回文、清洗用户输入。null 当空串、越界自动裁剪、解析失败给 fallback,绝不抛错。

截取与查找
函数    干什么
Str.Sub(s, start, len)    从 start 取 len 个字符(越界自动裁剪)
Str.Sub(s, start)    从 start 取到末尾
Str.Left(s, n) / Str.Right(s, n)    取最左 / 最右 n 个字符
Str.IndexOf(s, sub) → int    子串首次出现的下标(没有返回 -1)
Str.Between(s, a, b) → string    取 a、b 之间的文本 —— 解析设备返回帧特别好用
Str.Count(s, sub) → int    子串出现次数(不重叠)
Str.Contains(s, sub) → bool    是否包含
Str.StartsWith(s, p) / Str.EndsWith(s, p)    是否以某前缀/后缀开头结尾
增删改
函数    干什么
Str.Replace(s, old, new)    替换全部匹配
Str.Trim(s) / TrimStart / TrimEnd    去首尾 / 只去首 / 只去尾空白
Str.Upper(s) / Str.Lower(s)    转大写 / 小写(忽略大小写比对前先归一)
Str.PadLeft(s, w, '0')    左补齐到 w 位(补零:PadLeft("7",4,'0')="0007")
Str.PadRight(s, w)    右补齐(等宽列对齐、定长报文填充)
Str.Repeat(s, n)    重复 n 遍(画分隔线)
Str.Reverse(s)    反转字符顺序
拆分与拼接
函数    干什么
Str.Split(s, ",") → string[]    按分隔符拆(自动丢空项)。解析 CSV 行
Str.Join(",", a, b, c) → string    用分隔符拼多段(Split 的逆)
Str.Concat(a, b, c)    直接首尾相接(无分隔符)
Str.Format("{0}%", 75)    格式化(用 InvariantCulture,小数点固定是 .)
转换
函数    干什么
Str.ToInt(s, fallback=0) → int    转整数,转不动返回 fallback 不抛错
Str.ToDouble(s, fallback=0) → double    转浮点,同上
Str.IsEmpty(s) → bool    是否 null / 空串 / 全空白(校验必填输入第一步)
Str.Len(s) → int    字符个数(null 当空串,返回 0)
Bit —— 位运算与字节拆装
什么时候用:设备把一堆开关状态打包在一个寄存器里(状态字),你要拆出第几位;或者要把几个标志位拼成一个命令字。

取位 / 置位(状态字最常用)
函数    干什么
Bit.Get(value, bit) → bool    看第 bit 位是不是 1(bit=0 是最低位)。拆状态字的主力
Bit.Set(value, bit) → int    把第 bit 位置 1,返回新值
Bit.Clear(value, bit) → int    把第 bit 位清 0,返回新值
Bit.Toggle(value, bit) → int    翻转第 bit 位(做"按一下切换"的开关位)
Bit.PopCount(value) → int    数有几位置 1 —— 统计当前活动报警数
// 拆状态字:第 3 位是高温报警,第 5 位是急停
int status = Tag.GetInt("设备状态字");
bool highTemp = Bit.Get(status, 3);
bool eStop    = Bit.Get(status, 5);
Log.Info($"活动报警数:{Bit.PopCount(status)}");
按位运算(掩码)
函数    干什么
Bit.And(a, b)    按位与(取掩码位:And(状态字, 0x0F) 只留低 4 位)
Bit.Or(a, b)    按位或(合并多个标志位)
Bit.Xor(a, b)    按位异或(找哪些位变了、算 LRC 校验)
Bit.Not(a)    按位取反(算反掩码)
Bit.Shl(v, n) / Bit.Shr(v, n)    左移 / 右移(拼高低位、取位段)
字节与字拆装
函数    干什么
Bit.LowByte(v) / Bit.HighByte(v)    取低 / 高字节(0~255)
Bit.LowWord(v) / Bit.HighWord(v)    取低 / 高 16 位字(拆 32 位双字)
Bit.MakeWord(hi, lo) → int    高字节 + 低字节拼成 16 位字(字节序不对就把两个参数换一下)
十六进制
函数    干什么
Bit.ToHex(v, width=0) → string    转十六进制字符串,width>0 左补零(ToHex(255,4)="00FF")
Bit.FromHex("0xFF", fallback=0) → int    解析十六进制(认 0x 前缀,失败给 fallback)
Fs —— 受限文件读写
什么时候用:写生产日志、读配置/配方文件、导出报表。

⚠ 这个库被围栏限制在工程数据目录内。越界路径会安全失败 —— 读返回兜底值、写返回 false,绝不抛异常。 所以脚本读不到工程目录外面的文件,这是刻意的。

函数    干什么
Fs.AppendLine("logs/run.log", "设备已启动")    追加一行(自动换行) —— 写事件日志最常用
Fs.AppendCsvRow("logs/prod.csv", Sys.Now, Tag.GetFloat("温度"))    追加一行 CSV(自动转义逗号/引号) —— 做生产报表最顺手,Excel 能直接打开
Fs.AppendText(path, content)    追加文本,不自动换行
Fs.WriteText(path, content) → bool    覆盖写入(上级目录自动建)
Fs.ReadText(path, fallback="") → string    读整个文本(读失败给 fallback)
Fs.ReadLines(path) → string[]    按行读(每行一个元素)
Fs.Exists(path) → bool    文件是否存在
Fs.Size(path) → long    字节大小(不存在返回 -1)
Fs.Delete(path) → bool    删除(不存在也算成功)
Fs.MakeDir(path) → bool    建目录(一般不用,写文件会自动建)
Fs.ListFiles("logs", "*.csv") → string[]    列文件名(只给文件名,不含路径)
Fs.Combine("logs", "prod.csv") → string    拼路径(配合 ListFiles 拼回完整相对路径)
Fs.Root → string    当前围栏根目录(只用于日志里说明文件存哪了)
// 每天记一条产量到 CSV
Fs.AppendCsvRow("logs/产量.csv", Sys.Now, Tag.GetFloat("今日产量"));

// 遍历日志目录里所有 CSV
foreach (var f in Fs.ListFiles("logs", "*.csv"))
    Log.Info(Fs.Combine("logs", f));
⚠ WriteText 是覆盖不是追加。要往日志末尾续写用 AppendLine / AppendCsvRow。

Csv —— CSV 整表读写
什么时候用:把 CSV 当一整张表来用 —— 读回来遍历、按列删行、整表重写。 只想往日志末尾追加一行的话,用上面 Fs.AppendCsvRow 就够,不必上这个库。

⚠ 与 Fs 共用同一道围栏(工程数据目录内),越界一律安全失败:读不到给空表、 写不成返 false、删行删不动返 0,绝不抛异常。 ⚠ 首行一律当表头。用 Fs.AppendCsvRow 写出来的文件是没有表头的, 想用 Csv 读,第一行得自己先写成表头。

函数    干什么
Csv.ReadTable(path) → 行集    读整张表:每行一个字典,键=表头列名(rows[0]["操作员"])
Csv.AppendRow(path, 表头, row) → bool    追加一行;文件还不存在且给了表头,就先写一行表头
Csv.WriteTable(path, 表头, rows) → bool    整表覆盖写(第一行写表头)
Csv.DeleteRows(path, "列名", "值") → int    删掉该列恰好等于该值的所有行,返回删了几条
// 每天记一条操作记录(文件不存在时会自动写表头)
Csv.AppendRow("records/操作记录.csv",
              new[] { "时间", "操作员", "动作" },
              new[] { Sys.Now.ToString("yyyy-MM-dd HH:mm"), Sys.User, "启动 1#泵" });

// 读回来筛一遍
foreach (var r in Csv.ReadTable("records/操作记录.csv"))
    if (r["操作员"] == "张三") Log.Info(r["时间"] + " " + r["动作"]);

// 清掉张三的历史记录
int n = Csv.DeleteRows("records/操作记录.csv", "操作员", "张三");
Log.Info($"清了 {n} 条");
⚠ 单元格读回来全是字符串,要当数字算得自己 double.Parse 或 Str.ToDouble。 ⚠ WriteTable 是覆盖;要保留原有数据,先 ReadTable 读出来、拼上新行再写回。 ⚠ DeleteRows 是区分大小写的精确比对 —— 要模糊匹配就自己读出来筛,再用 WriteTable 写回。

Alarm —— 报警
什么时候用:脚本自己判断出了异常,要报一条警(不是靠报警引擎的限值判定)。

函数    干什么
Alarm.Trigger("内容", level, group)    触发一条报警。level:0信息/1警告/2报警/3故障
Alarm.Clear(key)    清除一条报警(条件恢复后调)
Alarm.IsActive(key) → bool    该报警是否还在活动(避免重复触发、做联锁)
Alarm.Ack(key)    确认(应答)一条报警
⚠ 别在高频循环里反复 Trigger 同一条报警 —— 会刷爆报警列表。 判断成立才报,恢复用 Clear 消掉。

if (Tag.GetFloat("油温") > 90 && !Alarm.IsActive("OIL_HOT"))
    Alarm.Trigger("油温过高", level: 2, group: "液压站");

if (Tag.GetFloat("油温") < 85 && Alarm.IsActive("OIL_HOT"))
    Alarm.Clear("OIL_HOT");
History —— 历史数据
什么时候用:脚本里要查"过去一段时间"的统计数据(出班报、算趋势)。

函数    干什么
History.Avg(tag, from, to) → double    时间段平均值
History.Max(tag, from, to) → double    时间段最大值(峰值)
History.Min(tag, from, to) → double    时间段最小值(谷值)
History.Log(tag, value)    主动记一条历史值(存脚本算出来的统计量)
// 交班时算这一班的平均温度
double avg = History.Avg("Oven.Temp", Sys.Now.AddHours(-8), Sys.Now);
Fs.AppendCsvRow("logs/班报.csv", Sys.Now, avg);
⚠ 一般点的历史由记录规则自动存(见第 14 章), History.Log 只用于"脚本算出来的中间量也想留档"。

Report —— 报表 / 实时表
什么时候用:脚本要自己往报表元件(或实时表)里填数据 —— 定时采一行、按行内按钮操作。

⚠ 这里的「报表名」是报表元件的名字(RealtimeTable / Report 元件的 Name),不是画面名。 数据由脚本自管、后端不落盘;改完必须调 Report.Refresh(名) 才推到界面。

函数    干什么
Report.SetColumns(名, "时间", "温度")    设列标题(个数=列数),已有行会被规整到这个列数
Report.AddRow(名, "值1", "值2") → int    末尾加一行,返回新行号(0 基)
Report.InsertRow(名, index, 值…)    在第 index 行插入(想让最新数据置顶就传 0)
Report.DeleteRow(名, index)    删第 index 行(⚠ 越界会抛,先看 RowCount)
Report.SetRows(名, rows)    整表替换(比循环 AddRow 快得多)
Report.SetCell(名, row, col, "值") / GetCell(名, row, col)    改 / 读某一格
Report.GetRows(名) → 只读行集    整表行(只读快照,改它不影响真表)
Report.ClearRows(名) / Report.Clear(名)    清数据行(留列头) / 连列头一起清
Report.GetColumns(名) → string[]    取列标题
Report.RowCount(名) / ColumnCount(名) / CellCount(名) → int    行数 / 列数 / 格数
Report.Reports() → string[]    现有报表 id 列表
Report.LastClickRow(名) / LastClickCol(名) → int    最近被点击的行 / 列(没点过返回 -1)
Report.RecordClick(名, row, col)    登记一次行内按钮点击(一般由前端回调,脚本很少主动调)
Report.Refresh(名)    把改动推到界面(批量改完调一次即可,别每改一格调一次)
// 周期脚本:采一行进报表,只留最近 200 行
Report.AddRow("机组表", Sys.Now.ToString("HH:mm"), Tag.GetString("1#机组.功率"));
if (Report.RowCount("机组表") > 200) Report.DeleteRow("机组表", 0);
Report.Refresh("机组表");

// 实时表里行内「启动」按钮被点之后,拿它操作的是哪一行
int i = Report.LastClickRow("机组表");
if (i >= 0 && Report.LastClickCol("机组表") == 2) Log.Info($"启动第 {i} 行");
⚠ 单元格全是字符串 —— 数值要先 ToString(),读回来要算得自己 double.Parse。

Log —— 日志
什么时候用:留下"程序走到哪了、干了什么"的脚印,方便事后排查。

函数    干什么
Log.Info("消息")    普通信息
Log.Warn("消息")    警告(还没坏,但值得注意)
Log.Error("消息")    错误(出问题了)
日志会进输出面板和本机日志文件。

⚠ 别在高频循环里狂打日志 —— 刷屏而且占磁盘。 Error 的消息里尽量写清"哪个点 / 哪一步 / 什么值",这是排障时最先看的一级。

Sys —— 系统信息
函数    干什么
Sys.Now → DateTime    当前本地时间(打时间戳、判班次、算时间差)
Sys.UtcNow → DateTime    当前 UTC 时间
Sys.User → string    当前登录操作员的用户名(日志留痕、按人判权限)
⚠ 点的 Tag.TimeOf 是 UTC,所以判断"数据多久没更新"要用 Sys.UtcNow - Tag.TimeOf("点"), 别拿 Sys.Now 去减(会差一个时区)。

Timer —— 命名定时器
什么时候用:脚本里要"每隔 N 毫秒做一次"。

函数    干什么
Timer.Every("名字", ms, () => { … })    每 ms 毫秒跑一次
Timer.Stop("名字")    停掉这个定时器
关键点:名字就是身份 —— 同名再注册会先停掉旧的,所以脚本反复加载也不会越堆越多。

Timer.Every("心跳", 5000, () =>
{
    Log.Info($"心跳 {Sys.Now:HH:mm:ss}");
});
⚠ ms 别设太小(几毫秒),会拖垮系统。停用它要 Timer.Stop("心跳"),名字要一模一样。

Comm —— 通信控制
什么时候用:查设备通不通、掉线后让脚本自动重连。

函数    干什么
Comm.IsDeviceOnline("设备名") → bool    设备是否在线
Comm.Reconnect("通道名")    重连一个通道
if (!Comm.IsDeviceOnline("1号PLC"))
{
    Log.Warn("1号PLC 掉线,尝试重连");
    Comm.Reconnect("车间Modbus");
}
⚠ Reconnect 只是"断开",真正的重连靠退避策略在下一轮发生(见第 07 章)。

Ui —— 界面控制 ⚠ 需运行态就绪
什么时候用:脚本里切画面、弹提示、改元素显示。

函数    干什么
Ui.OpenScreen("画面名")    打开/切换画面
Ui.CloseScreen("画面名")    关闭画面(关弹出式子画面)
Ui.ShowMessage("提示")    弹提示框
Ui.Confirm("确定吗?") → bool    弹确认框,返回是否点了确定
Ui.SetElementProp(画面, 元素, 属性, 值)    改某画面某元素的属性
还有更省事的简写语法:#窗口.组件.属性 = 值,效果和 Ui.SetElementProp 一样。

⚠ 这一组依赖前端运行态就绪,组态阶段调用无效。

Db —— 关系数据库 ⚠ 需子系统就绪
什么时候用:脚本要读写外部数据库(上报数据、查 MES 工单)。

函数    干什么
Db.Exec(conn, sql, args) → int    执行增删改,返回影响行数
Db.Scalar(conn, sql, args) → object?    查单个值(总行数、某字段)
Db.Query(conn, sql, args) → 行集    查多行
⚠⚠ 必须用参数占位 @0 @1… 把值放进 args,绝不要把值拼进 SQL 字符串。 拼接会造成 SQL 注入。这是硬要求。

// ✅ 正确
Db.Exec("mes", "UPDATE orders SET status=@0 WHERE id=@1", "done", 1001);

// ❌ 错误 —— 有注入风险
Db.Exec("mes", $"UPDATE orders SET status='done' WHERE id={id}");
写脚本的三条经验
读值前先 Tag.IsGood —— 坏质量会静默返回 0,不查就会把"没读到"当成"值就是 0"。
下发前先 Math2.Clamp —— 算出来的值可能越界,夹到合法范围再写。
脚本要快 —— 所有脚本串行执行,一个慢脚本会拖住全部(见第 12 章)。
小结
15 个库、153 个函数,全在这里
用得最多的是 Tag(读写点)和 Math2(算数)
拆状态字用 Bit,解析回文用 Str,写日志用 Fs
所有库越界/失败都给兜底值,不抛异常 —— 所以"没读到"和"值是 0"要靠 Tag.IsGood / Fs.Exists 区分
Ui / Db 需要对应能力就绪,没接上会抛 ScriptApiUnavailableException
下一步:第 14 章 历史库与报警引擎

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐