鲲鹏恒控脚本编写教程
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 章 历史库与报警引擎
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐


所有评论(0)