跳转到内容

仪表板可视化

仪表板可视化

本节介绍如何构建自动投料系统的 Node-RED Dashboard,实时展示液位趋势、投料记录和设备状态。学习完成后,您将能够:

  • 创建液位趋势图表
  • 显示投料记录和下次投料时间
  • 构建设备运行监控面板
  • 设计多容器 Dashboard
┌──────────────────────────────────────────────────────────────┐
│ 🌿 自动投料系统 Dashboard │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌───────────────────────────────────┐ │
│ │ 容器 A 状态 │ │ 液位趋势 (24h) │ │
│ │ ┌─────────┐ │ │ ╭───╮ │ │
│ │ │ 33% │ │ │ │███│ │ │
│ │ │ █████ │ │ │ ├───┤ │ │
│ │ │ █████ │ │ │ │ │ ╭───╮ │ │
│ │ │ ████ │ │ │ ╰─╯─╰──╯ ╰──╮ │ │
│ │ └─────────┘ │ └───────────────────────────────────┘ │
│ │ 距离: 16cm │ │
│ └───────────────┘ ┌───────────────────────────────────┐ │
│ │ 投料记录 │ │
│ ┌───────────────┐ │ 昨天 08:00 ✅ 投料 3s │ │
│ │ 下次投料 │ │ 今天 08:00 ✅ 投料 3s │ │
│ │ ⏳ 23小时后 │ │ 明天 08:00 ⏳ 等待中 │ │
│ └───────────────┘ └───────────────────────────────────┘ │
│ │
│ ┌───────────────┐ ┌───────────────────────────────────┐ │
│ │ 设备状态 │ │ 告警通知 │ │
│ │ 🟢 在线 │ │ ℹ️ 容器液位正常 │ │
│ │ 上次唤醒: │ │ │ │
│ │ 14:30:25 │ │ │ │
│ └───────────────┘ └───────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
Terminal window
# 通过 Palette 安装 Dashboard
npm install node-red-dashboard
# 或
docker exec -it nodered npm install node-red-dashboard

使用 ui_gauge 节点显示当前液位百分比:

[MQTT In: esp32/dosing/info] ──→ [Function: 提取液位] ──→ [ui_gauge]
──→ [ui_text]

Function 节点:

// Function: 提取液位数据供 Dashboard 显示
var data = msg.payload;
// 更新 Flow Context
flow.set("lastLevel", data.level);
flow.set("lastDistance", data.distance);
flow.set("lastWakeTime", Date.now());
flow.set("bootCount", data.boot);
flow.set("totalDosing", data.total_dosing);
// 输出给 ui_gauge (液位百分比 0-100)
msg.payload = data.level;
return msg;

ui_gauge 配置:

参数
GroupDosing Dashboard
TypeGauge
Label液位
Value format{{value}}%
Min0
Max100
ColorGreen-Yellow-Red

使用 ui_chart 节点显示历史液位趋势:

[MQTT In: esp32/dosing/info] ──→ [Function: 图表数据] ──→ [ui_chart]

Function 节点:

// Function: 构建图表数据 (最近 24 小时)
// 从数据库查询历史数据
msg.topic = "SELECT timestamp, level1 FROM level_history " +
"WHERE timestamp > UNIX_TIMESTAMP() - 86400 " +
"ORDER BY timestamp ASC";
return msg;

查询完成后,使用另一个 Function 格式化:

// Function: 格式化图表数据
var rows = msg.payload;
if (!rows || rows.length === 0) {
msg.payload = [];
return msg;
}
// 转换为图表格式
var chartData = rows.map(function(row) {
return {
x: new Date(row.timestamp * 1000).toISOString(),
y: row.level1
};
});
msg.payload = chartData;
return msg;

ui_chart 配置:

参数
GroupDosing Dashboard
TypeLine Chart
Label液位趋势 (24h)
X AxisLabels
Y AxisMin: 0, Max: 100
Interval1000 (1 秒刷新)
Points24 (每点 1 小时)
[MQTT In: esp32/dosing/status] ──→ [Function: 投料日志] ──→ [ui_table]
// Function: 更新投料日志
var status = msg.payload;
// 获取现有日志
var log = flow.get("dosingLog") || [];
// 添加新条目
if (status.state === "complete" || status.state === "pumping") {
log.unshift({
time: new Date().toLocaleString(),
event: status.state === "complete" ? "✅ 投料完成" : "⏳ 投料中",
duration: status.state === "complete" ? "3s" : "-",
level: flow.get("lastLevel") + "%"
});
// 保留最近 20 条
if (log.length > 20) log.pop();
flow.set("dosingLog", log);
}
msg.payload = log;
return msg;
// Function: 多容器 Dashboard 数据聚合
// 容器配置
var containers = flow.get("containers") || [
{id: "doser_01", name: "原料 A", height: 24},
{id: "doser_02", name: "原料 B", height: 30},
{id: "doser_03", name: "原料 C", height: 20}
];
// 为每个容器获取数据
var dashboardData = containers.map(function(c) {
var data = flow.get(c.id + "_lastData") || {};
return {
name: c.name,
level: data.level || 0,
distance: data.distance || 0,
status: data.state || "unknown",
lastWake: flow.get(c.id + "_lastWake") || "-"
};
});
msg.payload = dashboardData;
return msg;

