跳到主要内容

HMI 设计器教程 (完整版)

本文档以 菜鸟教程风格 编写, 每个概念包含: 解释 + 参数表 + 代码示例 + 常见错误。适合从零开始学习 Darra HMI 设计, 也可作为日常开发速查手册。


1. HMI 设计入门

1.1 HMI 在工业自动化中的作用

HMI (Human Machine Interface, 人机界面) 是操作员与 PLC 控制系统之间的桥梁。在 DarraRT 中, HMI 负责:

功能说明示例
状态监控实时显示 PLC 变量值温度、压力、转速、液位
操作控制向 PLC 写入数据启动/停止电机、设定目标值
报警管理显示、确认、归档报警超温报警、急停触发
数据趋势历史数据曲线可视化过去 2 小时温度趋势
配方管理切换工艺参数组不同产品规格的参数集
权限控制限定操作员可执行的操作操作员只能启停, 工程师能改参数

HMI 与 PLC 的关系:

操作员 ──操作──→ HMI 界面 ──写入──→ PLC 变量 ──控制──→ 现场设备
↑ │
└──────────── 读取 ──────────────┘

关键设计原则:

  • HMI 只是"显示层", 不执行控制逻辑 (控制逻辑在 PLC 中)
  • HMI 挂掉不影响 PLC 扫描运行
  • 一个工程可同时有 LocalHMI (本地全屏) 和 WebHMI (远程浏览器) 两份独立界面

1.2 Darra HMI 架构 (WebView2 + WinForms)

Darra HMI 使用 纯 Web 技术栈 (HTML + CSS + JavaScript), 通过 WebSocket 与 PLC Service 实时通信。

┌────────────────────────────────────────────────────────────────┐
│ LocalHMI (Darra.PLC.HMI.exe) │
│ WinForms + WebView2 全屏外壳 │
│ F11 全屏 / F5 刷新 / ESC 退出 / 自动重连 │
└──────────────────────────┬─────────────────────────────────────┘
│ 加载

