跳到主要内容

远程监控与控制

DarraRT 的 WebHMI 让工程师无需在车间现场, 就能通过手机、平板或办公 PC 的浏览器实时查看产线状态, 并在授权范围内执行启停、模式切换、参数调整等操作。

本章聚焦"设备级"远程监控与控制, 不涉及任何业务数据管理 (订单、物料、追溯等均由其他系统负责)。

场景

  • 工程师在办公室监看多条产线运行状态与节拍
  • 值班工程师在家里手机收到报警, 远程确认并临时调整 PID 参数
  • 生产经理午休时在手机查看当天产量曲线
  • 夜班异常时, 主管通过 VPN 远程停机并派车到现场

部署架构

  手机 / 平板 / 远程 PC
|
| HTTPS (反向代理, 强烈建议)
v
Nginx / Caddy / IIS
|
| HTTP (内网)
v
DarraRT Service:18823 ← 只暴露 WebHMI, 不暴露 WebSocket 直连外网
|
| GlobalIO (零拷贝)
v
DarraRT_PLC.sys + DarraRT_Eth.sys
|
v
EtherCAT I/O / 伺服

HMIType.Web 项目由 Service 托管, 路径 /web/<route>。反代层负责 TLS、Basic Auth 防爬、IP 白名单。

快速部署

Service 侧开启 WebHMI

IDE 新建或编辑工程时, 在 HMI 视图勾选 WebHMI, 设置首页路由 /, 发布后 Service 自动加载。

Service 监听端口默认 18823, 可在 appsettings.json 修改:

{
"Hmi": {
"Port": 18823,
"EnableWeb": true,
"EnableLocal": true
}
}

反向代理 (Nginx 示例)

server {
listen 443 ssl http2;
server_name plc.example.com;

ssl_certificate /etc/ssl/plc.pem;
ssl_certificate_key /etc/ssl/plc.key;

# 只允许公司内网 + VPN 段
allow 10.0.0.0/8;
allow 192.168.0.0/16;
deny all;

location / {
proxy_pass http://127.0.0.1:18823;
proxy_http_version 1.1;

# WebSocket 升级 (变量订阅用)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_read_timeout 3600s;
}
}

客户端访问

手机浏览器打开 https://plc.example.com/web/dashboard, 登录后即可查看。

建议 "添加到主屏幕" 作为 PWA 使用, 支持离线缓存设备状态最后快照。

登录与权限

登录流程

Service 提供真实的 /api/user/login 端点 (POST JSON), 通过 HmiWebServer.cs 实现:

// login.html
async function login() {
const res = await fetch('/api/user/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user: document.getElementById('u').value,
pass: document.getElementById('p').value
})
});
const data = await res.json();
if (data.ok) {
location.href = '/web/'; // 登录后跳主页
} else {
alert('登录失败: ' + data.error);
}
}

登录后 Service 颁发 Cookie, 后续所有 WebSocket 和页面请求都携带身份。

角色与写权限

DarraRT 内置四类角色, 远程场景下建议严格配置:

角色读变量写变量切模式调参备注
ViewerYesNoNoNo只读, 适合远程大屏
OperatorYesYes*YesNo*仅白名单变量
EngineerYesYesYesYes可改工艺参数
AdminYesYesYesYes管理 + 审计

* 写白名单在 HMI 项目级定义, 下文说明。

权限门控件

敏感操作区域用 <darra-permission-gate> 包裹, 无权限用户看不到按钮:

<darra-permission-gate role="engineer">
<button @click="adjustPID()">调整 PID</button>
<darra-input var="DB_PID.Kp" step="0.01"></darra-input>
</darra-permission-gate>

<darra-permission-gate role="operator,engineer,admin">
<darra-button var="M.RemoteStart" mode="momentary">启动</darra-button>
</darra-permission-gate>

只读模式: 监控

最简单的远程场景, 不涉及任何写操作。

状态总览页面

<div x-data="{ status: {} }"
x-init="
darra.bindGroup(
['M.Running','M.Fault','MW.Speed','MD.Temp','MD.Output'],
vs => status = vs
);
">

<darra-led var="M.Running" label="运行"></darra-led>
<darra-led var="M.Fault" label="故障" color="red"></darra-led>
<darra-numeric var="MW.Speed" unit="rpm" digits="0"></darra-numeric>
<darra-numeric var="MD.Temp" unit="C" digits="1"></darra-numeric>

<darra-gauge var="MD.Output" min="0" max="100" unit="%"></darra-gauge>
<darra-trend vars="MD.Temp,MD.Output" window-seconds="300"></darra-trend>
</div>

多设备矩阵

一个页面同时观察 12 台设备:

<template x-for="d in devices" :key="d.id">
<div class="card">
<h3 x-text="d.name"></h3>
<darra-led :var="`${d.id}.Running`"></darra-led>
<darra-numeric :var="`${d.id}.Yield`" unit=""></darra-numeric>
</div>
</template>

移动端自动单列布局, 大屏多列, CSS Grid 自适应即可。

受控写入: 远程操作

远程写入必须满足三重条件: 有登录会话 + 角色权限 + 变量在白名单。三者缺一即被 Service 拒绝并返回 write_ack { ok: false, error: 'permission denied' }

写白名单

项目定义里按变量名配置可远程写入的白名单:

