跳到主要内容

第一个 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 页面​

  1. 解决方案资源管理器 → 右键 HMI → 新建页面
  2. 填写:
字段值说明
名称MotorPanel英文数字, 不含空格
标题电机启停面板显示在标签栏
分辨率1280x800或 1920x1080 工业屏
背景#F5F5F5 (浅灰)工业标准背景
  1. 点 创建

IDE 会打开 XAML + 设计器双栏布局。左侧 工具箱 列出可拖入的 80+ 个控件分类 (按钮 / 指示 / 输入 / 趋势 / 报警 / 容器 / 第三方)。

step-1: 新建 HMI 页面 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>

step-2: HMI 页面布局 TODO

为什么不直接用 TextBox 而用 NumericDisplay

NumericDisplay 自带刻度单位、数据格式化、无效值变灰、超限闪烁, 工业场合比裸 TextBox 稳定太多。Darra HMI 工具箱中这类 工业化控件 超 80 种。

步骤 3 - 配置数据源 (绑定 PLC 变量)​

HMI 与 PLC 之间用 变量订阅 通讯, 由 ViewModel 处理。

3.1 生成 ViewModel 模板​

  1. 右键 MotorPanel → 生成 ViewModel
  2. IDE 创建 MotorPanelVM.cs 并自动打开
  3. 基础模板已就绪:
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; }
刷新过快 = CPU 爆

HMI 一页 20 个控件都 10 ms 订阅 = 每秒 2000 次跨进程调用, 会拖慢 Service。 常规逻辑量 100 ms 足够, 趋势 / 报警才用高频。

步骤 4 - 属性绑定细节​

4.1 指示灯绑到 Motor​

<IndicatorLamp IsOn="{Binding Motor}" />

MVVM 底层:

  1. ViewModel 的 Motor 属性打了 [BindPlc] 特性
  2. PLC 变量 Motor 变化 → 触发属性 setter → INotifyPropertyChanged
  3. WPF 绑定系统感知变化 → 更新 IsOn
  4. 指示灯颜色切换

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 - 预览运行​

  1. 保证 PLC 程序已编译且处于仿真运行 (上一节 F5 开着)
  2. 回到 HMI 页面, 点工具栏 预览 (F12)
  3. IDE 弹出独立预览窗口

测试:

  • 点击 启动 → Start 变 TRUE → PLC 逻辑把 Motor 变 TRUE → 指示灯变绿 → 趋势上升
  • 点击 停止 → Stop 变 TRUE → Motor 变 FALSE → 指示灯灰 → 趋势下降
  • 数值显示 RunSec 每秒 +1

step-5: HMI 预览运行 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​

  1. 菜单 HMI → 发布到 Panel
  2. 选择目标:
    • 本机 Panel — 开发机自用
    • 远程 Panel — 工控机 (需连接 Runtime)
  3. 勾选页面 (支持多选)
  4. 点 发布

发布流程:

[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 在工控机查看​

现场工控机:

  1. 双击桌面 Darra PLC Panel 图标
  2. 登录操作员账号 (operator)
  3. 左侧菜单 → HMI 页面 → 电机启停面板
  4. 全屏显示启停界面, 按钮能响应, 指示灯能刷新, 趋势曲线滚动

step-6: Panel 显示 HMI 页面 TODO

Web Panel

如果工控机没有显示器 (headless), 可以用浏览器访问: https://<工控机 IP>:18823/hmi/motor-panel Web Panel 自动把 XAML 渲染成 HTML5 Canvas, 触摸 / 按钮 / 刷新全支持, 适合远程值班。

常见问题​

指示灯不亮​

  1. 检查 PLC Motor 变量是否确实为 TRUE (IDE 变量监视里看)
  2. ViewModel 里 Motor 属性是否标了 [BindPlc]
  3. XAML 里 IsOn="{Binding Motor}" 路径名大小写匹配
  4. 检查 _last_layout.xml 是否缓存旧绑定, 删后重启 Panel

按钮点击无反应​

  1. StartCommand 是否在构造函数里 new RelayCommand(...) 了
  2. CanExecute 是否一直返回 false 导致按钮禁用
  3. IDE 输出窗口有没有 PlcClient.Write 的错误日志

趋势曲线不刷新​

  1. 变量名路径是否正确 (例如 GlobalVars.Motor 不是 Motor)
  2. TimeRange 时间窗是否太短
  3. 高频订阅下 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 写法错误

下一步​