┌────────────────────────────────────────────────────────────────┐
│ HmiWebServer (端口 18823) │
│ /local/{route} LocalHMI 页面 │
│ /web/{route} WebHMI 页面 │
│ /static/* 静态资源 (alpine.js, darra-plc.js 等) │
│ /ws WebSocket (变量订阅/推送/写入) │
│ /api/hmi/* 管理 API (登录/DB描述/列页面) │
└──────────────────────────┬─────────────────────────────────────┘


┌────────────────────────────────────────────────────────────────┐
│ Darra.PLC.Service (永驻进程) │
│ HmiBroadcaster 变量推送节流合批 │
│ HmiVariableBridge PLC ↔ JSON 变量读写桥 │
│ HmiPythonBridge HMI 调 Python 脚本 │
│ HmiAuthProvider 登录/角色/Token │
└──────────────────────────┬─────────────────────────────────────┘
│ (共享内存)

┌────────────────────────────────────────────────────────────────┐
│ PLC 运行时 (内核态, 扫描周期 1-100ms) │
└────────────────────────────────────────────────────────────────┘

LocalHMI vs WebHMI 对比:

属性LocalHMIWebHMI
访问方式本地 WebView2 全屏浏览器 http://ip:18823/web/...
外壳Darra.PLC.HMI.exe无, 浏览器直接访问
典型终端工控机 / 触摸屏 / 工位 Panel移动端 / 办公电脑 / 外部大屏
认证可选 (局域网直接登录)强制 (RequireAuth = true)
写入白名单通常宽松强烈建议启用
页面路由/local/{route}/web/{route}

1.3 三种制作方式

Darra HMI 提供三种制作方式, 可根据场景灵活选择:

方式适用场景难度推荐度
拖拽式 (FreeForm)快速搭建, 非开发者⭐⭐⭐⭐⭐
代码式 (Code)复杂逻辑, 自定义样式⭐⭐⭐⭐⭐⭐⭐
混合模式先拖后补逻辑⭐⭐⭐⭐⭐⭐⭐

经验: 90% 的页面用拖拽搭骨架, 再用代码式补 JS 逻辑。这是最有效率的工作流。

1.4 第一个 HMI 项目

步骤 1: 新建 HMI 项目

  1. 在 IDE 中打开或新建一个 PLC 项目
  2. 左侧项目树 → 右键 "HMI 设计" 节点 → "新建 LocalHMI 项目" (或 WebHMI 项目)
  3. 输入项目名称, 选择设计尺寸 (默认 1920×1080)

步骤 2: 添加第一个页面

  1. 在 HMI 项目节点上右键 → "新建 HMI 页面"
  2. 设置页面名称: 首页, 路由: /, 勾选 "设为首页"
  3. 双击页面节点打开设计器

步骤 3: 拖入控件

  1. 左侧 Block 面板找到 "基础控件 → 按钮"
  2. 拖拽到画布上
  3. 右侧 Trait 面板设置:
    • var: M0.0 (绑定到 PLC 位变量)
    • mode: toggle (切换模式)
    • 控件的文本: 启动/停止

步骤 4: 添加指示灯

  1. 拖入 "基础控件 → LED" 到画布
  2. 设置 var: M0.0 (与按钮绑定同一变量)
  3. 设置 color-on: #00FF00 (绿色)
  4. 设置 color-off: #666666 (灰色)

步骤 5: 添加数值显示

  1. 拖入 "基础控件 → Numeric" 到画布
  2. 设置 var: DB1.Temperature
  3. 设置 format: 0.00
  4. 设置 unit:

步骤 6: 保存并预览

  1. Ctrl+S 保存项目
  2. 菜单 运行 → 启动 PLC Service
  3. 点击设计器顶部工具栏的 "预览" 按钮
  4. 浏览器打开 http://localhost:18823/local/

完整页面代码 (如果你用代码模式):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>我的第一个 HMI 页面</title>
<link rel="stylesheet" href="/static/darra-hmi.css" />
<script src="/static/alpine.min.js" defer></script>
<script src="/static/darra-plc.js"></script>
</head>
<body class="theme-modern">
<h1>1# 反应釜监控</h1>

<!-- 启动/停止按钮, 绑定 M0.0 -->
<darra-button var="M0.0" mode="toggle">启动/停止</darra-button>

<!-- 运行指示灯, 与按钮同一变量 -->
<darra-led var="M0.0" color-on="#00FF00" color-off="#666"></darra-led>

<!-- 温度显示 -->
<div>
温度: <darra-numeric var="DB1.Temperature" format="0.00" unit=""></darra-numeric>
</div>

<!-- 压力显示 -->
<div>
压力: <darra-numeric var="DB1.Pressure" format="0.00" unit="MPa"></darra-numeric>
</div>
</body>
</html>

常见错误: 预览时看不到控件

错误原因解决
画布空白Service 未启动运行 → 启动 PLC Service
控件显示 "未定义"变量名拼写错误检查 var 属性是否与 PLC 变量名一致
按钮点击无反应变量不在写入白名单在项目设置中配置 WriteWhitelist
右下角显示 "断开"端口 18823 被防火墙拦截检查防火墙规则

2. HMI 控件大全

Darra HMI 提供 36 个自定义 HTML 元素 (<darra-*>), 分为 11 大类。每个控件包含: 属性表 + 示例 + 常见错误。

2.1 基础控件

<darra-button> — 按钮

按钮用于触发 PLC 操作, 支持两种模式: toggle (切换) 和 momentary (点动)。

属性表:

属性必填默认值说明
var绑定的 PLC 位变量 (如 M0.0)
modetoggletoggle 翻转 / momentary 按下即松 (200ms 后自动置 0)
label子节点文本按钮文字
color按下时的颜色
disabledfalse是否禁用
sizemediumsmall / medium / large

示例:

<!-- 切换按钮: 点击翻转 M0.0 的状态 -->
<darra-button var="M0.0" mode="toggle">启动/停止</darra-button>

<!-- 点动按钮: 按下时 M0.1 为 true, 松开后 200ms 自动变 false -->
<darra-button var="M0.1" mode="momentary">点动</darra-button>

<!-- 禁用按钮: 当 M10.0 为 true 时禁用 -->
<darra-button var="M0.2" disabled="M10.0">危险操作</darra-button>

<!-- 大按钮, 触摸屏用 -->
<darra-button var="M0.3" mode="toggle" size="large">紧急停止</darra-button>

常见错误:

错误原因解决
按钮点击后变量不变变量不在 WriteWhitelist在项目设置中添加该变量到白名单
点动模式一直为 trueService 版本低升级到 v1.0.5+
按钮文字不显示忘了写子节点文本在标签中写文本: <darra-button>启动</darra-button>

<darra-led> — 指示灯

指示灯用于显示 PLC 位的 ON/OFF 状态, 带颜色和闪烁功能。

属性表:

属性必填默认值说明
var绑定的 PLC 位变量
color-on#00FF00ON 状态的颜色
color-off#FF0000OFF 状态的颜色
size40px圆形直径
blinkfalse为 true 时 ON 状态闪烁
label指示灯下方的标签文字
shapecirclecircle 圆形 / square 方形

示例:

<!-- 基本指示灯: M0.0 为 true 时绿色, false 时红色 -->
<darra-led var="M0.0" color-on="#00FF00" color-off="#FF0000"></darra-led>

<!-- 运行指示灯, 带闪烁 -->
<darra-led var="M10.0" color-on="#00FF00" color-off="#666" blink="true"></darra-led>

<!-- 故障指示灯, 红色闪烁, 大方块 -->
<darra-led var="M10.1" color-on="#FF0000" color-off="#666" size="64px" blink="true" shape="square" label="故障"></darra-led>

<!-- 黄色警告灯 -->
<darra-led var="M10.2" color-on="#FFD700" color-off="#666" label="警告"></darra-led>

常见错误:

错误原因解决
指示灯一直灰色变量未订阅或变量名拼错检查 var 拼写, 看 F12 网络 WS 帧
闪烁不生效blink 属性设为字符串 "false"blink="true" 而非 blink="false"

<darra-numeric> — 数值显示

用于显示 PLC 的数值变量 (INT, DINT, REAL 等), 支持格式化和单位。

属性表:

属性必填默认值说明
var绑定的 PLC 数值变量
format0显示格式 (0, 0.0, 0.00, #,##0.00)
unit""单位后缀
color#333文本颜色
font-size32px字体大小
prefix数值前缀 (如 ~)
decimals小数位数 (优先级低于 format)

示例:

<!-- 简单数值显示 -->
<darra-numeric var="DB1.Temperature" format="0.0" unit=""></darra-numeric>

<!-- 大字号 KPI 显示 -->
<darra-numeric var="DB100.OEE" format="0.0%" unit="" font-size="72px" color="#38bdf8"></darra-numeric>

<!-- 带前缀的数值 -->
<darra-numeric var="DB1.Pressure" format="0.00" unit="MPa" prefix="~"></darra-numeric>

<!-- 转速显示, 千分位 -->
<darra-numeric var="DB3.RPM" format="#,##0" unit="r/min"></darra-numeric>

常见错误:

错误原因解决
显示 0NaN变量未绑定或 PLC 未运行确认 PLC 正在运行, 变量地址正确
格式不生效format 值写错使用 0.0 (小数点后一位) 或 0.00 (两位)
单位显示在数值上方样式问题在 CSS 中加 display: inline-flex

<darra-input> — 输入框 (双向绑定)

输入框支持双向数据绑定: 用户输入的值会自动写入 PLC 变量。

属性表:

属性必填默认值说明
var绑定的 PLC 可写变量
typenumbernumber / text / password
min最小值 (type=number 时)
max最大值
step步进值
placeholder""占位提示文字
readonlyfalse是否只读

示例:

<!-- 数值输入, 设定目标温度 -->
<darra-input var="DB2.Setpoint" min="0" max="300" step="0.1"></darra-input>

<!-- 文本输入, 产品批号 -->
<darra-input var="DB2.BatchNo" type="text" placeholder="输入批号"></darra-input>

<!-- 只读显示 (与 numeric 不同, 它显示原始值) -->
<darra-input var="DB1.Temperature" readonly="true"></darra-input>

常见错误:

错误原因解决
输入后数值不变变量不在白名单检查 WriteWhitelist
输入框显示 NaN变量类型不匹配确保 PLC 变量是数值类型
输入小数自动取整忘了设置 stepstep="0.1"
输入框无焦点光标误用了 PopupContainerEdit确认使用的是 <darra-input> 而非其他控件

<darra-label> — 静态文字

显示静态文本, 不绑定 PLC 变量。

属性表:

属性必填默认值说明
text子节点文本显示的文字
font-size16px字体大小
color继承主题色文字颜色
boldfalse是否加粗
alignleftleft / center / right

示例:

<darra-label>反应釜 #1</darra-label>
<darra-label text="温度监控" font-size="24px" bold="true" color="#1E90FF"></darra-label>

2.2 输入控件

<darra-input> — 输入框 (见 2.1 基础控件)

<darra-slider> — 滑块

滑块用于直观地调整数值, 适合频率、速度等连续调节。

属性表:

属性必填默认值说明
var绑定的 PLC 可写变量
min0最小值
max100最大值
step1步进值
orientationhorizontalhorizontal / vertical
show-valuetrue是否显示当前值
color主题主色滑块颜色

示例:

<!-- 水平滑块, 调节电机频率 -->
<darra-slider var="DB2.Frequency" min="0" max="50" step="0.5"></darra-slider>

<!-- 垂直滑块, 调节阀门开度 -->
<darra-slider var="DB2.ValveOpen" min="0" max="100" step="1" orientation="vertical"></darra-slider>

常见错误:

错误原因解决
拖动滑块时写入太频繁需要防抖在脚本中加 debounce(300ms)
滑块位置不更新变量是只读的确认变量可写, 且在白名单中

<darra-switch> — 开关

开关控件, 适合启停控制。

属性表:

属性必填默认值说明
var绑定的 PLC 位变量
label-onON开启时的标签
label-offOFF关闭时的标签
color-on#22C55E开启颜色
sizemediumsmall / medium / large

示例:

<darra-switch var="M0.0" label-on="运行" label-off="停止"></darra-switch>

<darra-dropdown> — 下拉框

下拉框用于从多个预设值中选择一个写入 PLC。

属性表:

属性必填默认值说明
var绑定的 PLC 变量 (写入选中的值)
options选项列表, 格式: 值1:标签1,值2:标签2
placeholder请选择占位文字

示例:

<!-- 选择运行模式 -->
<darra-dropdown var="DB2.Mode" options="0:手动,1:半自动,2:全自动"></darra-dropdown>

<!-- 选择配方 -->
<darra-dropdown var="DB2.RecipeID" options="1:碳钢3mm,2:不锈钢2mm,3:铝合金4mm"></darra-dropdown>

2.3 容器控件

<darra-panel> — 面板容器

面板用于将多个控件组合在一起, 带标题和边框。

属性表:

属性必填默认值说明
title面板标题
border1px solid #ccc边框样式
collapsiblefalse是否可折叠
collapsedfalse初始折叠状态
widthauto面板宽度
heightauto面板高度

示例:

<darra-panel title="1# 反应釜" border="1px solid #1E90FF" collapsible="true">
<darra-led var="M0.0" color-on="#00FF00" color-off="#666"></darra-led>
<darra-numeric var="DB1.Temperature" format="0.0" unit=""></darra-numeric>
<darra-numeric var="DB1.Pressure" format="0.0" unit="MPa"></darra-numeric>
</darra-panel>

<darra-group-box> — 分组框

分组框与面板类似, 但视觉更简洁 (仅边框+标题)。

属性表:

属性必填默认值说明
title分组标题
border1px solid #ddd边框样式

示例:

<darra-group-box title="供水系统">
<darra-tank var="DB1.Level" max="100"></darra-tank>
<darra-pipe flow-var="DB1.Flow" direction="right"></darra-pipe>
</darra-group-box>

<darra-tab-container> — 标签页容器

标签页用于在有限空间内展示多个页面内容。

属性表:

属性必填默认值说明
active-tab第一个 Tab当前激活的 Tab 索引
positiontoptop / bottom / left / right

示例:

<darra-tab-container>
<darra-tab title="概览">
<darra-numeric var="DB1.Temperature" unit=""></darra-numeric>
</darra-tab>
<darra-tab title="趋势">
<darra-trend vars="DB1.Temperature,DB1.Pressure" max-points="300"></darra-trend>
</darra-tab>
<darra-tab title="报警">
<darra-alarm-list max-rows="10"></darra-alarm-list>
</darra-tab>
</darra-tab-container>

<darra-table> — 表格

表格控件用于显示结构化数据。

属性表:

属性必填默认值说明
var绑定的 PLC 数组变量
columns列定义, 格式: 字段1:标题1,字段2:标题2
page-size20每页行数
sortabletrue是否可排序
filterablefalse是否可筛选

示例:

<darra-table var="DB100.OrderList" columns="id:订单号,product:产品,qty:数量,state:状态" page-size="10"></darra-table>

2.4 数据控件

<darra-dx-grid> — 数据表格 (DevExtreme DataGrid)

高级表格控件, 支持排序、筛选、分页、行编辑、列拖拽等企业级功能。

属性表:

属性必填默认值说明
var绑定的 PLC 数组变量
columns列定义 字段:标题:宽度
paging20每页行数
filter-rowfalse是否启用筛选行
editingfalse是否启用行编辑
selection-modesinglesingle / multiple / none
heightauto表格高度

示例:

<darra-dx-grid var="DB100.OrderList"
columns="id:编号:80,name:产品名称:200,qty:数量:100,state:状态:120"
paging="20"
filter-row="true"
selection-mode="multiple"></darra-dx-grid>

常见错误:

错误原因解决
表格为空白变量未绑定或数据未更新确认 PLC 变量有数据, 查看 F12 网络
列标题显示字段名columns 格式错误格式为 字段:标题 而非 字段:标题:宽度

<darra-trend> — 趋势图 (Chart.js)

趋势图用于实时显示变量的历史变化曲线。

属性表:

属性必填默认值说明
vars逗号分隔的多个变量名
max-points200窗口内最大点数
colors自动每条曲线的颜色, 逗号分隔
update-rate500采样周期 (ms)
unitY 轴单位
height300px图表高度
show-legendtrue是否显示图例
show-gridtrue是否显示网格线

示例:

<!-- 双变量趋势图 -->
<darra-trend vars="DB1.Temperature,DB1.Pressure"
max-points="300"
colors="#38bdf8,#fbbf24"
unit="℃ / MPa"
height="400px"></darra-trend>

<!-- 单变量趋势, 快速刷新 -->
<darra-trend vars="DB3.RPM" max-points="600" update-rate="100" height="200px"></darra-trend>

常见错误:

错误原因解决
趋势图不更新变量未订阅确认 vars 属性中的变量名拼写正确
曲线挤在一起看不清量程差太大考虑用双 Y 轴或分开显示
图表卡顿点数太多减少 max-points 或增大 update-rate

<darra-bar-chart> — 柱状图

属性表:

属性必填默认值说明
vars逗号分隔的变量名
labels变量名柱状图标签
typebarbar / stacked-bar / horizontal-bar
height300px图表高度

示例:

<darra-bar-chart vars="DB4.Production_Q1,DB4.Production_Q2,DB4.Production_Q3,DB4.Production_Q4"
labels="Q1,Q2,Q3,Q4"
type="bar"
height="400px"></darra-bar-chart>

<darra-pie-chart> — 饼图

属性表:

属性必填默认值说明
vars逗号分隔的变量名
labels变量名扇区标签
typepiepie / doughnut
height300px图表高度

示例:

<darra-pie-chart vars="DB4.GoodQty,DB4.DefectQty,DB4.ReworkQty"
labels="合格,缺陷,返工"
type="doughnut"
height="300px"></darra-pie-chart>

<darra-gauge> — 仪表盘

仪表盘用于显示单个数值在量程范围内的位置, 带警告/严重阈值区域。

属性表:

属性必填默认值说明
var数值变量
min0量程最小值
max100量程最大值
warn警告阈值 (显示黄色区域)
critical严重阈值 (显示红色区域)
unit""单位
sizemediumsmall / medium / large

示例:

<darra-gauge var="DB1.Pressure" min="0" max="10" unit="MPa"
warn="7" critical="9"></darra-gauge>

<darra-progress-bar> — 进度条

属性表:

属性必填默认值说明
var进度变量
max100满量程
color#1E90FF进度条颜色
show-labeltrue是否显示百分比
height24px高度

示例:

<darra-progress-bar var="DB1.Progress" max="100" color="#22C55E"></darra-progress-bar>

2.5 报警控件

<darra-alarm-list> — 报警列表

报警列表自动显示 Service 推送的实时报警, 支持级别筛选、排序、确认。

属性表:

属性必填默认值说明
max-rows10最大显示行数
level-filterwarn,error过滤级别 info,warn,error
sorttime desc排序方式
allow-ackfalse是否允许在列表中确认
show-timetrue是否显示时间列
heightauto列表高度

示例:

<!-- 报警面板, 显示所有级别, 可确认 -->
<darra-alarm-list max-rows="20" level-filter="warn,error" allow-ack="true" height="400px"></darra-alarm-list>

<!-- 底部报警条, 只显示错误 -->
<darra-alarm-list max-rows="3" level-filter="error" show-time="false"></darra-alarm-list>

<darra-alarm-popup> — 报警弹窗

当新报警到达时自动弹出窗口, 支持声音和闪烁。

属性表:

属性必填默认值说明
level-filtererror触发弹窗的级别
soundtrue是否播放声音
auto-closefalse是否自动关闭
auto-close-sec10自动关闭秒数

示例:

<darra-alarm-popup level-filter="error" sound="true" auto-close="true" auto-close-sec="15"></darra-alarm-popup>

2.6 导航控件

<darra-nav-button> — 页面导航按钮

属性表:

属性必填默认值说明
route目标路由, 如 /reactor
label子节点文本按钮文字
icon图标 URL

示例:

<darra-nav-button route="/">首页</darra-nav-button>
<darra-nav-button route="/reactor">反应釜</darra-nav-button>
<darra-nav-button route="/alarm">报警中心</darra-nav-button>

<darra-menu> — 菜单

属性表:

属性必填默认值说明
items菜单项, 格式: 标签1:路由1,标签2:路由2
positiontoptop / left / right
modehorizontalhorizontal / vertical

示例:

<darra-menu items="首页:/,反应釜:/reactor,报警:/alarm,趋势:/trend" position="top" mode="horizontal"></darra-menu>

2.7 工业控件

<darra-tank> — 储罐 (含液位动画)

属性表:

属性必填默认值说明
var液位变量 (0 - max)
max100满量程
color#1E90FF液体颜色
show-labelfalse显示百分比
width120px宽度
height200px高度

示例:

<darra-tank var="DB1.Level" max="100" color="#22C55E" show-label="true"></darra-tank>

<darra-motor> — 电机

属性表:

属性必填默认值说明
run-var运行状态位变量
fault-var故障位变量
speed-var速度变量 (控制旋转快慢)
sizemediumsmall / medium / large

示例:

<darra-motor run-var="M10.0" fault-var="M10.1" speed-var="DB3.RPM"></darra-motor>

<darra-valve> — 阀门

属性表:

属性必填默认值说明
open-var开到位位变量
closed-var关到位位变量
position-var开度变量 (0-100%)

示例:

<darra-valve open-var="M11.0" closed-var="M11.1"></darra-valve>

<darra-pipe> — 管道

属性表:

属性必填默认值说明
flow-var流量变量 (>0 时显示流动动画)
directionrightleft / right / up / down
color#888管道颜色

示例:

<darra-pipe flow-var="DB1.Flow" direction="right" color="#4FC3F7"></darra-pipe>

3. 数据绑定

数据绑定是 HMI 与 PLC 通信的核心机制。Darra HMI 提供 变量绑定、属性绑定、表达式绑定 三种方式。

3.1 变量绑定

方式一: 控件属性绑定 (最常用)

<darra-*> 控件的 var 属性中直接指定 PLC 变量地址:

<darra-numeric var="DB1.Temperature" format="0.0" unit=""></darra-numeric>
<darra-led var="M0.0" color-on="#00FF00" color-off="#666"></darra-led>

PLC 变量地址语法:

语法类型示例
M<byte>.<bit>位 (BOOL)M0.0, M10.3
MB<offset>字节 (BYTE)MB10
MW<offset>字 (WORD/INT)MW100
MD<offset>双字 (DWORD/REAL)MD200
I<byte>.<bit>输入位I0.0
Q<byte>.<bit>输出位Q0.0
DB<n>.<field>数据块字段DB1.Temperature
DB<n>.<field>.<sub>嵌套结构DB1.Motor.Speed

方式二: darra.bind() API 绑定

在页面脚本中通过 JavaScript 手动绑定:

// 绑定单变量
darra.bind('DB1.Temperature', (value, meta) => {
document.getElementById('temp-display').textContent = value.toFixed(2) + ' ℃'
// meta.timestamp: 服务端采集时间
// meta.quality: 'good' | 'bad' | 'cached'
// meta.type: 'number' | 'boolean' | 'string'
})

// 绑定整个 DB 块
darra.bindDB('DB1', (values) => {
// values = { Temperature: 25.3, Pressure: 1.2, Level: 80.5 }
})

// 绑定多个变量组
darra.bindGroup(['DB1.T1', 'DB1.T2', 'M0.0'], (values) => {
// values = { 'DB1.T1': 25, 'DB1.T2': 26, 'M0.0': true }
})

方式三: 订阅 API

// 订阅变量变化, 返回取消订阅函数
const unsubscribe = darra.subscribeVariable('DB1.Temperature', (value, meta) => {
console.log(`温度: ${value}`)
})

// 不再需要时取消订阅
unsubscribe()

// 订阅整个 DB
darra.subscribeDB('DB1', (values) => {
console.log('DB1 更新:', values)
})

3.2 属性绑定

除了 var 属性绑定数值外, 还可以绑定其他 HTML 属性。

使用 x-bind (Alpine.js)

<!-- 绑定 CSS class -->
<div x-bind:class="{ 'running': isRunning, 'stopped': !isRunning }"
x-data="{ isRunning: false }"
x-init="darra.bind('M0.0', v => isRunning = v)">

<!-- 绑定 style -->
<div x-bind:style="`background: ${level > 80 ? 'red' : 'green'}`"
x-data="{ level: 0 }"
x-init="darra.bind('DB1.Level', v => level = v)">

使用 Darra.bindElement API

// 绑定元素的 textContent
const el = document.getElementById('temp-display')
Darra.bindElement(el, 'DB1.Temperature')

// 绑定元素的任意属性
Darra.bindElementAttribute(el, 'M0.0', 'class')
// M0.0 = true → el.className = 'on'
// M0.0 = false → el.className = 'off'

// 绑定 CSS 属性 (带阈值映射)
Darra.bindElementStyle(el, 'DB1.Level', 'width', {
min: 0,
max: 100,
unit: '%'
})
// DB1.Level = 75 → el.style.width = '75%'

3.3 表达式绑定

Alpine.js 支持在 HTML 属性中写 内联表达式, 可以做运算、格式化、条件判断。

数学运算

<div x-data="{ t: 0, r: 0 }"
x-init="darra.bind('DB1.TempC', v => t = v); darra.bind('DB1.Ratio', v => r = v)">

<!-- 单位换算: C → F -->
华氏温度: <span x-text="(t * 1.8 + 32).toFixed(1)"></span> °F

<!-- 百分比 -->
利用率: <span x-text="(r * 100).toFixed(1)"></span> %

<!-- 分段条件 -->
状态: <span x-text="t < 20 ? '低' : t < 60 ? '正常' : '高'"></span>
</div>

条件渲染

<div x-data="{ level: 0 }" x-init="darra.bind('DB1.Level', v => level = v)">
<div x-show="level < 20" style="color:red">液位低</div>
<div x-show="level >= 20 && level <= 80" style="color:green">正常</div>
<div x-show="level > 80" style="color:orange">液位高</div>
</div>

<!-- x-if: 条件满足时才会创建 DOM -->
<template x-if="level > 95">
<div class="alarm-panel">
<h2>紧急: 液位超限</h2>
<darra-button var="M0.0" mode="momentary">排空</darra-button>
</div>
</template>

列表循环

<div x-data="{ axes: [
{ id:0, name:'X', pos:0 },
{ id:1, name:'Y', pos:0 },
{ id:2, name:'Z', pos:0 }
] }"
x-init="
axes.forEach(a => {
darra.bind(`DB50.Axis${a.id}.Pos`, v => a.pos = v)
})">

<template x-for="axis in axes" :key="axis.id">
<div>
<span x-text="axis.name"></span>轴: <span x-text="axis.pos.toFixed(3)"></span> mm
</div>
</template>
</div>

3.4 数据源配置

写入白名单

所有写入操作必须经过 WriteWhitelist 白名单校验:

{
"WriteWhitelist": [
"M0.*",
"DB2.Setpoint*",
"MW200"
]
}

不在白名单的写入会被 Service 拒绝, 返回 error 事件。

全局数据源

darra 全局对象提供了几个核心数据源方法:

// 读取当前缓存值 (同步, 不走网络)
const t = darra.get('DB1.Temperature')

// 查询 DB 结构 (异步, 发 HTTP 请求)
const fields = await darra.describeDB('DB1')
// fields = [{ name: 'Temperature', type: 'REAL', comment: '温度' }, ...]

// 写入变量
darra.write('M0.0', true)
darra.write('DB2.Setpoint', 75.5)

// 批量写入 (性能更好)
darra.writeGroup({
'DB2.Setpoint': 75.5,
'M0.1': false,
'MW100': 1234
})

3.5 数组绑定

对于数组类型变量, 需要在控件中指定数组下标:

<!-- 绑定数组第一个元素 -->
<darra-numeric var="DB10.ArrayData[0]" format="0.0"></darra-numeric>

<!-- 循环遍历数组 -->
<div x-data="{ data: [] }" x-init="
for (let i = 0; i < 10; i++) {
darra.bind(`DB10.ArrayData[${i}]`, v => data[i] = v)
}">
<template x-for="(val, idx) in data" :key="idx">
<div>元素 [<span x-text="idx"></span>]: <span x-text="val"></span></div>
</template>
</div>

3.6 绑定更新模式

模式说明适用场景
推送 (默认)Service 主动推送变量变化, 默认 100ms 节流实时监控, 默认推荐
轮询HMI 定时调用 darra.get() 读取缓存低频更新, 不需要实时
事件驱动通过 darra.on('alarm', ...) 等事件接收报警、登录等异步事件

推送节流配置:

darra.init({
updateRate: 100, // 推送节流 ms (越小越实时, 越大越省带宽)
reconnect: true,
reconnectInterval: 3000
})

4. 事件与脚本

4.1 事件类型

Darra HMI 支持以下事件类型:

事件类型来源示例
DOM 事件浏览器原生click, input, change, keydown
PLC 事件Service 推送ready, offline, alarm, error
变量事件值变化触发bind 回调, subscribe 回调
自定义事件用户代码触发CustomEvent, darra.bus
页面生命周期页面切换pageEnter, pageLeave
定时器JavaScriptsetInterval, setTimeout

4.2 DOM 事件 (Alpine 语法)

使用 Alpine 的 @event 语法绑定 DOM 事件:

<!-- 点击事件 -->
<button @click="darra.write('M0.0', true)">启动</button>

<!-- 输入事件, 带防抖 -->
<input @input.debounce.500ms="handleInput($event.target.value)" />

<!-- 键盘事件 -->
<div @keydown.escape="closeDialog()" tabindex="0">按 ESC 关闭</div>

<!-- 全局快捷键 (Ctrl+S 保存) -->
<div @keydown.window.ctrl.s.prevent="save()">全局保存</div>

<!-- 鼠标事件 -->
<div @mouseenter="hover = true" @mouseleave="hover = false"
:class="hover ? 'highlighted' : ''">悬停高亮</div>

Alpine 事件修饰符:

修饰符说明
.prevent阻止默认行为
.stop阻止冒泡
.debounce.300ms防抖 300ms
.throttle.200ms节流 200ms
.outside元素外点击
.window挂在 window 对象上
.once只触发一次
.ctrl / .shift / .alt组合键

4.3 PLC 事件 (darra.on)

使用 darra.on() 监听 Service 推送事件:

// 连接状态
darra.on('ready', () => {
console.log('WebSocket 已连接')
document.querySelector('.offline-banner').style.display = 'none'
})

darra.on('offline', () => {
console.warn('连接断开')
document.querySelector('.offline-banner').style.display = 'block'
// 断开时禁用所有写入按钮
document.querySelectorAll('button.writable').forEach(b => b.disabled = true)
})

// 写入失败
darra.on('error', (err) => {
// err = { type: 'write', var: 'DB2.Setpoint', error: 'not_in_whitelist' }
showToast(`写入失败: ${err.var} (${err.error})`, 'error')
})

// 报警事件
darra.on('alarm', (alarm) => {
// alarm = { level: 'error', source: '伺服2', message: '过载', timestamp: 1713429123000 }
playSound('alarm.mp3')
addToAlarmList(alarm)
})

// 登录成功
darra.on('auth_ok', (msg) => {
console.log('登录成功:', msg.user, '角色:', msg.role)
location.href = '/local/'
})

4.4 HMI 中编写 SCL 脚本

HMI 的 JsCode 中可以编写 JavaScript 逻辑, 通过 darra.* API 访问 PLC 变量。SCL 是 PLC 端的编程语言, HMI 端使用 JavaScript。

变量读写

// 读取 PLC 变量 (从缓存)
const temp = darra.get('DB1.Temperature')

// 写入 PLC 变量 (异步)
darra.write('M0.0', true)
darra.write('DB2.Setpoint', 75.5)

// 批量写入
darra.writeGroup({
'DB2.Setpoint': 80.0,
'M0.1': false
})

页面导航

// 跳转到指定页面
darra.navigateTo('/reactor')
darra.navigateTo('/alarm')

// 获取当前路由
const route = darra.getCurrentRoute() // '/reactor'

// 显示弹出窗口
darra.showPopup('<h2>确认停车?</h2><button onclick="darra.closePopup(true)">确认</button>', {
width: 400,
height: 300,
modal: true,
title: '停车确认'
})

// 关闭弹出窗口
darra.closePopup()

系统函数

// 获取系统信息
const info = darra.getSystemInfo()
// { version: '1.0.5', runtimeMode: 'Production', plcCycleTime: 5, ... }

// 获取报警列表
const alarms = darra.getAlarms({ acknowledged: false, severity: 'Critical' })

// 确认报警
darra.acknowledgeAlarm('alarm-001', '已确认, 安排处理')

// 获取用户信息
const user = darra.getUserInfo()
// { username: 'wang', role: 'engineer', displayName: '王工' }

// 登出
darra.logout()

// 切换主题
darra.setTheme('industrial')

// 显示 Toast 通知
darra.showToast('保存成功', { type: 'success', duration: 3000 })

4.5 完整脚本示例

示例: 一键停机脚本

// 在 JsCode 中定义
async function emergencyStop() {
// 二次确认
const confirmed = await showConfirmDialog('确认切入紧急停机?')
if (!confirmed) return

// 批量写入 PLC 变量
darra.writeGroup({
'M0.0': false, // 主启动 OFF
'M0.1': true, // 急停 ON
'DB2.SetpointSpeed': 0,
'DB2.SetpointTemp': 25
})

// 记录审计日志
await fetch('/api/hmi/audit', {
method: 'POST',
body: JSON.stringify({ action: 'emergency_stop', ts: Date.now() })
})

// UI 反馈
document.body.classList.add('emergency-mode')
darra.showToast('已进入紧急停机', { type: 'error', duration: 0 })
}

示例: 阈值检测脚本

// 温度超限检测
let overTempFired = false
darra.bind('DB1.Temp', (v) => {
if (v > 80 && !overTempFired) {
overTempFired = true
darra.showToast('温度超限!', { type: 'warning' })
darra.write('M0.5', true) // 触发报警输出
} else if (v < 75 && overTempFired) {
overTempFired = false
darra.write('M0.5', false) // 复位报警
}
})

示例: 定时轮询脚本

// 每 30 秒查询一次历史 KPI (历史数据用 REST, 实时数据用订阅)
const pollTimer = setInterval(async () => {
const kpi = await fetch('/api/hmi/kpi/today').then(r => r.json())
updateKpiPanel(kpi)
}, 30000)

// 页面关闭时清理定时器
window.addEventListener('beforeunload', () => clearInterval(pollTimer))

5. 页面管理

5.1 页面创建与配置

创建新页面

  1. 在 HMI 项目节点上右键 → "新建 HMI 页面"
  2. 填写:
    • 页面名称: 显示在页面标签上
    • 路由 (Route): 访问路径, 如 /reactor, 首页设为 /
    • 设为首页: 勾选后将作为默认加载页面

页面配置属性

{
"Name": "反应釜监控",
"Route": "/reactor",
"IsHome": false,
"HtmlCode": "<!DOCTYPE html>...",
"CssCode": "body { ... }",
"JsCode": "// 页面脚本",
"Elements": [
{
"Id": "el_001",
"Type": "Numeric",
"X": 100, "Y": 50,
"Width": 200, "Height": 60,
"VariableBinding": "DB1.Temperature",
"Properties": { "format": "0.0", "unit": "℃" }
}
]
}

5.2 页面路由 (多页 SPA)

Darra HMI 支持多页架构, 每页一个独立路由:

路由页面访问 URL
/首页/local/
/reactor反应釜/local/reactor
/alarm报警中心/local/alarm
/trend趋势分析/local/trend
/recipe配方管理/local/recipe

页面导航方式

<!-- 使用导航按钮 -->
<darra-nav-button route="/reactor">反应釜</darra-nav-button>

<!-- 使用菜单 -->
<darra-menu items="首页:/,反应釜:/reactor,报警:/alarm"></darra-menu>

<!-- 使用 JavaScript 导航 -->
<button @click="darra.navigateTo('/reactor')">反应釜</button>

5.3 Popup 弹窗

Popup 弹窗用于在当前页面之上显示临时内容, 如确认对话框、参数编辑等。

// 打开弹窗
const popup = darra.showPopup(`
<div style="padding:20px;text-align:center;">
<h2>确认停机?</h2>
<p>当前批次: BATCH-001</p>
<button onclick="darra.closePopup(true)" style="background:red;color:white;padding:10px 20px;">确认停机</button>
<button onclick="darra.closePopup(false)" style="margin-left:10px;padding:10px 20px;">取消</button>
</div>
`, {
width: 400,
height: 250,
modal: true,
title: '操作确认',
closeOnEsc: false
})

// 关闭弹窗
darra.closePopup()
darra.closePopup(true) // 带返回值

5.4 页面生命周期

每个页面有完整的生命周期:

页面加载 (Load)


pageEnter 事件触发

├── 首次: 执行 JsCode, 初始化 Alpine

└── Resume (从其他页面返回): 不重新加载, 保留状态


正常运行 (绑定变量, 监听事件)


pageLeave 事件触发 (离开页面时)

├── Suspend: 保留 DOM 状态, 隐藏

└── Destroy: 清理订阅, 释放内存 (多页内存压力大时)

页面生命周期脚本:

// 页面进入时
darra.on('pageEnter', ({ route, prevRoute }) => {
console.log(`进入页面: ${route}, 来自: ${prevRoute}`)
if (route === '/reactor') {
// 初始化反应釜页面
initReactorPage()
}
})

// 页面离开时
darra.on('pageLeave', ({ route, nextRoute }) => {
console.log(`离开页面: ${route}, 前往: ${nextRoute}`)
// 清理定时器
clearInterval(pageTimer)
// 取消所有订阅
darra.unsubscribeAll()
})

// 页面完全加载
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM 已加载')
})

// Alpine 初始化前 (注册自定义指令/魔法的好时机)
document.addEventListener('alpine:init', () => {
Alpine.magic('darra', () => ({
get: (v) => darra.get(v),
write: (v, val) => darra.write(v, val)
}))
})

5.5 多页面切换动画

/* 页面淡入动画 */
body {
animation: page-in 0.4s ease;
}
@keyframes page-in {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}