<HMI type="Web">
<WriteWhitelist>
<Var name="M.RemoteStart" roles="operator,engineer,admin"/>
<Var name="M.RemoteStop" roles="operator,engineer,admin"/>
<Var name="M.Mode.Auto" roles="operator,engineer,admin"/>
<Var name="M.Mode.Manual" roles="engineer,admin"/>
<Var name="M.Alm.Ack" roles="operator,engineer,admin"/>
<Var name="DB_PID.Kp" roles="engineer,admin" range="0.01,100.0"/>
<Var name="DB_PID.Ki" roles="engineer,admin" range="0.00,50.0"/>
<Var name="MW.SpeedScale" roles="operator,engineer,admin" range="0,200"/>
</WriteWhitelist>
</HMI>

任何未列入的变量即使代码里 darra.write() 也会被拒。

远程启停

<darra-button var="M.RemoteStart" mode="momentary" label="启动"
confirm-text="确认启动产线?">
</darra-button>

<darra-button var="M.RemoteStop" mode="momentary" label="停机"
confirm-text="确认停机?" style="background:#ff6b3d">
</darra-button>

mode="momentary" 按下置 TRUE 200ms 后自动复位, PLC 梯形图用上升沿触发。

关键按钮建议开启 confirm-text, 弹出原生 confirm() 二次确认, 防止手机口袋误触。

模式切换

<div class="mode-switch">
<darra-button var="M.Mode.Auto" mode="exclusive" group="mode"
label="自动"></darra-button>
<darra-button var="M.Mode.Manual" mode="exclusive" group="mode"
label="手动"></darra-button>
<darra-button var="M.Mode.Maint" mode="exclusive" group="mode"
label="维护"></darra-button>
</div>

group="mode" 保证三者互斥, 点任一个先把同组其他置 FALSE 再置目标 TRUE, 避免"三个模式同时亮"。

参数临时调整

PID 调参是典型远程工程师场景:

<darra-permission-gate role="engineer,admin">
<div class="pid-tuner" x-data="{ confirmed: false }">
<label>比例 Kp:
<darra-input var="DB_PID.Kp" step="0.01" min="0.01" max="100"></darra-input>
</label>
<label>积分 Ki:
<darra-input var="DB_PID.Ki" step="0.01" min="0" max="50"></darra-input>
</label>
<label>微分 Kd:
<darra-input var="DB_PID.Kd" step="0.001" min="0" max="10"></darra-input>
</label>

<darra-trend vars="DB_PID.SP,DB_PID.PV,DB_PID.OUT"
window-seconds="120"></darra-trend>

<button @click="darra.write('DB_PID.Reset', true)">重置积分项</button>
</div>
</darra-permission-gate>

每个 <darra-input> 失焦时触发 darra.write, Service 侧按白名单的 range 约束额外校验, 超范围直接拒写。

移动网络优化

选择性订阅

移动网络带宽有限, 不要 darra.bindDB('*') 全订阅。只订阅当前页面需要的:

// 切换到概览页时
darra.bindGroup(['M.Running','MW.Speed','MD.Temp'], update);

// 切换到参数页时, 先解绑旧的
darra.unbindAll();
darra.bindDB('DB_PID', updatePid);

订阅节流

darra-plc.js 支持配置更新率:

darra.init({
updateRate: 500, // 默认 100ms, 移动端降到 500ms
reconnect: true,
reconnectInterval: 5000
});

高频变量 (MW.Encoder 每 2ms 变化) 前端再节流到 500ms, 用 darra.on('variable', ...) 拿到原始流后 debounce。

断线重连与缓存

darra.on('offline', () => {
document.body.classList.add('offline');
showBanner('网络中断, 显示最后一次快照');
});

darra.on('ready', () => {
document.body.classList.remove('offline');
hideBanner();
});

PWA 配合 Service Worker 可将最后快照存 IndexedDB, 重连前至少让操作员看到"5 分钟前的数据", 而不是白屏。

远程操作审计

所有远程写入都必须可追溯, 核心要求:

谁 + 何时 + 哪个变量 + 原值 → 新值 + 来源 IP

Service 的 WebSocket 处理器在 write / write_group 之前读取当前会话, 写入 Historian:

[2026-04-18 10:23:15] user=zhang.eng role=engineer  ip=10.2.34.5
var=DB_PID.Kp old=1.20 new=1.35

本地 HMI 可做审计查询页面 (只读 Historian):

<darra-audit-log days="7" filter-user="" filter-var="">
</darra-audit-log>

对远程工程师的每一次参数调整都留痕, 事后可回溯。

安全清单

  • 强制 HTTPS + HSTS (Nginx 反代配置)
  • 内网或 VPN 访问, 严禁直接暴露 18823 公网
  • 登录密码强度要求, 支持 LDAP / OAuth 代理认证
  • 写白名单按最小权限原则配置, 写范围 range 尽可能窄
  • 启用审计日志, 保留期 >= 180 天
  • 关键按钮二次确认 (confirm-text)
  • Session 超时 30 分钟无操作自动登出
  • 反代层限流 (同 IP <= 60 req/min)
  • 反代层配置 IP 黑白名单
  • 夜间模式: 非工作时间 Engineer 以下角色只读

常见问题

现象原因处理
页面能开, 变量一直转圈WebSocket 未通过反代Nginx 加 Upgrade/Connection
点按钮无反应变量未在白名单项目文件补 <Var name="..."/>
write_ack ok=false error=permission角色不够用 Engineer 账号或扩展白名单角色
手机上图表卡顿订阅量过大降低 updateRate, 只订阅当前页面变量
VPN 断开后无法访问VPN 隧道路由问题检查 IT 的路由策略
Android Chrome 长连接被杀系统优化后台开启前台通知保活, 或改 SSE

相关文档