ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

鲲鹏恒控脚本编写教程

鲲鹏恒控脚本编写教程 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 读点定义(单位/量程/可写性)两个必须养成的习惯:// ① 读值前先查质量否则坏值会静默返回 0if (Tag.IsGood(温度) Tag.GetFloat(温度) 80) { … }// ② 动态拼的点名先探存在否则拼错名字也会返回 0string 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) 取余(b0 返回 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, fallback0) → int 转整数,转不动返回 fallback 不抛错Str.ToDouble(s, fallback0) → double 转浮点,同上Str.IsEmpty(s) → bool 是否 null / 空串 / 全空白(校验必填输入第一步)Str.Len(s) → int 字符个数(null 当空串,返回 0)Bit —— 位运算与字节拆装什么时候用:设备把一堆开关状态打包在一个寄存器里(状态字),你要拆出第几位;或者要把几个标志位拼成一个命令字。取位 / 置位(状态字最常用)函数 干什么Bit.Get(value, bit) → bool 看第 bit 位是不是 1(bit0 是最低位)。拆状态字的主力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, width0) → string 转十六进制字符串,width0 左补零(ToHex(255,4)00FF)Bit.FromHex(0xFF, fallback0) → 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 当前围栏根目录(只用于日志里说明文件存哪了)// 每天记一条产量到 CSVFs.AppendCsvRow(logs/产量.csv, Sys.Now, Tag.GetFloat(今日产量));// 遍历日志目录里所有 CSVforeach (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 status0 WHERE id1, done, 1001);// ❌ 错误 —— 有注入风险Db.Exec(mes, $UPDATE orders SET statusdone 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 章 历史库与报警引擎
返回列表