跳到主要内容

HMI 诊断

HMI 运行在浏览器/WebView2, 无法像 PLC 那样靠断点/单步; 调试手段全部是 Web 标准工具: 控制台、Network、Performance、darra 内部状态。本文汇总所有必备诊断手段。

PLC 程序没有断点/单步 (实时系统), HMI 作为前端展示层也不使用 WPF 调试器概念, 所有诊断均通过 DevTools 进行。

1. 打开 DevTools

环境方法
LocalHMI (Debug 构建)Ctrl+Shift+I
LocalHMI (Release)默认禁用, 需改注册表 ms-edge-devtools-enabled=1Darra.PLC.HMI.exe --dev
浏览器 WebHMIF12

2. Console 日志层级

console.log   ('普通日志')
console.info ('提示')
console.warn ('警告')
console.error ('错误')
console.table([{id:1,name:'a'}, {id:2,name:'b'}]) // 表格
console.group('配方加载')
console.log('读取配方列表')
console.log('切换到目标配方')
console.groupEnd()

console.time('query')
await fetch('/api/hmi/db/DB_Recipe') // 查询 DB 块结构是真实 API
console.timeEnd('query') // query: 123ms

3. darra 内部状态

控制台直接查:

darra._connected          // true / false
darra._ws.readyState // 0 CONNECTING 1 OPEN 2 CLOSING 3 CLOSED
darra._subscribedVars // Set { 'DB1.Temp', 'M0.0', ... }
darra._values // { 'DB1.Temp': 25.3, ... } 当前缓存
darra._bindings // Map { 'DB1.Temp' => Set(callbacks) }
darra._dbBindings // Map { 'DB1' => Set(callbacks) }
darra._groupBindings // [ { vars:[...], callback } ]
darra._config // { updateRate, reconnect, ... }
darra.getTheme() // 'modern' | 'industrial'

4. 业务日志 darra.log(...)

在 JsCode 顶部约定统一 logger:

