第一个 HMI 工程
本节承接 第一个 PLC 程序, 我们已经有了电机启停逻辑和
Start / Stop / Motor / Running / RunSec 变量。现在给它配一个 HMI 界面:
一个启动按钮、一个停止按钮、一个运行指示灯、一个运行秒数显示, 外加一条
趋势曲线, 最后发布到 Panel 让操作员使用。
目标
- 理解 Darra HMI = WPF + MVVM 绑定, XAML 为主
- 会拖入按钮 / 指示灯 / 数值显示 / 趋势图控件
- 会配置数据源并绑定 PLC 变量
- 会做点击事件绑定 (按钮 → 写变量)
- 会在 IDE 里预览运行
- 会发布到 Panel 并在真机上操作
前置条件
- 已完成 第一个 PLC 程序, 全局变量表有
Start / Stop / Motor / Running / RunSec - 工程类型在向导里勾选了 HMI
- 如果工程没 HMI 子树, 右键解决方案 → 添加子系统 → HMI 补上
HMI 架构简述
- 页面描述用 XAML (同 WPF)
- 数据绑定用 MVVM Binding, 变量变化自动刷新控件
- 每页一个 ViewModel, 订阅自己关心的变量集合
- 运行在 IDE 预览 / Panel 全屏 / Web Panel 浏览器三端
步骤 1 - 新建 HMI 页面
- 解决方案资源管理器 → 右键 HMI → 新建页面
- 填写:
| 字段 | 值 | 说明 |
|---|---|---|
| 名称 | MotorPanel | 英文数字, 不含空格 |
| 标题 | 电机启停面板 | 显示在标签栏 |
| 分辨率 | 1280x800 | 或 1920x1080 工业屏 |
| 背景 | #F5F5F5 (浅灰) | 工业标准背景 |
- 点 创建
IDE 会打开 XAML + 设计器双栏布局。左侧 工具箱 列出可拖入的 80+ 个控件分类 (按钮 / 指示 / 输入 / 趋势 / 报警 / 容器 / 第三方)。
TODO
步骤 2 - 拖入控件
2.1 从工具箱拖入
从工具箱依次拖到画布, 摆成两行:
| 控件 | 类别 | 作用 | 放置位置 |
|---|---|---|---|
StartButton | 按钮 → 工业按钮 | 启动按钮 (绿圆) | (100, 100) |
StopButton | 按钮 → 工业按钮 | 停止按钮 (红圆) | (300, 100) |
MotorLamp | 指示 → 指示灯 | 电机运行灯 | (500, 100) |
RunSecDisplay | 数值 → 数字显示 | 运行秒数 | (100, 300) |
MotorTrend | 趋势 → 实时趋势图 | Motor 波形 | (100, 450) |
设计器也可以直接从 XAML 输入, 下一步直接贴代码更快。
2.2 XAML 完整代码
双击标签栏 XAML 切到代码视图, 替换为:
<HmiPage x:Class="DemoLine01.HMI.MotorPanel"
xmlns="http://schemas.darra.tech/hmi/2025"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="电机启停面板"
Width="1280" Height="800"
Background="#F5F5F5">
<HmiPage.DataContext>
<Binding Source="MotorPanelVM" />
</HmiPage.DataContext>
<Grid>
<!-- 标题 -->
<TextBlock Text="电机启停面板"
FontSize="32" FontWeight="Bold"
Margin="40,20" Foreground="#333" />
<!-- 启动按钮 -->
<IndustrialButton x:Name="StartButton"
Style="{StaticResource GreenRound}"
Content="启 动"
Width="120" Height="120"
Margin="100,100,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Command="{Binding StartCommand}" />
<!-- 停止按钮 -->
<IndustrialButton x:Name="StopButton"
Style="{StaticResource RedRound}"
Content="停 止"
Width="120" Height="120"
Margin="260,100,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Command="{Binding StopCommand}" />
<!-- 运行指示灯 -->
<IndicatorLamp x:Name="MotorLamp"
Width="80" Height="80"
Margin="440,120,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
IsOn="{Binding Motor}"
OnColor="#2ECC71"
OffColor="#BDC3C7"
Label="电机运行" />
<!-- 运行秒数 -->
<NumericDisplay x:Name="RunSecDisplay"
Value="{Binding RunSec}"
Unit="s"
Caption="累计运行"
Width="240" Height="80"
Margin="100,260,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Format="F0" />
<!-- 实时趋势图 -->
<RealtimeTrend x:Name="MotorTrend"
Width="1080" Height="300"
Margin="100,400,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
TimeRange="60"
YMin="-0.2" YMax="1.2">
<RealtimeTrend.Series>
<TrendSeries Name="Motor"
Variable="GlobalVars.Motor"
Color="#2ECC71"
LineWidth="2"
Kind="Boolean" />
<TrendSeries Name="Running"
Variable="GlobalVars.Running"
Color="#3498DB"
LineWidth="1.5"
Kind="Boolean" />
</RealtimeTrend.Series>
</RealtimeTrend>
</Grid>
</HmiPage>
TODO
NumericDisplay 自带刻度单位、数据格式化、无效值变灰、超限闪烁, 工业场合比裸 TextBox
稳定太多。Darra HMI 工具箱中这类 工业化控件 超 80 种。
步骤 3 - 配置数据源 (绑定 PLC 变量)
HMI 与 PLC 之间用 变量订阅 通讯, 由 ViewModel 处理。
3.1 生成 ViewModel 模板
- 右键 MotorPanel → 生成 ViewModel
- IDE 创建
MotorPanelVM.cs并自动打开 - 基础模板已就绪:
using Darra.HMI.Mvvm;
using Darra.PLC.Client;
namespace DemoLine01.HMI
{
public class MotorPanelVM : HmiViewModelBase
{
// 自动绑定的变量
[BindPlc("GlobalVars.Motor")]
public bool Motor { get; set; }
[BindPlc("GlobalVars.Running")]
public bool Running { get; set; }
[BindPlc("GlobalVars.RunSec")]
public int RunSec { get; set; }
// 按钮命令
public RelayCommand StartCommand { get; }
public RelayCommand StopCommand { get; }
public MotorPanelVM()
{
StartCommand = new RelayCommand(
execute: () => PlcClient.Write("GlobalVars.Start", true),
canExecute: () => !Motor
);
StopCommand = new RelayCommand(
execute: () => PlcClient.Write("GlobalVars.Stop", true),
canExecute: () => true // 停止永远可按
);
}
}
}
3.2 变量订阅刷新频率
HMI 默认每 100 ms 轮询一次变量值, 趋势图控件内部用 10 ms 高频订阅 (通过 Service 的推送通道, 不走轮询)。
要修改刷新频率:
[BindPlc("GlobalVars.Motor", RefreshRateMs = 50)]
public bool Motor { get; set; }
HMI 一页 20 个控件都 10 ms 订阅 = 每秒 2000 次跨进程调用, 会拖慢 Service。 常规逻辑量 100 ms 足够, 趋势 / 报警才用高频。
步骤 4 - 属性绑定细节
4.1 指示灯绑到 Motor
<IndicatorLamp IsOn="{Binding Motor}" />
MVVM 底层:
- ViewModel 的
Motor属性打了[BindPlc]特性 - PLC 变量
Motor变化 → 触发属性 setter →INotifyPropertyChanged - WPF 绑定系统感知变化 → 更新
IsOn - 指示灯颜色切换
4.2 按钮 Click 写变量
传统 WPF 用 Click 事件, 但那是 code-behind, 不推荐。用 Command 绑定更干净:
<IndustrialButton Command="{Binding StartCommand}" />
StartCommand 是 ViewModel 里的 RelayCommand, 按下时 execute 调用
PlcClient.Write("GlobalVars.Start", true) 直接写 PLC 变量。
4.3 数值显示带格式
<NumericDisplay Value="{Binding RunSec}"
Format="F0"
Unit="s" />
F0= 零位小数F2= 两位小数P1= 百分比 (0.5 → 50.0%)E3= 科学计数 (1234 → 1.234E+3)
步骤 5 - 预览运行
- 保证 PLC 程序已编译且处于仿真运行 (上一节 F5 开着)
- 回到 HMI 页面, 点工具栏 预览 (F12)
- IDE 弹出独立预览窗口
测试:
- 点击 启动 →
Start变 TRUE → PLC 逻辑把Motor变 TRUE → 指示灯变绿 → 趋势上升 - 点击 停止 →
Stop变 TRUE →Motor变 FALSE → 指示灯灰 → 趋势下降 - 数值显示
RunSec每秒 +1
TODO
默认 Start 点一下会一直保持 TRUE。在 PLC 里自己检测上升沿清零, 或在 HMI 里改:
StartCommand = new RelayCommand(() => {
PlcClient.Write("GlobalVars.Start", true);
Task.Delay(100).ContinueWith(_ =>
PlcClient.Write("GlobalVars.Start", false));
});
就能实现脉冲按钮效果。
步骤 6 - 发布到 Panel
在 IDE 里预览只是验证, 现场要把 HMI 发到 Panel (工控机本地) 或 Web Panel (浏览器):
6.1 发布到本地 Panel
- 菜单 HMI → 发布到 Panel
- 选择目标:
本机 Panel— 开发机自用远程 Panel— 工控机 (需连接 Runtime)
- 勾选页面 (支持多选)
- 点 发布
发布流程:
[HMI] 编译 MotorPanel.xaml → IR
[HMI] 打包 MotorPanel + MotorPanelVM → MotorPanel.dkhmi (18.4 KB)
[HMI] 通过 DarraLink 18821 上传到 192.168.1.100 ... OK
[HMI] Panel 热加载 ... OK
6.2 在工控机查看
现场工控机:
- 双击桌面 Darra PLC Panel 图标
- 登录操作员账号 (operator)
- 左侧菜单 → HMI 页面 → 电机启停面板
- 全屏显示启停界面, 按钮能响应, 指示灯能刷新, 趋势曲线滚动
TODO
如果工控机没有显示器 (headless), 可以用浏览器访问:
https://<工控机 IP>:18823/hmi/motor-panel
Web Panel 自动把 XAML 渲染成 HTML5 Canvas, 触摸 / 按钮 / 刷新全支持, 适合远程值班。
常见问题
指示灯不亮
- 检查 PLC
Motor变量是否确实为 TRUE (IDE 变量监视里看) - ViewModel 里
Motor属性是否标了[BindPlc] - XAML 里
IsOn="{Binding Motor}"路径名大小写匹配 - 检查
_last_layout.xml是否缓存旧绑定, 删后重启 Panel
按钮点击无反应
StartCommand是否在构造函数里new RelayCommand(...)了CanExecute是否一直返回 false 导致按钮禁用- IDE 输出窗口有没有
PlcClient.Write的错误日志
趋势曲线不刷新
- 变量名路径是否正确 (例如
GlobalVars.Motor不是Motor) TimeRange时间窗是否太短- 高频订阅下 Service 是否吃满 CPU, 看日志
service_*.log
发布失败 "版本不匹配"
Panel 和 Runtime 版本差异导致。检查 Panel 版本 (Panel → 关于) 与 IDE 编译时 工程属性里的目标 Runtime 版本一致。
Panel 白屏
查看 %AppData%/DarraPLC/logs/panel_*.log:
XAML Parse Error→ 检查 XAML 语法, 特别注意命名空间ViewModel Not Found→ 确认.cs被编译进包里Null DataContext→HmiPage.DataContext写法错误