跳到主要内容

数据源

Darra HMI 的数据来源不局限于 PLC 变量, 页面可同时集成:

  1. PLC 变量 (最常用, WebSocket 订阅)
  2. Service REST API /api/hmi/* (数据库 / 文件 / 配置)
  3. 外部 REST (通过 Service 代理跨域)
  4. 外部 MQTT (通过 Service 桥接)
  5. IndexedDB (浏览器本地大数据缓存)
  6. LocalStorage (用户偏好 / 主题)
  7. Python 脚本桥接 (AI / 图像 / 大数据)

1. PLC 变量 (主干)

变量绑定基础. 所有 darra.bind / <darra-* var> 走 WebSocket /ws

2. Service REST API /api/hmi/*

HmiWebServer 提供的内置端点:

HmiWebServer 是设备控制系统的 HMI 服务器, 不是业务后端。它只提供 HMI 自身需要的管理端点 (登录/DB 描述/页面列表/资源上传), 不提供业务 CRUD (订单/物料/客户等)。

路径方法说明
/api/hmi/statusGET服务器状态 (活跃会话数/启动时间/版本)
/api/hmi/variablesGET所有可绑定 PLC 变量清单
/api/hmi/db/{dbName}GET查询 DB 块结构 (字段名/类型/偏移)
/api/hmi/pagesGET列出所有 HMI 页面
/api/hmi/templatesGET项目模板列表
/api/hmi/uploadPOST上传图片/资源到项目 assets
/api/hmi/loginPOST用户登录 (body {user, pass}, 返回 JWT)
/api/user/*用户管理 (角色/权限)
/api/script/*Python 脚本调度与结果查询 (HmiScriptScheduler)

实时值/写控制, 不要走 REST — 直接用 darra.bind / darra.write (走 WebSocket /ws, 见 变量绑定)。

通用调用模式

// 描述 DB 块 (拿到字段/类型, 做动态表单)
const db1 = await (await fetch('/api/hmi/db/DB1')).json()
// → { fields: [{name:'Temp', type:'REAL', offset:0}, ...] }

// 登录拿 JWT
const res = await fetch('/api/hmi/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user: 'op01', pass: '****' })
})
const { token } = await res.json()
// token 自动存 Cookie, 后续请求带上

Alpine 异步数据源 (PLC 变量首选 darra.bind)

读当前 DB 块结构 生成动态表单:

<div x-data="{ fields: [] }"
x-init="fields = (await (await fetch('/api/hmi/db/DB_Recipe')).json()).fields">

<template x-for="f in fields" :key="f.name">
<label>
<span x-text="f.name"></span>
<darra-input :var="'DB_Recipe.' + f.name"></darra-input>
</label>
</template>
</div>

3. 外部 REST (经 Service 代理)

浏览器不能直接跨域调外部 API (CORS 限制)。Service 提供代理路径 /api/hmi/proxy/*:

// 假设 Service 配置代理: upstream = 'https://api.factory.com'
const prod = await fetch('/api/hmi/proxy/mes/production?date=2026-04-18').then(r => r.json())

Service 端配置 (项目文件 .plc 中):

{
"HmiProxy": {
"mes": {
"upstream": "https://api.factory.com",
"auth": "Bearer ${env:MES_TOKEN}",
"allowedPaths": ["/mes/*"]
}
}
}

4. 外部 MQTT (Service 桥接)

HmiWebServer 内置 MQTT 桥接: 订阅的 MQTT 主题会通过 WebSocket 推送给 HMI。

订阅

darra.on('ready', () => {
// 发送桥接订阅
darra._ws.send(JSON.stringify({
type: 'mqtt_subscribe',
topic: 'factory/line1/temperature'
}))
})

// 收到推送
darra.on('mqtt', (msg) => {
// msg = { topic, payload }
console.log(msg.topic, msg.payload)
})

发布

darra._ws.send(JSON.stringify({
type: 'mqtt_publish',
topic: 'factory/line1/cmd',
payload: { action: 'start' }
}))

5. IndexedDB (本地大数据)

浏览器原生, 适合存储历史趋势 / 报警历史 / 工单缓存:

// 打开数据库
const dbReq = indexedDB.open('darra-hmi', 1)
dbReq.onupgradeneeded = (e) => {
const db = e.target.result
if (!db.objectStoreNames.contains('trend')) {
db.createObjectStore('trend', { keyPath: 'ts' })
}
}

let dbHandle
dbReq.onsuccess = (e) => { dbHandle = e.target.result }

// 写入
function saveTrend(record) {
const tx = dbHandle.transaction('trend', 'readwrite')
tx.objectStore('trend').add(record)
}

darra.bindDB('DB1', (vals) => {
saveTrend({ ts: Date.now(), t: vals.Temperature, p: vals.Pressure })
})

// 读取近 1000 条
function queryRecent(n) {
return new Promise((resolve) => {
const tx = dbHandle.transaction('trend', 'readonly')
const req = tx.objectStore('trend').getAll(null, n)
req.onsuccess = () => resolve(req.result)
})
}

IndexedDB 只做纯本地缓存 — 设备历史趋势, 页面偏好, 离线暂存。不是业务数据库 (详见 通讯 / 数据库 说明 DarraRT 的数据库定位: 以读为主, 写罕见)。

6. LocalStorage (轻量配置)

// 保存用户偏好
localStorage.setItem('darra-layout', JSON.stringify({ showSidebar: true, columns: 3 }))

// 读取
const pref = JSON.parse(localStorage.getItem('darra-layout') || '{}')

// 监听变化 (多标签同步)
window.addEventListener('storage', (e) => {
if (e.key === 'darra-layout') {
console.log('其他标签改了偏好:', JSON.parse(e.newValue))
}
})

限制: 最大约 5-10 MB, 仅存字符串, 不适合大数据。

7. Python 脚本桥接

调用 Service 的 HmiPythonBridge:

// Service 通过 HmiScriptScheduler 调度 Python 脚本, REST 端点在 /api/script/*
const result = await fetch('/api/script/invoke', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
script: 'analyze_trend', // scripts/analyze_trend.py
args: { window: 3600 }
})
}).then(r => r.json())

console.log('Python 结果:', result) // { anomaly: true, score: 0.87 }

Service 端 scripts/analyze_trend.py:

import numpy as np
from bridge import get_trend

def run(window=3600):
data = get_trend('DB1.Temperature', window)
arr = np.array(data)
mean = arr.mean()
std = arr.std()
z = (arr[-1] - mean) / std if std > 0 else 0
return { 'anomaly': abs(z) > 3, 'score': float(z), 'mean': float(mean) }

多数据源聚合

一个 HMI 页面常同时消费多种数据源:

async function bootstrap() {
// 1. PLC 实时变量
darra.bindDB('DB1', updateKpi)

// 2. Historian 历史数据 (见 advanced/historian)
const history = await queryHistorian({ var: 'DB1.OEE', bucket: '5m', from: '-1d' })
renderHistoryChart(history)

// 3. IndexedDB 本地趋势缓存
const trend = await queryRecent(1000)
renderTrendChart(trend)

// 4. MQTT 外部事件桥接 (Service MQTT 客户端代理)
darra.on('mqtt', renderMqttAlert)

// 5. Python 分析 (每分钟一次, HmiScriptScheduler 执行)
setInterval(async () => {
const r = await callPython('analyze_trend', { window: 3600 })
updateAnomaly(r)
}, 60000)
}

darra.on('ready', bootstrap)

刷新策略

数据类型推荐策略
PLC 变量订阅推送 (默认 100ms 节流)
REST 静态数据启动拉 1 次 + 手动刷新按钮
REST 动态数据定时 setInterval (5s-60s) 或 MQTT 事件驱动
IndexedDB本地读, 启动时全量 / 变化时增量
Python 分析按需 (用户点击) 或 定时 (1 分钟)

断线容错

数据源断线表现处理
PLC (WebSocket)offline 事件darra-conn-status 红色, 3s 重连
Service RESTHTTP 超时/500try/catch, 显示 toast, 不影响其他源
外部 RESTService 代理返 502降级到缓存 / 提示
MQTT桥接断Service 自动重连, HMI 无需处理
IndexedDB极少出错try/catch, 失败降级到 LocalStorage

排错

现象原因排查
fetch 401未登录查 Cookie darra-token, 重新 login
fetch 404路径拼错Network 面板看完整 URL
WebSocket 收不到 MQTTService MQTT 未连Service 日志 [MqttBridge] connected?
IndexedDB 满浏览器配额定期清理旧数据

相关文档