darra.log = (level, module, ...args) => {
const line = `[${new Date().toLocaleTimeString()}] [${level}] [${module}]`
console[level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log'](line, ...args)

// 同时上报到 Service 日志
if (level === 'error' || level === 'warn') {
darra._ws?.send(JSON.stringify({
type: 'log', level, module, message: args.map(String).join(' '),
ts: Date.now()
}))
}
}

// 使用
darra.log('info', 'RecipePanel', '加载配方', recipeCode)
darra.log('warn', 'RecipePanel', '配方参数超限:', value)
darra.log('error', 'RecipePanel', ex.message, ex.stack)

Service 端 HmiWebServer 收到 log 消息, 写入 logs/service_*.log

5. Network 面板

HTTP 请求

Filter 输入 /api/hmi/ 看所有 Service 调用:

  • Status Code 401: 未登录
  • Status Code 500: Service 异常, 看 Response 错误堆栈
  • Status Code 404: 路径错

WebSocket 帧

  • Filter 切 WS 标签
  • ws 连接 → Messages 标签
  • 绿色 ↑ = 客户端发 (subscribe / write), 红色 ↓ = 服务端推 (update / alarm)

示例帧

// ↑ 订阅
{ "type": "subscribe", "vars": ["DB1.Temp", "M0.0"] }

// ↓ 更新推送
{ "type": "update", "values": { "DB1.Temp": 25.30, "M0.0": true }, "ts": 1713429123456 }

// ↑ 写入
{ "type": "write", "var": "DB2.Setpoint", "value": 75.5 }

// ↓ 写入应答
{ "type": "write_ack", "ok": true, "var": "DB2.Setpoint" }

// ↑ 写入失败
{ "type": "write_ack", "ok": false, "var": "M100.0", "error": "not_in_whitelist" }

6. 帧率 (FPS) 检测

let lastTs = performance.now(), frameCount = 0
function tick() {
frameCount++
const now = performance.now()
if (now - lastTs >= 1000) {
const fps = frameCount / ((now - lastTs) / 1000)
console.log('FPS:', fps.toFixed(1))
document.getElementById('fps-meter').textContent = fps.toFixed(0)
frameCount = 0
lastTs = now
}
requestAnimationFrame(tick)
}
requestAnimationFrame(tick)

健康值:

  • 60 FPS: 空闲/简单页
  • 30 FPS: 大量动画
  • <20 FPS: 卡顿, 需优化 (减少动画 / 降低 Chart update / 关闭 will-change)

7. Performance 面板

  1. F12 → Performance → Record
  2. 操作 10-20 秒 → Stop
  3. 查看:
    • Frames 条目 (绿色健康, 红色掉帧)
    • Scripting 蓝色 (JS 时间)
    • Rendering 紫色 (布局/绘制)
    • GPU 黄色 (合成)

Scripting 过长 → JS 代码优化; Rendering 过长 → 减少 DOM 操作。

8. 内存检测

// 快速快照
console.log((performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(1), 'MB')

// Chrome 专属 (Edge 也有)
// F12 → Memory → Take Heap Snapshot

观察:

  • 每个页面加载后应在 20-50 MB 内
  • 长期运行内存持续增长 → 有泄漏, 找 setInterval / 未清理的 bind

9. 绑定刷新率

// 统计每个变量每秒触发次数
const counter = {}
const origBind = darra.bind.bind(darra)
darra.bind = (v, cb) => origBind(v, (val, meta) => {
counter[v] = (counter[v] || 0) + 1
cb(val, meta)
})

setInterval(() => {
console.table(counter)
for (const k in counter) counter[k] = 0
}, 1000)

健康值: 每个变量 <=10 次/秒 (与 Service updateRate 100ms 吻合)。超出说明 Service 推送过于频繁。

10. WebSocket 健康检查

function healthCheck() {
return {
connected: darra._connected,
readyState: ['CONNECTING','OPEN','CLOSING','CLOSED'][darra._ws?.readyState ?? 3],
subscribed: darra._subscribedVars.size,
cachedValues: Object.keys(darra._values).length,
bindings: darra._bindings.size,
dbBindings: darra._dbBindings.size
}
}
console.table(healthCheck())

11. 报错收集

// 全局 JS 错误 (darra-plc.js 已内置)
window.addEventListener('error', e => {
console.error('[global error]', e.message, e.filename, e.lineno)
})
window.addEventListener('unhandledrejection', e => {
console.error('[unhandled promise]', e.reason)
})

darra-plc.js 已在右下角显示红色 toast, 并通过 WebSocket 上报到 Service。

12. CSS 调试

DevTools Elements 选中 → 右侧 Styles:

  • 看每个 CSS 规则的来源 (darra-hmi.css / 皮肤 / 页面)
  • 勾选/去掉单条规则试效果
  • 计算样式 (Computed) 看最终生效值
  • Box Model 看 margin/padding

13. 常见问题定位

症状排查
页面白屏Console 有 darra is not defined → 检查 darra-plc.js 路径
变量不更新WS Messages 无 subscribe → bind 调用时机早于 ready
写入无反应WS 有 write_ack ok:false → 白名单
右下角红色"断开"Service 18823 未启动 或 防火墙
卡顿Performance Record → JS 重 or DOM 多
内存涨Memory 对比两次快照 → 找 Detached DOM 和未释放闭包
样式错乱Elements → Computed 看覆盖关系
字体模糊body { transform: scale(...) 过度缩放 → 改 DesignWidth

14. Darra HMI 诊断卡片 (可内嵌)

提供一个页面右下角的诊断小面板:

<div id="darra-debug" x-data="darraDebug()" x-init="start()" x-show="visible"
style="position:fixed;bottom:40px;right:12px;background:#000a;color:#0f0;font:11px monospace;padding:8px 12px;border-radius:6px;z-index:99998">
<div>FPS: <span x-text="fps"></span></div>
<div>WS: <span x-text="wsState"></span></div>
<div>订阅: <span x-text="subs"></span></div>
<div>内存: <span x-text="mem"></span> MB</div>
</div>

<script>
function darraDebug() {
return {
visible: false, fps: 0, wsState: '-', subs: 0, mem: 0,
start() {
// Ctrl+Alt+D 切换
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.altKey && e.key === 'd') this.visible = !this.visible
})
let last = performance.now(), count = 0
const tick = () => {
count++
const now = performance.now()
if (now - last >= 1000) {
this.fps = (count / ((now - last)/1000)).toFixed(0)
this.wsState = darra._connected ? 'OPEN' : 'CLOSED'
this.subs = darra._subscribedVars.size
this.mem = ((performance.memory?.usedJSHeapSize || 0) / 1048576).toFixed(1)
count = 0; last = now
}
requestAnimationFrame(tick)
}
requestAnimationFrame(tick)
}
}
}
</script>

相关文档