物联网 物联网数据可视化:Grafana + InfluxDB 搭建实时监控大屏
为什么 IoT 项目需要数据可视化大屏?
做物联网项目的同学一定遇到过这个问题:传感器数据采了一大堆,存在数据库里落灰,老板或客户问”现在温度多少?湿度趋势怎样?“你只能翻日志查半天。
数据不可视化,就等于没有数据。
今天这篇,我就带你从零搭建一套完整的 IoT 数据可视化系统。我们使用 Grafana + InfluxDB 这对黄金组合,ESP32 采集传感器数据后通过 HTTP 协议写入 InfluxDB,再用 Grafana 创建炫酷的实时监控大屏。整个过程不需要写一行前端代码,纯配置就能搞定。
整体架构
ESP32 + BME280 传感器
│
│ HTTP POST /api/v2/write
▼
InfluxDB 2.x
(时序数据库)
│
│ 查询
▼
Grafana 10.x
(可视化大屏)
- ESP32:硬件端,负责采集温湿度、气压数据,通过 WiFi 发送 HTTP 请求写入 InfluxDB
- InfluxDB 2.x:专门存储时序数据的数据库,写入速度快、压缩率高,天然适合传感器场景
- Grafana:开源可视化工具,内置 InfluxDB 插件,拖拽即可生成图表
硬件清单
| 硬件 | 数量 | 说明 |
|---|---|---|
| ESP32 开发板(ESP32-WROOM-32) | 1 | 主控,自带 WiFi |
| BME280 传感器模块 | 1 | 温湿度 + 气压三合一,I2C 接口 |
| 杜邦线(母对母) | 4 | 连接传感器 |
| 面包板 | 1 | 原型搭建 |
第一步:安装 InfluxDB 2.x
推荐用 Docker 部署,一条命令搞定:
docker run -d \
--name influxdb2 \
-p 8086:8086 \
-v influxdb-storage:/var/lib/influxdb2 \
-v influxdb-config:/etc/influxdb2 \
-e DOCKER_INFLUXDB_INIT_MODE=setup \
-e DOCKER_INFLUXDB_INIT_USERNAME=admin \
-e DOCKER_INFLUXDB_INIT_PASSWORD=MakerOnsite2026 \
-e DOCKER_INFLUXDB_INIT_ORG=makeronsite \
-e DOCKER_INFLUXDB_INIT_BUCKET=iot-sensors \
influxdb:2.7
启动成功后,浏览器访问 http://localhost:8086,用 admin / MakerOnsite2026 登录,就能看到 InfluxDB 的管理界面。
创建 API Token
登录后进入 Load Data → API Tokens,点击 Generate API Token → Read/Write Token,创建一个新的读写权限 Token。这个 Token 后续 ESP32 写入数据和 Grafana 读取数据都要用到。
⚠️ 注意:Token 创建后只显示一次,务必复制保存!
第二步:ESP32 采集数据并写入 InfluxDB
我们用 Arduino 框架开发 ESP32,通过 I2C 读取 BME280 传感器数据,然后用 HTTP POST 将数据写入 InfluxDB 的 API。
接线方式
| BME280 引脚 | ESP32 引脚 |
|---|---|
| VCC | 3.3V |
| GND | GND |
| SCL | GPIO 22 |
| SDA | GPIO 21 |
Arduino 代码
#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
// WiFi 配置
const char* ssid = "YourWiFiSSID";
const char* password = "YourWiFiPassword";
// InfluxDB 配置
const char* influxdb_url = "http://192.168.1.100:8086";
const char* org = "makeronsite";
const char* bucket = "iot-sensors";
const char* token = "your-api-token-here";
// BME280 传感器
#define SEALEVELPRESSURE_HPA 1013.25
Adafruit_BME280 bme;
// 数据发送间隔(毫秒)
const int SEND_INTERVAL = 10000;
unsigned long lastSend = 0;
void setup() {
Serial.begin(115200);
// 连接 WiFi
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected, IP: " + WiFi.localIP().toString());
// 初始化 BME280
if (!bme.begin(0x76)) {
Serial.println("BME280 sensor not found!");
while (1) delay(100);
}
Serial.println("BME280 sensor initialized.");
}
void loop() {
unsigned long now = millis();
if (now - lastSend >= SEND_INTERVAL) {
lastSend = now;
sendToInfluxDB();
}
delay(100);
}
void sendToInfluxDB() {
// 读取传感器数据
float temperature = bme.readTemperature();
float humidity = bme.readHumidity();
float pressure = bme.readPressure() / 100.0F;
if (isnan(temperature) || isnan(humidity)) {
Serial.println("Failed to read sensor data!");
return;
}
Serial.printf("Temp: %.2f°C | Humidity: %.2f%% | Pressure: %.2fhPa\n",
temperature, humidity, pressure);
// 构建 InfluxDB Line Protocol
String lineProtocol = "bme280,location=living_room ";
lineProtocol += "temperature=" + String(temperature) + ",";
lineProtocol += "humidity=" + String(humidity) + ",";
lineProtocol += "pressure=" + String(pressure);
// 构建 InfluxDB v2 API URL
String writeUrl = String(influxdb_url) + "/api/v2/write";
writeUrl += "?org=" + String(org);
writeUrl += "&bucket=" + String(bucket);
writeUrl += "&precision=ms";
HTTPClient http;
http.begin(writeUrl);
http.addHeader("Authorization", "Token " + String(token));
http.addHeader("Content-Type", "text/plain; charset=utf-8");
int httpResponseCode = http.POST(lineProtocol);
if (httpResponseCode > 0) {
Serial.printf("InfluxDB write success, HTTP code: %d\n", httpResponseCode);
} else {
Serial.printf("InfluxDB write failed, HTTP code: %d\n", httpResponseCode);
}
http.end();
}
代码要点解析
- Line Protocol:这是 InfluxDB 的数据格式,格式为
measurement,tag_key=tag_value field_key=field_value timestampmeasurement类似 SQL 中的表名,这里是bme280tag是索引字段(如 location),用于快速筛选field是实际数值(如 temperature、humidity)
- 精度参数:
precision=ms表示时间戳精确到毫秒 - Token 认证:InfluxDB 2.x 要求每次请求都带上 Authorization Header
💡 小贴士:如果你的 ESP32 内存紧张,可以把
SEND_INTERVAL调整为 30000(30 秒)或 60000(1 分钟),降低写入频率。
第三步:安装 Grafana 并连接 InfluxDB
Docker 安装 Grafana
docker run -d \
--name grafana \
-p 3000:3000 \
-v grafana-storage:/var/lib/grafana \
grafana/grafana:10.4.0
访问 http://localhost:3000,默认用户名和密码都是 admin,首次登录会要求修改密码。
添加 InfluxDB 数据源
- 左侧菜单点击 Connections → Data Sources
- 点击 Add data source,搜索并选择 InfluxDB
- 关键配置:
| 配置项 | 值 |
|---|---|
| Query Language | Flux |
| URL | http://192.168.1.100:8086 |
| Organization | makeronsite |
| Token | 你的 API Token |
| Default Bucket | iot-sensors |
- 点击 Save & Test,看到 “Data source is working” 就说明连接成功!
第四步:创建监控 Dashboard
创建第一个面板:实时温度
- 点击左侧菜单 Dashboards → Create Dashboard → Add visualization
- 选择刚才创建的 InfluxDB 数据源
- 在 Query Editor 中使用 Flux 查询语言:
from(bucket: "iot-sensors")
|> range(start: -1h)
|> filter(fn: (r) => r["_measurement"] == "bme280")
|> filter(fn: (r) => r["_field"] == "temperature")
|> aggregateWindow(every: 1m, fn: mean, createEmpty: false)
|> yield(name: "mean")
- 右侧 Panel 类型选择 Time series(时序折线图)
- 设置 Panel title 为 “Living Room Temperature”
- 调整颜色、单位(°C)、坐标轴
添加更多面板
点击 Add → Visualization,用同样的方法添加:
湿度面板:
from(bucket: "iot-sensors")
|> range(start: -1h)
|> filter(fn: (r) => r["_measurement"] == "bme280")
|> filter(fn: (r) => r["_field"] == "humidity")
|> aggregateWindow(every: 1m, fn: mean, createEmpty: false)
|> yield(name: "mean")
Panel 类型选 Gauge(仪表盘),单位设置为 Percent (0-100),颜色阈值设为 30%-70% 绿色、其他红色。
气压面板:
from(bucket: "iot-sensors")
|> range(start: -1h)
|> filter(fn: (r) => r["_measurement"] == "bme280")
|> filter(fn: (r) => r["_field"] == "pressure")
|> aggregateWindow(every: 1m, fn: mean, createEmpty: false)
|> yield(name: "mean")
Panel 类型选 Stat(单值显示),单位 hPa。
组合布局
Grafana 支持拖拽调整面板大小和位置。建议布局:
┌──────────────────────┬──────────────────────┐
│ 温度折线图 (宽) │ 湿度仪表盘 (小) │
├──────────────────────┴──────────────────────┤
│ 气压单值显示 │
└──────────────────────────────────────────────┘
保存 Dashboard 并命名为 IoT Sensors Dashboard。
第五步:进阶玩法
添加告警规则
Grafana 支持内置告警。点击面板标题 → Alert → Create Alert Rule:
- 规则:当温度 > 35°C 持续 5 分钟时触发
- 通知渠道:可配置 Email、Webhook、钉钉、企业微信等
多传感器对比
如果你有多个 ESP32 节点,在 Line Protocol 中使用不同的 location tag:
// 节点 2(室外)
String lineProtocol = "bme280,location=outdoor ";
lineProtocol += "temperature=" + String(temperature) + ",";
然后在 Grafana 中用 $location 变量做筛选,或用以下查询同时展示多个位置:
from(bucket: "iot-sensors")
|> range(start: -1h)
|> filter(fn: (r) => r["_measurement"] == "bme280")
|> filter(fn: (r) => r["_field"] == "temperature")
|> group(columns: ["location"])
|> aggregateWindow(every: 5m, fn: mean, createEmpty: false)
历史数据保留策略
InfluxDB 默认数据永久保存,对于高频采集场景会占用大量磁盘。可以创建保留策略(Retention Policy):
# 通过 InfluxDB CLI 创建 90 天自动清理策略
influx v1 dbrp create \
--bucket-id your-bucket-id \
--db iot-sensors \
--rp 90d \
--default
常见问题排查
问题 1:ESP32 写入返回 HTTP 401
原因:Token 不正确或过期。
排查:
# 测试 Token 是否有效
curl -s http://192.168.1.100:8086/api/v2/buckets \
-H "Authorization: Token YOUR_TOKEN"
如果返回 401,重新生成 Token 并更新 ESP32 代码。
问题 2:Grafana 查不到数据
原因:Bucket 名称不匹配或查询语句有误。
排查:
- 在 InfluxDB Web UI 的 Data Explorer 中手动运行查询,确认数据已写入
- 检查 Grafana 数据源的 Bucket 名称是否与 InfluxDB 中一致
- 查询中的
_measurement名称要与 Line Protocol 中一致
问题 3:数据时间戳不对
原因:ESP32 没有 NTP 时间同步,写入的时间戳不准确。
解决:不传时间戳,让 InfluxDB 使用服务器时间:
// 不追加时间戳,InfluxDB 自动使用写入时的服务器时间
String lineProtocol = "bme280,location=living_room ";
lineProtocol += "temperature=" + String(temperature) + ",";
// URL 中去掉 precision=ms 参数
问题 4:图表数据断断续续
原因:ESP32 WiFi 连接不稳定或发送间隔过长。
排查:
- 串口监视器中查看
InfluxDB write success的日志频率 - 检查 ESP32 与 InfluxDB 服务器的网络延迟
- 考虑在 ESP32 端添加数据缓存,网络恢复后补发
总结
今天我们完成了一套完整的 IoT 数据可视化方案:
- InfluxDB 负责高效存储传感器时序数据,写入速度轻松达到每秒数万条
- Grafana 零代码拖拽生成漂亮的监控面板
- ESP32 + BME280 提供实时环境数据采集
这套方案的优势在于:
- 低成本:全部开源软件,硬件成本不到 50 元
- 可扩展:轻松接入几十甚至上百个传感器节点
- 实时性:数据从采集到展示延迟不到 10 秒
- 零前端:不需要写 HTML/CSS/JS,配置即可
下一步你可以尝试接入 MQTT Broker(如 EMQX),让数据通过 MQTT → Telegraf → InfluxDB 的管道流入,实现更标准化的 IoT 数据流水线。
Happy Making!🔧