跳到主要内容

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 SCLHMI 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
  • 日志记录: 关键操作(参数修改、启动/停止)记录日志

相关文档