跳到主要内容

第一个 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英文数字, 不含空格
标题电机启停面板显示在标签栏
分辨率1280x8001920x1080 工业屏
背景#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 DataContextHmiPage.DataContext 写法错误

下一步