HMI 中编写 SCL
Darra HMI 支持在脚本运行时中直接编写 SCL 代码,用于处理控件事件、访问 PLC 变量和执行工艺逻辑,无需编译下载即可运行。
HMI 脚本运行时架构
HMI 脚本引擎在浏览器端(WebView2)以沙箱模式运行,通过 DarraLink 协议与 PLC 运行时通信。
架构分层
┌─────────────────────────────────────┐
│ HMI 页面 (HTML + CSS + JS) │
│ ┌───────────────────────────────┐ │
│ │ HMI SCL 脚本引擎 │ │
│ │ - 事件驱动调度器 │ │
│ │ - 变量缓存层 │ │
│ │ - 内置函数库 │ │
│ └───────────┬───────────────────┘ │
└──────────────┼──────────────────────┘
│ DarraLink 协议 (18821)
┌──────────────┼──────────────────────┐
│ PLC 运行时 │ │
│ ┌───────────┴───────────────────┐ │
│ │ HMI 网关 (HmiGatewayService) │ │
│ │ - 变量订阅管理器 │ │
│ │ - 脚本请求代理 │ │
│ └───────────┬───────────────────┘ │
│ ┌───────────┴───────────────────┐ │
│ │ PLC 变量表 / DB 块 │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
执行模式
| 模式 | 说明 | 实时性 |
|---|---|---|
| 事件驱动 | 用户操作触发,立即执行 | ~10ms (受 UI 线程影响) |
| 定时轮询 | OnTimer 周期执行 | 可配 50ms ~ 10s |
| 数据驱动 | PLC 变量变化触发回调 | ~50ms (含网络延迟) |
与 PLC SCL 的区别
| 特性 | PLC SCL | HMI SCL |
|---|---|---|
| 执行位置 | PLC 运行时 | HMI 浏览器端 |
| 触发方式 | 扫描周期 | 事件驱动 |
| 实时性 | 微秒级 | 毫秒级 (受网络影响) |
| 编译 | 需编译下载 | 无需编译,即时生效 |
| 访问 PLC 变量 | 直接地址 | 通过 HMI 变量绑定 |
| 调试 | 断点 / 单步 | 日志输出 |
| 容量限制 | 程序大小 | 单个脚本 ≤ 100KB |
事件绑定
OnClick — 点击事件
按钮点击时执行的 SCL 脚本:
// 按钮 OnClick 事件
VAR
bToggle : BOOL;
END_VAR
bToggle := NOT bToggle;
HMI_SetVariable("Motor_Enable", bToggle);
HMI_SetVariable("Status_Text", IF bToggle THEN '电机已启动' ELSE '电机已停止' END_IF);
OnValueChanged — 值变化事件
输入框、滑块等控件值变化时触发:
// 数值输入 OnValueChanged 事件
// 限幅逻辑:防止操作员输入超出安全范围的值
VAR
rInput : REAL;
END_VAR
rInput := HMI_GetVariable("Speed_Setpoint");
IF rInput > 3000.0 THEN
HMI_SetVariable("Speed_Setpoint", 3000.0);
HMI_ShowToast("速度上限 3000 RPM", TOAST_WARNING);
ELSIF rInput < 0.0 THEN
HMI_SetVariable("Speed_Setpoint", 0.0);
HMI_ShowToast("速度不能为负", TOAST_ERROR);
END_IF;
OnPageLoad — 页面加载事件
页面加载时执行一次,适用于数据初始化:
// 页面加载时初始化
HMI_SetVariable("Title_Text", '温度监控面板');
HMI_SetVariable("RefreshInterval", 1000);
// 读取 PLC 数据初始化界面
rCurrentTemp := HMI_ReadPlcVariable("DB_ProcessParam.rActTemp");
HMI_SetVariable("Temp_Display", REAL_TO_STRING(rCurrentTemp));
OnTimer — 定时器事件
周期性执行,适用于数据刷新和趋势更新:
// 100ms 定时器:周期性刷新数据
VAR
rTemp : REAL;
rPress : REAL;
rSpeed : REAL;
END_VAR
// 批量读取 PLC 变量
rTemp := HMI_ReadPlcVariable("DB_ProcessParam.rActTemp");
rPress := HMI_ReadPlcVariable("DB_ProcessParam.rActPressure");
rSpeed := HMI_ReadPlcVariable("DB_Machine.stData.stAxes[0].rMaxSpeed");
// 更新显示
HMI_SetVariable("Temp_Value", REAL_TO_STRING(rTemp));
HMI_SetVariable("Press_Value", REAL_TO_STRING(rPress));
HMI_SetVariable("Speed_Value", REAL_TO_STRING(rSpeed));
// 报警检查
IF rTemp > 150.0 THEN
HMI_TriggerAlarm("超温报警", ALARM_CRITICAL);
END_IF;
访问 PLC 变量
HMI 内置函数
| 函数 | 说明 | 示例 |
|---|---|---|
HMI_ReadPlcVariable(path) | 读取 PLC DB 变量 | HMI_ReadPlcVariable("DB1.rSetTemp") |
HMI_WritePlcVariable(path, value) | 写入 PLC DB 变量 | HMI_WritePlcVariable("DB1.rSetTemp", 150.0) |
HMI_GetVariable(name) | 读取 HMI 本地变量 | HMI_GetVariable("Speed_Setpoint") |
HMI_SetVariable(name, value) | 设置 HMI 本地变量 | HMI_SetVariable("Status_Text", '运行中') |
HMI_ShowToast(msg, type) | 显示提示消息 | HMI_ShowToast("保存成功", TOAST_SUCCESS) |
HMI_TriggerAlarm(msg, level) | 触发报警 | HMI_TriggerAlarm("超温", ALARM_CRITICAL) |
HMI_NavigateTo(page) | 页面跳转 | HMI_NavigateTo("alarm-page") |
HMI_Log(msg) | 写入 HMI 日志 | HMI_Log("用户点击启动按钮") |
数据类型转换
// 从 PLC 读取的数据类型转换
VAR
iRaw : INT;
rValue : REAL;
sText : STRING[32];
END_VAR
// INT → REAL 转换
iRaw := HMI_ReadPlcVariable("DB_Analog.rRawValue");
rValue := INT_TO_REAL(iRaw) * 0.01;
// REAL → 字符串格式化
sText := CONCAT('当前值: ', REAL_TO_STRING(rValue));
HMI_SetVariable("Display_Text", sText);
// 布尔值判断
IF HMI_ReadPlcVariable("DB_Status.bMotorRunning") = TRUE THEN
HMI_SetVariable("Motor_Indicator", '运行中');
ELSE
HMI_SetVariable("Motor_Indicator", '已停止');
END_IF;
HMI 系统函数
HMI SCL 引擎提供一组系统函数,用于操作 HMI 运行时环境和 UI 控件。
页面管理
| 函数 | 说明 | 示例 |
|---|---|---|
HMI_NavigateTo(pageName) | 跳转到指定页面 | HMI_NavigateTo("main-page") |
HMI_GoBack() | 返回上一页 | HMI_GoBack() |
HMI_GetCurrentPage() | 获取当前页面名 | sPage := HMI_GetCurrentPage() |
HMI_RefreshPage() | 刷新当前页面 | HMI_RefreshPage() |
对话框与提示
| 函数 | 说明 | 示例 |
|---|---|---|
HMI_ShowToast(msg, type) | 底部提示消息 | HMI_ShowToast("保存成功", TOAST_SUCCESS) |
HMI_ShowConfirm(title, msg) | 确认对话框 | IF HMI_ShowConfirm("确认", "要启动吗?") THEN |
HMI_ShowInput(title, prompt) | 输入对话框 | sInput := HMI_ShowInput("密码", "请输入操作密码") |
报警管理
| 函数 | 说明 | 示例 |
|---|---|---|
HMI_TriggerAlarm(msg, level) | 触发报警事件 | HMI_TriggerAlarm("超温", ALARM_CRITICAL) |
HMI_AcknowledgeAlarm(alarmId) | 确认报警 | HMI_AcknowledgeAlarm(1001) |
HMI_GetAlarmCount(level) | 获取报警计数 | iCount := HMI_GetAlarmCount(ALARM_CRITICAL) |
本地存储
| 函数 | 说明 | 示例 |
|---|---|---|
HMI_SaveSetting(key, value) | 保存本地设置 | HMI_SaveSetting("language", "zh") |
HMI_LoadSetting(key) | 读取本地设置 | sLang := HMI_LoadSetting("language") |
HMI_ClearSettings() | 清除所有本地设置 | HMI_ClearSettings() |
时间与定时
| 函数 | 说明 | 示例 |
|---|---|---|
HMI_GetSystemTime() | 获取系统时间 | dt := HMI_GetSystemTime() |
HMI_FormatDateTime(dt, fmt) | 格式化时间 | sTime := HMI_FormatDateTime(dt, "HH:mm:ss") |
HMI_StartTimer(id, intervalMs) | 启动定时器 | HMI_StartTimer("refresh", 1000) |
HMI_StopTimer(id) | 停止定时器 | HMI_StopTimer("refresh") |
完整示例
按钮控制 — 电机启停
// OnClick: 启动按钮
// 执行经验证启动操作
VAR
bMotorRunning : BOOL;
END_VAR
// 读取当前状态
bMotorRunning := HMI_ReadPlcVariable("DB_Status.bMotorRunning");
IF bMotorRunning THEN
HMI_ShowToast("电机已在运行中", TOAST_WARNING);
RETURN;
END_IF;
// 写入启动命令
HMI_WritePlcVariable("DB_Control.bStartMotor", TRUE);
HMI_Log("电机启动命令已发送");
// 延时等待启动确认
HMI_StartTimer("start_confirm", 500);
数据校验 — 参数输入验证
// OnValueChanged: 温度输入框
// 实时验证输入值的合法性
VAR
rInput : REAL;
END_VAR
rInput := HMI_GetVariable("TempInput");
// 范围检查
IF rInput < -50.0 THEN
HMI_SetVariable("TempInput", -50.0);
HMI_SetVariable("TempWarning", '下限 -50°C');
HMI_ShowToast("温度不能低于 -50°C", TOAST_ERROR);
ELSIF rInput > 500.0 THEN
HMI_SetVariable("TempInput", 500.0);
HMI_SetVariable("TempWarning", '上限 500°C');
HMI_ShowToast("温度不能超过 500°C", TOAST_ERROR);
ELSE
HMI_SetVariable("TempWarning", '');
END_IF;
页面切换 — 根据权限显示不同页面
// OnPageLoad: 主页面
// 根据用户权限决定导航入口
VAR
iUserLevel : INT;
END_VAR
// 读取用户权限级别
iUserLevel := HMI_ReadPlcVariable("DB_System.iUserLevel");
// 根据权限启用/禁用功能按钮
IF iUserLevel >= 3 THEN
// 工程师权限 — 显示全部
HMI_SetVariable("BtnParamVisible", TRUE);
HMI_SetVariable("BtnRecipeVisible", TRUE);
HMI_SetVariable("BtnDebugVisible", TRUE);
ELSIF iUserLevel >= 2 THEN
// 操作员权限 — 仅显示操作面板
HMI_SetVariable("BtnParamVisible", TRUE);
HMI_SetVariable("BtnRecipeVisible", TRUE);
HMI_SetVariable("BtnDebugVisible", FALSE);
ELSE
// 观察者权限 — 仅监视
HMI_SetVariable("BtnParamVisible", FALSE);
HMI_SetVariable("BtnRecipeVisible", FALSE);
HMI_SetVariable("BtnDebugVisible", FALSE);
END_IF;
HMI_Log(CONCAT('用户登录,权限级别: ', INT_TO_STRING(iUserLevel)));
参数设置面板
// 页面: 参数设置 (ParameterPage)
// OnPageLoad 事件
VAR
iRecipeIndex : INT;
END_VAR
// 加载配方列表
HMI_SetVariable("RecipeList", '标准配方|高速配方|节能配方');
HMI_SetVariable("SelectedRecipe", 0);
// 读取当前 PLC 参数
rTemp := HMI_ReadPlcVariable("DB_ProcessParam.rSetTemp");
HMI_SetVariable("TempInput", rTemp);
// OnClick: 保存按钮
// 验证并写入 PLC
VAR
rSetTemp : REAL;
rSetPress : REAL;
END_VAR
rSetTemp := HMI_GetVariable("TempInput");
rSetPress := HMI_GetVariable("PressInput");
// 参数验证
IF rSetTemp < 0.0 OR rSetTemp > 300.0 THEN
HMI_ShowToast("温度范围 0~300°C", TOAST_ERROR);
RETURN;
END_IF;
// 写入 PLC
HMI_WritePlcVariable("DB_ProcessParam.rSetTemp", rSetTemp);
HMI_WritePlcVariable("DB_ProcessParam.rSetPressure", rSetPress);
HMI_WritePlcVariable("DB_ProcessParam.sRecipeName", CONCAT('配方_', INT_TO_STRING(rSetTemp)));
HMI_ShowToast("参数保存成功", TOAST_SUCCESS);
HMI_Log(CONCAT('参数已保存: 温度=', REAL_TO_STRING(rSetTemp)));
报警监控面板
// OnTimer (500ms 定时器)
VAR
bAlarmActive : BOOL;
sAlarmText : STRING[64];
END_VAR
// 读取 PLC 报警状态
bAlarmActive := HMI_ReadPlcVariable("DB_Alarm.bActiveAlarm");
IF bAlarmActive THEN
// 读取报警详细信息
sAlarmText := HMI_ReadPlcVariable("DB_Alarm.sAlarmMessage");
// 设置 HMI 报警指示
HMI_SetVariable("AlarmIndicator", TRUE);
HMI_SetVariable("AlarmText", sAlarmText);
HMI_SetVariable("AlarmColor", '#FF0000'); // 红色
// 触发声音提示
HMI_TriggerAlarm(sAlarmText, ALARM_CRITICAL);
ELSE
HMI_SetVariable("AlarmIndicator", FALSE);
HMI_SetVariable("AlarmText", '系统正常');
HMI_SetVariable("AlarmColor", '#00FF00'); // 绿色
END_IF;
SCL 脚本调试
日志输出
// 使用 HMI_Log 输出调试信息
HMI_Log(CONCAT('当前状态: ', INT_TO_STRING(iState)));
HMI_Log(CONCAT('温度值: ', REAL_TO_STRING(rTemp)));
// 日志可在 HMI 诊断面板中查看
常见错误
| 错误 | 原因 | 解决 |
|---|---|---|
| 变量值为空 | PLC 变量路径错误 | 确认 DB 块名称和变量名 |
| 类型不匹配 | 读写类型与 PLC 定义不一致 | 使用显式类型转换 |
| 脚本无响应 | 死循环或超时 | 避免 WHILE 无限循环 |
| 写入不生效 | 变量在 PLC 中被强制 | 检查 IDE 强制状态 |
| 事件不触发 | 事件绑定未正确设置 | 检查控件事件配置 |
最佳实践
- 避免长时间循环: HMI 脚本应在 100ms 内完成,否则 UI 卡顿
- 批量读写: 频繁读取多个变量时,使用单次批量读取
- 错误处理: 读取 PLC 变量后检查是否为 NULL
- 事件分离: OnTimer 仅做数据刷新,用户操作逻辑放 OnClick
- 日志记录: 关键操作(参数修改、启动/停止)记录日志