/* 页面切换时显示加载状态 */
body.loading {
opacity: 0.5;
pointer-events: none;
}
body.loading::after {
content: '加载中...';
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
}

6. 动画与交互

Darra HMI 动画全部基于 CSS transition / animation / keyframes, 不需要 JS 动画库。90% 场景优先用 CSS, 由 GPU 处理, 流畅且省电。

6.1 三种动画技术

技术适合性能
transition属性渐变 (width/color/transform)极高
animation + @keyframes循环/复杂序列 (旋转/闪烁/流动)
JS 动画 (requestAnimationFrame)极端自定义 (轨迹曲线)中 (占 CPU)

6.2 属性动画

位置/大小变化

.box {
transition: all 0.5s ease;
}
.box.expanded {
width: 400px;
height: 300px;
}

旋转动画

@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.motor.running {
animation: spin 1s linear infinite;
}
.motor.slow { animation-duration: 3s; }
.motor.fast { animation-duration: 0.3s; }

透明度动画

.fade-in {
transition: opacity 0.5s ease;
}
.fade-in.hidden {
opacity: 0;
pointer-events: none;
}

颜色动画

.alarm-indicator {
transition: background-color 0.3s ease;
}
.alarm-indicator.warning { background-color: #F59E0B; }
.alarm-indicator.danger { background-color: #EF4444; }

6.3 状态动画

报警闪烁

@keyframes alarm-blink {
0%, 49% { background: #EF4444; }
50%,100% { background: #7f1d1d; }
}
.alarm-active {
animation: alarm-blink 0.5s step-end infinite;
color: #fff;
}

LED 呼吸灯

@keyframes pulse {
0%,100% { opacity: 1; box-shadow: 0 0 8px currentColor; }
50% { opacity: .5; box-shadow: 0 0 20px currentColor; }
}
.led.on { animation: pulse 2s ease-in-out infinite; }

6.4 触摸交互

触摸滑动

<div @touchstart.passive="startX = $event.touches[0].clientX"
@touchend="
let dx = $event.changedTouches[0].clientX - startX;
if (dx > 50) prevPage();
if (dx < -50) nextPage();
">
</div>

长按操作

<script>
let longPressTimer
function startLongPress() {
longPressTimer = setTimeout(() => {
darra.showToast('长按触发', { type: 'warning' })
darra.write('M0.0', true)
}, 2000)
}
function cancelLongPress() {
clearTimeout(longPressTimer)
}
</script>

<button @mousedown="startLongPress()" @mouseup="cancelLongPress()"
@mouseleave="cancelLongPress()"
@touchstart="startLongPress()" @touchend="cancelLongPress()">
长按 2 秒启动
</button>

6.5 动画示例

旋转风扇

@keyframes fan-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.fan {
width: 100px; height: 100px;
border-radius: 50%;
background: conic-gradient(from 0deg, #4FC3F7, #0288D1, #4FC3F7, #0288D1, #4FC3F7);
}
.fan.running {
animation: fan-spin 2s linear infinite;
}
.fan.fast {
animation-duration: 0.5s;
}
<div class="fan" :class="{ 'running': speed > 0, 'fast': speed > 500 }"
x-data="{ speed: 0 }"
x-init="darra.bind('DB3.FanSpeed', v => speed = v)"></div>

液位动画

.tank {
width: 120px; height: 200px;
border: 3px solid #333;
overflow: hidden;
position: relative;
}
.tank .liquid {
position: absolute;
left: 0; right: 0; bottom: 0;
background: linear-gradient(180deg, #4FC3F7, #1E88E5);
height: 0;
transition: height 0.5s ease;
}
.tank .liquid::before {
content: '';
position: absolute;
top: -10px; left: 0; right: 0;
height: 10px;
background: inherit;
border-radius: 50%;
animation: wave 2s ease-in-out infinite;
}
@keyframes wave {
0%,100% { transform: translateX(0) scaleY(1); }
50% { transform: translateX(-5px) scaleY(0.8); }
}
<div class="tank" x-data="{ level: 0 }"
x-init="darra.bind('DB1.Level', v => level = v)">
<div class="liquid" :style="`height: ${level}%`"></div>
</div>

6.6 动画性能最佳实践

做法说明
只动 transform / opacity这两个属性走 GPU 合成层, 不触发 layout/paint
will-change 提示.el { will-change: transform; } (不滥用)
关 Chart.js 动画chart.update('none') 跳过动画
降频处理60FPS 太高, 节流到 20FPS
动画降级@media (prefers-reduced-motion: reduce) 禁用动画

7. 主题与样式

7.1 39 种预设主题

Darra HMI 提供 39 种预设主题, 分为 7 大类别:

现代风格 (6 种)

主题名说明适用场景
modern现代深色, 蓝紫主色数据大屏、车间总览
modern-light现代浅色办公区监控
ocean海洋蓝渐变水处理、船舶
aurora极光绿渐变新能源、环保
sunset暖橙渐变冶金、热处理
graphite石墨灰, 极简高端设备、展厅

工业风格 (8 种)

主题名说明适用场景
industrial经典工业, 浅色绿底黑字通用 SCADA
industrial-dark工业深色昏暗机房
steel冷钢灰冶金、重工
petrochemical石化蓝化工、炼油
pharma医药白, 洁净医药、食品
water水务蓝水处理、泵站
energy能源绿电力、光伏
logistics物流橙仓储、分拣

触摸屏风格 (5 种)

主题名说明
touch-panel通用触摸, 48px 最小触摸目标
touch-panel-dark触摸深色
touch-heavy重工业触摸 (戴手套操作)
kiosk自助查询机, 大字
mobile手机竖屏优化

数字孪生风格 (5 种)

主题名说明
digital-twin暗色发光, 适合 3D 场景
cyberpunk赛博朋克, 霓虹紫
hologram全息蓝, 透明效果
matrix矩阵绿, 数据可视化
glassmorphism毛玻璃效果

无障碍与辅助 (3 种)

主题名说明
high-contrast高对比度白底黑字
high-contrast-dark高对比度黑底白字
colorblind-safe色盲安全配色 (蓝-橙主色)

7.2 主题切换

运行时切换

// JS 切换
darra.setTheme('industrial')
darra.setTheme('brandRed') // 自定义主题

// 获取当前主题
console.log(darra.getTheme()) // 'industrial'

按钮切换

<button @click="darra.setTheme('modern')">现代</button>
<button @click="darra.setTheme('industrial')">工业</button>
<button @click="darra.setTheme('digital-twin')">数字孪生</button>

按页面自动切换

darra.on('pageEnter', ({ route }) => {
switch (route) {
case '/': darra.setTheme('modern'); break
case '/alarm': darra.setTheme('industrial'); break
case '/3d': darra.setTheme('digital-twin'); break
}
})

7.3 自定义主题

方式一: 覆盖 CSS 变量 (推荐)

HMIProjectDef.CustomSkins 中定义:

{
"CustomSkins": [
{
"Name": "brand-blue",
"DisplayName": "公司蓝",
"BaseTheme": "modern",
"Variables": {
"--darra-primary": "#005A9E",
"--darra-bg": "#0A1628",
"--darra-panel-bg": "#112240",
"--darra-text": "#E6F1FF",
"--darra-success": "#22C55E",
"--darra-warning": "#F59E0B",
"--darra-danger": "#EF4444",
"--darra-radius": "6px",
"--darra-font": "'MiSans', 'Segoe UI', sans-serif"
}
}
],
"ActiveSkin": "brand-blue"
}

方式二: 完整 CSS 文件

创建 custom/brand-red.css:

body.theme-brand-red {
--darra-bg: #1A0A0A;
--darra-panel-bg: #2D1515;
--darra-text: #FFE6E6;
--darra-primary: #D7263D;
--darra-success: #22C55E;
--darra-warning: #F59E0B;
--darra-danger: #FF0000;
--darra-radius: 4px;
--darra-font: 'MiSans', sans-serif;
}

然后在 CustomSkins 中引用:

{
"Name": "brand-red",
"DisplayName": "品牌红",
"CssFile": "custom/brand-red.css"
}

7.4 核心 CSS 变量参考

变量默认值 (modern)用途
--darra-bg#1A1D23页面底色
--darra-panel-bg#23272E面板/卡片底色
--darra-text#E6E8EB主文本颜色
--darra-primary#4F7CFF主色 (按钮/链接/图表主线)
--darra-success#22C55E成功 (绿 LED/运行态)
--darra-warning#F59E0B警告 (黄/橙色)
--darra-danger#EF4444危险 (红 LED/停止/故障)
--darra-font'MiSans','Segoe UI',sans-serif字体族
--darra-radius8px默认圆角
--darra-shadow0 4px 16px rgba(0,0,0,0.3)默认阴影

7.5 暗色/浅色模式

// 根据系统偏好自动切换
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
darra.setTheme('modern') // 暗色
} else {
darra.setTheme('modern-light') // 浅色
}

// 监听系统主题变化
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
darra.setTheme(e.matches ? 'modern' : 'modern-light')
})

8. 报警管理

8.1 报警配置

报警在 PLC 侧定义, 通过 UDT 结构体声明:

TYPE T_Alarm :
STRUCT
Code : DINT; (* 唯一编号 *)
Severity : INT; (* 0=Info, 1=Warn, 2=Error, 3=Fatal *)
Active : BOOL;
Acked : BOOL;
Message : STRING[128];
Source : STRING[32]; (* 设备/工位 *)
FirstTime : DT;
LastTime : DT;
END_STRUCT
END_TYPE

报警级别:

级别颜色典型场景
Info0启停/换型/换班
Warn1温度接近上限
Error2工艺偏差/单工位故障
Fatal3急停/设备损坏

8.2 报警显示

报警列表

<darra-alarm-list max-rows="20"
level-filter="warn,error,fatal"
allow-ack="true"
sort="severity,time"
height="400px"></darra-alarm-list>

报警弹窗

<darra-alarm-popup level-filter="error,fatal"
sound="true"
auto-close="true"
auto-close-sec="15"></darra-alarm-popup>

自定义报警显示

darra.on('alarm', (alarm) => {
// alarm: { code, severity, message, source, timestamp, acked }
const row = document.createElement('div')
row.className = `alarm-row severity-${alarm.severity}`
row.innerHTML = `
<span class="time">${new Date(alarm.timestamp).toLocaleTimeString()}</span>
<span class="badge">${['INFO','WARN','ERROR','FATAL'][alarm.severity]}</span>
<span class="source">${alarm.source}</span>
<span class="msg">${alarm.message}</span>
`
document.getElementById('alarm-container').prepend(row)
})

8.3 报警确认

// 手动确认
darra.acknowledgeAlarm('alarm-001', '已确认, 安排处理')

// 批量确认
darra.getAlarms({ acknowledged: false }).forEach(alarm => {
darra.acknowledgeAlarm(alarm.id, '批量确认')
})

8.4 报警历史

// 获取历史报警
const history = darra.getAlarms({
startTime: Date.now() - 7 * 86400000, // 最近 7 天
acknowledged: true,
limit: 100
})

8.5 报警推送

浏览器通知

if (Notification.permission === 'granted') {
new Notification('[ERROR] 1# 反应釜超温', {
body: '当前温度: 85.3℃, 阈值: 80℃',
icon: '/static/img/alarm-error.png',
requireInteraction: true
})
}

语音播报

function speakAlarm(alarm) {
if (!window.speechSynthesis) return
const label = ['信息', '警告', '错误', '致命'][alarm.severity]
const msg = new SpeechSynthesisUtterance(`${label}: ${alarm.source} ${alarm.message}`)
msg.lang = 'zh-CN'
msg.rate = 0.9
speechSynthesis.cancel()
speechSynthesis.speak(msg)
}

9. 配方管理

9.1 配方概念

配方 (Recipe) = 一组命名的工艺参数集合。每个配方项绑定一个 PLC 变量地址, 激活配方时将参数值写入对应的 PLC 地址。

配方组: "焊接参数"
├── 配方: "碳钢 3mm"
│ ├── 焊接电流: 180A → DB1.SetCurrent
│ ├── 焊接电压: 22V → DB1.SetVoltage
│ └── 送丝速度: 8.5m/min → DB1.WireSpeed

├── 配方: "不锈钢 2mm"
│ ├── 焊接电流: 120A
│ └── ...

9.2 配方面板

<darra-recipe-panel
group="焊接参数"
recipe="碳钢_3mm"
show-history="true"
show-compare="true"
height="500px"></darra-recipe-panel>

9.3 配方下发

// 手动下发
darra.loadRecipe('焊接参数', '碳钢_3mm')
.then(() => showToast('配方加载成功', { type: 'success' }))
.catch(err => showToast(`配方加载失败: ${err}`, { type: 'error' }))

// 自动选择配方
const productCode = darra.get('DB1.ProductCode')
let recipeName
switch (productCode) {
case 1: recipeName = '碳钢_3mm'; break
case 2: recipeName = '不锈钢_2mm'; break
default: return
}
darra.loadRecipe('焊接参数', recipeName)

9.4 配方导入/导出

// 导出为 JSON
const json = darra.exportRecipe('焊接参数', '碳钢_3mm', 'json')

// 导出为 CSV
const csv = darra.exportRecipeGroup('焊接参数', 'csv')

// 导入
darra.importRecipe(jsonData, { overwrite: false, validate: true })

10. 安全与权限

10.1 用户认证

启用登录认证:

{
"RequireAuth": true,
"AuthSettings": {
"SessionTimeout": 480,
"MaxLoginAttempts": 5,
"LockoutDuration": 15,
"PasswordMinLength": 8
}
}

登录页面:

<darra-login-panel redirect-on-success="/"></darra-login-panel>

10.2 角色权限

角色权限范围典型用户
viewer只读参观者、质量巡检
operator读写变量、确认报警、切换配方产线操作员
engineer修改配方、调整参数、编辑 HMI工艺工程师
admin所有权限车间主管、IT

权限门控控件

<!-- 仅 admin 可见 -->
<darra-permission-gate role="admin">
<darra-button var="M100.0">紧急停车</darra-button>
</darra-permission-gate>

<!-- 多角色 -->
<darra-permission-gate role="admin,engineer">
<button @click="showEditDialog()">编辑参数</button>
</darra-permission-gate>

脚本权限检查

const user = darra.getUserInfo()
// { username: 'wang', role: 'engineer', permissions: ['read', 'write', 'recipe_edit'] }

if (!user.permissions.includes('write')) {
document.getElementById('setpoint-input').disabled = true
}

10.3 屏幕锁定

{
"ScreenLock": {
"Enabled": true,
"Timeout": 5,
"LockOnSleep": true
}
}
// 手动锁定
darra.lockScreen()

// 解锁
darra.unlockScreen(password)

// 重置空闲计时器
darra.resetIdleTimer()

10.4 审计日志

// 获取最近审计日志
const logs = darra.getAuditLogs({ limit: 100 })

// 按条件筛选
const todayLogs = darra.getAuditLogs({
startTime: Date.now() - 86400000,
category: 'write',
user: 'wang'
})

11. 发布与部署

11.1 编译 HMI 项目

HMI 发布 = 保存项目 → 启动 HmiWebServer → 访问端口 18823。没有传统"打包 MSI"的步骤。

  1. 在 IDE 中 Ctrl+S 保存项目
  2. 菜单 运行 → 启动 PLC Service
  3. 看到日志: [Info] [HmiWebServer] 监听 http://+:18823

11.2 发布到运行时

# 启动 Service (带项目)
Darra.PLC.Service.exe --project=myproject.plc

# 启动 LocalHMI 外壳
Darra.PLC.HMI.exe --fullscreen --kiosk

# 或指定 URL
Darra.PLC.HMI.exe --url=http://localhost:18823/local/ --fullscreen --no-esc-exit

11.3 移动端适配

同一局域网下:

  1. 查 Service 主机 IP: ipconfig | findstr IPv4
  2. 手机浏览器输入 http://192.168.1.100:18823/web/
  3. 添加到主屏 → 创建 PWA 应用图标

11.4 多屏显示

{
"MultiScreen": {
"Enabled": true,
"Screens": [
{ "Id": "main", "Route": "/", "Monitor": 0 },
{ "Id": "alarm", "Route": "/alarm", "Monitor": 1 },
{ "Id": "trend", "Route": "/trend", "Monitor": 2 }
]
}
}

11.5 发布前检查清单

  • 所有 <darra-*>var 属性指向真实 PLC 变量
  • DesignWidth/Height 匹配真实屏幕
  • WriteWhitelist 只包含必须的变量 (最小权限)
  • WebHMI 必须启用 RequireAuth = true
  • 断开 PLC 验证: UI 不崩, 显示"断开重连"
  • 触摸屏测试: 按钮 ≥ 44×44px
  • 移动端测试: 屏幕旋转不遮挡输入框

12. 实际应用示例

12.1 电机控制面板

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>电机控制</title>
<link rel="stylesheet" href="/static/darra-hmi.css" />
<script src="/static/alpine.min.js" defer></script>
<script src="/static/darra-plc.js"></script>
</head>
<body class="theme-industrial">
<div style="padding:20px;max-width:600px;margin:0 auto;">

<h1>1# 主电机控制</h1>

<!-- 电机状态 -->
<div x-data="{ running: false, speed: 0, fault: false }"
x-init="
darra.bind('M10.0', v => running = v);
darra.bind('DB3.RPM', v => speed = v);
darra.bind('M10.1', v => fault = v)">

<!-- 电机指示 -->
<darra-motor run-var="M10.0" fault-var="M10.1" speed-var="DB3.RPM"></darra-motor>

<!-- 运行状态 -->
<div style="margin:16px 0;">
状态: <span x-text="fault ? '故障' : running ? '运行中' : '停止'"
:style="fault ? 'color:red' : running ? 'color:green' : 'color:gray'"></span>
</div>

<!-- 转速 -->
<div style="margin:16px 0;">
当前转速: <darra-numeric var="DB3.RPM" format="#,##0" unit="r/min"></darra-numeric>
</div>

<!-- 控制按钮 -->
<div style="display:flex;gap:12px;margin:16px 0;">
<darra-button var="M10.0" mode="toggle" :disabled="fault">启动/停止</darra-button>
<darra-button var="M10.2" mode="momentary" color="#FF0000">急停</darra-button>
<darra-button var="M10.3" mode="momentary" :disabled="!fault">故障复位</darra-button>
</div>

<!-- 速度设定 -->
<div style="margin:16px 0;">
<label>目标转速: <darra-input var="DB3.SetRPM" min="0" max="3000" step="10"></darra-input></label>
</div>

<darra-slider var="DB3.SetRPM" min="0" max="3000" step="10"></darra-slider>
</div>

</div>
</body>
</html>

12.2 报警监控页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>报警中心</title>
<link rel="stylesheet" href="/static/darra-hmi.css" />
<script src="/static/alpine.min.js" defer></script>
<script src="/static/darra-plc.js"></script>
</head>
<body class="theme-industrial">
<div style="padding:20px;">

<h1>报警中心</h1>

<!-- 报警统计 -->
<div style="display:flex;gap:20px;margin:16px 0;" x-data="{
active: 0, unacked: 0, critical: 0
}" x-init="
darra.on('alarm', () => {
const alarms = darra.getAlarms()
active = alarms.filter(a => !a.acknowledged).length
unacked = alarms.filter(a => !a.acknowledged).length
critical = alarms.filter(a => a.severity === 'Critical' && !a.acknowledged).length
})">
<div style="padding:16px;background:var(--darra-panel-bg);border-radius:8px;">
<div>活动报警</div>
<div style="font-size:48px;font-weight:bold;" x-text="active"></div>
</div>
<div style="padding:16px;background:var(--darra-panel-bg);border-radius:8px;color:#F59E0B;">
<div>未确认</div>
<div style="font-size:48px;font-weight:bold;" x-text="unacked"></div>
</div>
<div style="padding:16px;background:var(--darra-panel-bg);border-radius:8px;color:#EF4444;">
<div>严重报警</div>
<div style="font-size:48px;font-weight:bold;" x-text="critical"></div>
</div>
</div>

<!-- 报警列表 -->
<darra-alarm-list max-rows="50" level-filter="warn,error,fatal" allow-ack="true" height="500px"></darra-alarm-list>

<!-- 报警弹窗 -->
<darra-alarm-popup level-filter="error,fatal" sound="true"></darra-alarm-popup>

</div>
</body>
</html>

12.3 数据仪表盘

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>车间总览</title>
<link rel="stylesheet" href="/static/darra-hmi.css" />
<link rel="stylesheet" href="/static/devextreme/dx.common.css" />
<link rel="stylesheet" href="/static/devextreme/dx.dark.css" />
<script src="/static/alpine.min.js" defer></script>
<script src="/static/darra-plc.js"></script>
<script src="/static/chart.umd.js"></script>
<script src="/static/devextreme/dx.all.js"></script>
<script src="/static/darra-devextreme.js"></script>
</head>
<body class="theme-modern">
<div style="display:grid;grid-template-columns:repeat(12,1fr);grid-template-rows:80px 1fr 1fr 80px;gap:16px;padding:24px;height:100vh;background:#0f172a;">

<!-- 标题栏 -->
<header style="grid-column:1/-1;display:flex;align-items:center;justify-content:space-between;border-bottom:2px solid #334155;">
<h1 style="font-size:48px;margin:0;background:linear-gradient(90deg,#38bdf8,#818cf8);-webkit-background-clip:text;color:transparent;">1 号车间生产总览</h1>
<div x-data="{ now: new Date().toLocaleString('zh-CN') }" x-init="setInterval(() => now = new Date().toLocaleString('zh-CN'), 1000)">
<span x-text="now"></span>
</div>
</header>

<!-- KPI 卡片 -->
<div style="grid-column:1/4;grid-row:2/4;background:rgba(30,41,59,0.7);border-radius:12px;padding:24px;display:flex;flex-direction:column;justify-content:center;">
<div style="font-size:22px;color:#94a3b8;">实时 OEE</div>
<div style="font-size:72px;font-weight:700;color:#38bdf8;" x-data="{ v: 0 }" x-init="darra.bind('DB100.OEE', x => v = x)">
<span x-text="(v * 100).toFixed(1)"></span>%
</div>
</div>

<div style="grid-column:4/7;grid-row:2/4;background:rgba(30,41,59,0.7);border-radius:12px;padding:24px;">
<div style="font-size:22px;color:#94a3b8;">今日产量</div>
<div style="font-size:72px;font-weight:700;color:#22C55E;" x-data="{ v: 0 }" x-init="darra.bind('DB100.TodayQty', x => v = x)">
<span x-text="v.toLocaleString()"></span>
</div>
</div>

<div style="grid-column:7/10;grid-row:2/4;background:rgba(30,41,59,0.7);border-radius:12px;padding:24px;">
<div style="font-size:22px;color:#94a3b8;">合格率</div>
<div style="font-size:72px;font-weight:700;color:#22C55E;" x-data="{ v: 0 }" x-init="darra.bind('DB100.QualityRate', x => v = x)">
<span x-text="(v * 100).toFixed(2)"></span>%
</div>
</div>

<div style="grid-column:10/-1;grid-row:2/4;background:rgba(30,41,59,0.7);border-radius:12px;padding:24px;">
<div style="font-size:22px;color:#94a3b8;">设备在线</div>
<div style="font-size:72px;font-weight:700;color:#38bdf8;" x-data="{ n: 0, t: 30 }" x-init="darra.bind('DB100.OnlineCount', x => n = x)">
<span x-text="n"></span>/<span x-text="t"></span>
</div>
</div>

<!-- 报警条 -->
<footer style="grid-column:1/-1;grid-row:4;background:rgba(30,41,59,0.7);border-radius:12px;padding:8px 16px;">
<darra-alarm-list max-rows="3" level-filter="error"></darra-alarm-list>
</footer>

</div>
</body>
</html>

12.4 配方管理页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>配方管理</title>
<link rel="stylesheet" href="/static/darra-hmi.css" />
<script src="/static/alpine.min.js" defer></script>
<script src="/static/darra-plc.js"></script>
</head>
<body class="theme-industrial">
<div style="padding:20px;max-width:1000px;margin:0 auto;">

<h1>焊接参数 - 配方管理</h1>

<p>当前激活配方: <span x-data="{ active: '' }" x-init="
const a = darra.getActiveRecipe();
if (a) active = a.recipe;
" x-text="active || '无'"></span></p>

<darra-recipe-panel
group="焊接参数"
show-history="true"
show-compare="true"
height="500px"></darra-recipe-panel>

<!-- 参数显示 -->
<div style="margin-top:20px;padding:16px;background:var(--darra-panel-bg);border-radius:8px;">
<h3>当前参数</h3>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div>焊接电流: <darra-numeric var="DB1.SetCurrent" format="0.0" unit="A"></darra-numeric></div>
<div>焊接电压: <darra-numeric var="DB1.SetVoltage" format="0.0" unit="V"></darra-numeric></div>
<div>送丝速度: <darra-numeric var="DB1.WireSpeed" format="0.0" unit="m/min"></darra-numeric></div>
<div>气体流量: <darra-numeric var="DB1.GasFlow" format="0.0" unit="L/min"></darra-numeric></div>
</div>
</div>

</div>
</body>
</html>

13. 调试与排错

13.1 浏览器开发者工具

工具快捷键用途
元素检查F12Ctrl+Shift+I查看 DOM 结构和样式
控制台 ConsoleCtrl+Shift+J查看 JS 错误和日志
网络 NetworkF12 → Network 标签查看 WebSocket 帧和 HTTP 请求
性能 PerformanceF12 → Performance分析页面卡顿原因

13.2 常见问题排错表

连接问题

现象原因解决
右下角显示"断开"Service 未启动或端口未监听运行 → 启动 PLC Service, 确认 18823 端口
连接断断续续网络不稳定或防火墙拦截检查防火墙规则, 确保 WebSocket 端口放行
重连后值还是旧值缓存未失效Ctrl+F5 强刷
外网访问连不上端口映射未配置配置路由器端口映射, 启用 HTTPS 反代

控件问题

现象原因解决
控件显示空白JS 未加载检查 /static/darra-controls.js 返回 200
变量值显示 NaN变量地址拼写错误确认 var 属性与 PLC 变量名一致
按钮点击无反应变量不在写入白名单在项目设置中添加白名单
趋势图不更新变量名拼写错误检查 vars 属性, 看 F12 网络 WS 帧
滑块拖动太卡写入频率太高debounce(300ms)

样式问题

现象原因解决
控件叠在一起未设置 position 或布局在画布中调整位置, 或使用 CSS Grid
主题切换后颜色不变CSS 变量未更新检查 ExtraCss 是否覆盖了 --darra-*
字体不显示字体文件未部署确认字体文件在 /static/fonts/ 目录
触摸屏按钮太小未使用触摸主题切换 touch-panel 主题

脚本问题

现象原因解决
darra 对象未定义JS 文件未加载确认 <script src="/static/darra-plc.js"></script> 存在
bind 回调不触发变量未订阅成功F12 Network → WS → 看 subscribe 帧是否发出
写入失败白名单未通过或变量只读监听 error 事件, 检查 WriteWhitelist
定时器不清理页面切换时未取消pageLeaveclearInterval

13.3 WebSocket 调试

// 查看所有 WebSocket 通信
darra.on('ready', () => console.log('WebSocket 已连接'))
darra.on('offline', () => console.warn('WebSocket 断开'))
darra.on('error', (err) => console.error('WebSocket 错误:', err))

// 查看当前连接状态
console.log(darra.getConnectionStatus())
// 'connected' | 'connecting' | 'offline' | 'reconnecting'

13.4 变量调试

// 查看所有已订阅的变量
console.log(darra._subscribedVars)

// 查看某个变量的缓存值
console.log(darra.get('DB1.Temperature'))

// 查看 DB 结构
darra.describeDB('DB1').then(fields => {
console.table(fields)
})

13.5 性能调优

问题优化方法
页面加载慢减少首次加载的控件数量, 使用懒加载
趋势图卡顿减少 max-points, 增大 update-rate, 关动画
变量更新延迟降低 updateRate (默认 100ms, 可调至 50ms)
内存持续增长pageLeave 中调用 unsubscribeAll()clearInterval
触摸屏响应慢使用 touch-panel 主题, 大按钮减少 DOM 层级

13.6 日志查看

# IDE 日志
%AppData%/DarraPLC/logs/ide_*.log

# Service 日志
<Service目录>/logs/service_*.log

# HMI 运行时日志
F12 Console 面板

附录: 速查手册

控件速查

控件标签绑定属性主要用途
按钮<darra-button>var启停控制
指示灯<darra-led>var状态显示
数值显示<darra-numeric>var数值监控
输入框<darra-input>var参数输入
滑块<darra-slider>var连续调节
开关<darra-switch>var开关控制
下拉框<darra-dropdown>var模式选择
仪表盘<darra-gauge>var量程显示
趋势图<darra-trend>vars实时曲线
进度条<darra-progress-bar>var进度显示
储罐<darra-tank>var液位显示
电机<darra-motor>run-var电机状态
阀门<darra-valve>open-var阀门状态
报警列表<darra-alarm-list>报警显示
导航按钮<darra-nav-button>route页面跳转
面板<darra-panel>容器分组
数据表格<darra-dx-grid>var表格数据
登录面板<darra-login-panel>用户登录

API 速查

函数说明
darra.bind(var, cb)绑定变量
darra.bindDB(db, cb)绑定整个 DB
darra.bindGroup(vars, cb)绑定多变量组
darra.write(var, val)写入变量
darra.writeGroup(vals)批量写入
darra.get(var)读取缓存
darra.on(event, cb)监听事件
darra.navigateTo(route)页面导航
darra.showPopup(html, opts)显示弹窗
darra.setTheme(name)切换主题
darra.getSystemInfo()系统信息
darra.getAlarms(filter)获取报警
darra.acknowledgeAlarm(id)确认报警
darra.getUserInfo()用户信息
darra.logout()登出
darra.showToast(msg, opts)显示通知

地址速查

地址说明
http://localhost:18823/local/LocalHMI 首页
http://localhost:18823/web/WebHMI 首页
ws://localhost:18823/wsWebSocket 地址
http://localhost:18823/api/hmi/db/DB1DB 结构查询 API

本文档由 Darra 文档团队维护, 最后更新: 2026-07-26。