对于更复杂的布局,使用 ui_template 节点:

<!-- ui_template: 投料系统概览 -->
<div class="dosing-dashboard">
<style>
.container-card {
background: #fff;
border-radius: 8px;
padding: 15px;
margin: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.level-bar {
height: 20px;
border-radius: 10px;
background: linear-gradient(90deg, #dc3545 0%, #ffc107 50%, #28a745 100%);
transition: width 1s ease;
}
.status-online { color: #28a745; }
.status-offline { color: #dc3545; }
.next-dosing {
font-size: 24px;
font-weight: bold;
color: #007bff;
}
</style>
<div class="container-card" ng-repeat="c in msg.containers">
<h3>{{c.name}} <span class="status-{{c.status}}"></span></h3>
<div class="level-bar" style="width: {{c.level}}%"></div>
<p>液位: {{c.level}}% | 距离: {{c.distance}}cm</p>
</div>
<div class="container-card">
<p>下次投料: <span class="next-dosing">{{msg.nextDosing}}</span></p>
<p>总投料次数: {{msg.totalDosing}}</p>
</div>
</div>
// Function: 定时刷新 Dashboard 数据
// 由 Inject 节点每 5 分钟触发
// 重新查询数据库获取最新趋势
msg.topic = "SELECT timestamp, level1 FROM level_history " +
"ORDER BY id DESC LIMIT 24";
return msg;
Dashboard Tab: "自动投料"
├── Group: "容器状态"
│ ├── ui_gauge: 液位 (%)
│ ├── ui_text: 距离 (cm)
│ └── ui_text: 设备状态
├── Group: "液位趋势"
│ ├── ui_chart: 24h 趋势
│ └── ui_chart: 周趋势
├── Group: "投料控制"
│ ├── ui_button: 手动投料
│ ├── ui_dropdown: 选择容器
│ └── ui_text: 下次投料
├── Group: "投料日志"
│ └── ui_table: 最近记录
└── Group: "设备监控"
├── ui_text: 唤醒计数
├── ui_text: 总投料次数
└── ui_text: 最近唤醒时间
1880/ui
# 1. 打开 Dashboard
# 2. 模拟传感器数据
mosquitto_pub -t "esp32/dosing/info" \
-m '{"distance":16,"level":33,"boot":5,"total_dosing":3}'
# 3. 验证 Dashboard 更新
# - 液位仪表显示 33%
# - 距离显示 16cm
# - 设备状态显示 🟢
# 4. 模拟投料
mosquitto_pub -t "esp32/dosing/status" \
-m '{"state":"complete"}'
# - 日志表格出现新条目

可以。使用 Node-RED 的 ui_template 加上 PDF 导出功能,或使用 Grafana 作为替代方案(支持导出 PDF/CSV)。

Dashboard 自带响应式设计,手机浏览器直接访问即可。也可以通过 Telegram Bot 发送每日液位摘要。

MQTT 消息触发更新是即时的。图表数据需要按需刷新(推荐 5-15 分钟/次),避免频繁查询数据库。

推荐做法:

  • 使用 Flow Context 缓存最新数据,减少数据库查询
  • 图表数据使用定时刷新,避免每次 MQTT 消息都查库
  • Dashboard 分组按功能划分清晰
  • 添加手动投料按钮用于测试和应急
  • 移动端优先设计,确保手机查看体验

避免做法:

  • Dashboard 上直接显示原始 JSON 数据
  • 图表查询频率过高(<1 分钟)导致数据库压力
  • 未处理传感器故障时的数据显示(显示 ”-” 而非错误值)
  • 忽略 Dashboard 权限控制(生产环境需加认证)
  1. UI 元素: Gauge (液位), Chart (趋势), Table (日志), Button (控制)
  2. 数据流: MQTT → Function → Dashboard UI 节点
  3. 缓存策略: Flow Context 缓存最新值,定时刷新历史趋势
  4. 多容器: 通过设备 ID 区分数据源
  5. Dashboard Tab: 容器状态 → 趋势 → 日志 → 监控