当前位置: 首页 > news >正文

基于MQTT与Docker的物联网数据采集与可视化实战

1. 项目概述:从物理感知到数据流动的实践

几年前,我在家里布置了几个传感器,初衷很简单:想知道有没有人进出我的房间。一开始,我用杜邦线直接把传感器接到我的主力电脑上,代码一跑,数据就在终端里跳出来了。但很快我就发现了问题——我的电脑被“栓”在了房间角落,想抱着笔记本去客厅或者书房工作一会儿都成了奢望。更麻烦的是,如果我想在客厅或者厨房也加装传感器,难道要拉一屋子飞线,或者给每个房间都配一台电脑吗?这显然不是个可持续的方案。

这个小小的不便,恰恰是很多物联网(IoT)项目从原型走向实用化时遇到的第一道坎:数据采集端与数据处理端的解耦。我们需要一种方式,让分布在各个角落的传感器能独立、可靠地把数据“报告”上来,而中央处理单元(比如我的电脑)可以自由移动,甚至可以是云端的一台虚拟机。这时,MQTT协议进入了我的视野。它是一种基于发布/订阅模式的轻量级消息传输协议,专为网络带宽有限、设备资源受限的物联网场景设计。传感器作为“发布者”,只需要将数据扔到指定的“主题”(Topic)上;而我的电脑作为“订阅者”,只需要订阅这个主题,就能收到所有消息。双方无需知道对方的具体位置或状态,实现了彻底的松耦合。

为了搭建这个通信桥梁,我选择了Solace PubSub+作为消息代理(Broker)。你可以把它理解为一个“邮局”或者“消息交换机”。所有设备(Raspberry Pi)都把消息寄到这个邮局,并写明收件人地址(主题);而需要处理消息的应用(如运行在我电脑上的数据看板)则到邮局登记订阅这个地址。邮局负责可靠地中转所有信件。为了让整个环境干净、易于管理,我使用Docker将 Solace 和后续的数据处理流程(如Node-RED流编排、InfluxDB时序数据库、Grafana可视化)全部容器化。最终,Raspberry Pi 负责采集物理世界的数据(比如人体移动),通过 MQTT 协议发布到 Solace Broker,再由 Node-RED 接收、处理,并存入数据库用于持久化和可视化。

这套方案的核心价值在于其模块化和可扩展性。无论你是想监测房间的温湿度、检测门窗开关状态,还是像我做人体感应,其数据流的骨架是一致的。你可以轻易地复制出第二个、第三个 Raspberry Pi 节点,只需将其配置为向同一个 Broker 发布数据即可,中央处理系统无需做任何改动。这对于智能家居、环境监测、工业传感等场景来说,是一个坚实且优雅的基础架构。

2. 核心架构与工具选型解析

在动手接线写代码之前,理清整个系统的架构和为什么选择这些工具,能避免后期很多不必要的折腾。整个系统的数据流可以清晰地分为三层:感知层、传输层、应用层

感知层的核心是 Raspberry Pi 及其连接的传感器。我选择 Raspberry Pi(树莓派)的原因很直接:它是一台完整的、可运行 Linux 的微型电脑,拥有丰富的 GPIO(通用输入输出)接口,可以直接连接各种电子传感器,同时具备网络连接能力。相比单纯的单片机(如 Arduino),它的优势在于可以直接运行 Python、Node.js 等高级语言程序,处理复杂的网络通信协议(如 MQTT 客户端库)轻而易举,免去了在单片机上移植 TCP/IP 协议栈的麻烦。

传输层的核心是 MQTT 协议和 Solace PubSub+ Broker。为什么是 MQTT 而不是 HTTP 或 WebSocket?

  • 轻量级:MQTT 协议头很小,最小消息仅需 2 字节,非常适合传感器这种可能使用蜂窝网络(流量贵、延迟高)或电池供电(需要节能)的场景。
  • 异步发布/订阅:传感器(发布者)发送数据后无需等待响应,立即进入休眠以省电。处理程序(订阅者)即使暂时离线,Broker 也可以为其保留消息(需配置 QoS 等级),待其上线后重新推送,保证了消息的可靠交付。
  • 一对多通信:一个传感器数据可以被多个后端应用同时订阅。例如,同一个移动传感器数据可以同时触发告警、更新数据库、并刷新前端仪表盘。

而选择Solace PubSub+作为 Broker,一方面是它的社区版功能足够强大且免费,支持标准的 MQTT 3.1.1/5.0 协议;另一方面,它作为企业级消息中间件,提供了高吞吐、低延迟的特性,并且其 Docker 镜像部署极其简单。当然,你也可以选择更轻量的Mosquitto或云服务商提供的 MQTT Broker(如 EMQX Cloud, AWS IoT Core),但 Solace 在功能完整性和部署简便性上取得了很好的平衡。

应用层我使用了Node-REDInfluxDBGrafana的组合。这是一个在物联网领域非常流行的“铁三角”。

  • Node-RED:一个基于流的低代码编程工具。用它可以像搭积木一样,通过拖拽节点并连线,快速构建出数据接收、过滤、转换、分发的逻辑。对于快速原型和中小型项目来说,它比从头编写后端服务高效得多。
  • InfluxDB:一个专门为时间序列数据(如传感器读数)优化的数据库。它写入速度快,压缩效率高,并且提供了强大的时间范围查询函数,非常适合存储“时间戳-值”这样的数据对。
  • Grafana:一个功能强大的数据可视化平台。它可以轻松地从 InfluxDB 中读取数据,绘制出实时曲线图、仪表盘、统计面板等,让我们能直观地观察数据变化趋势。

最后,Docker是整个系统的“粘合剂”和“保险箱”。它将每个服务(Solace, Node-RED, InfluxDB, Grafana)封装在独立的容器中,彼此隔离。

注意:使用 Docker 的最大好处是环境一致性和可移植性。你在一台机器上配好的整套服务,可以轻易地通过docker-compose.yml文件在另一台机器上原样复现,完全不用担心操作系统差异、依赖库冲突等问题。如果某个容器崩溃了,重启它也不会影响其他服务。

3. 基础环境搭建:虚拟机与Docker部署

我的主力机是 Windows,虽然 Docker Desktop for Windows 已经很好用,但在处理网络桥接和端口暴露时,有时会遇到一些棘手的兼容性问题。为了让 Raspberry Pi(在家庭局域网内)能稳定地访问到运行在主机上的 Docker 服务,我决定引入一个中间层:虚拟机。这样,所有服务都部署在一个独立的 Linux 虚拟机中,虚拟机的网络模式可以灵活配置,完美解决了跨设备通信的问题。

3.1 创建Linux虚拟机

我选择了VirtualBox作为虚拟机软件,因为它免费、开源且功能全面。客户机操作系统我推荐Ubuntu Server LTS版本,它没有图形界面,更轻量,通过 SSH 管理即可,非常适合作为服务器环境。

  1. 下载与安装:从 Ubuntu 官网下载最新的 Server LTS 镜像。在 VirtualBox 中创建新虚拟机,类型选 Linux,版本选 Ubuntu (64-bit)。内存分配建议至少 2GB,硬盘空间 20GB 以上。在存储设置中,加载下载好的 Ubuntu ISO 文件作为启动盘。
  2. 系统安装:启动虚拟机,跟随安装向导。关键步骤包括配置键盘布局、设置主机名(如iot-gateway)、创建用户(避免一直用 root)。在软件选择界面,只需勾选 “OpenSSH server”,这样安装完成后就可以从主机通过 SSH 连接了,无需在 VirtualBox 界面里操作,更方便。
  3. 网络配置(关键步骤):安装完成后,关闭虚拟机。进入虚拟机设置 -> 网络。这里我推荐使用“桥接网卡”模式。在这种模式下,虚拟机会从你的家庭路由器获取一个独立的 IP 地址,就像一台真实的新设备接入了你的局域网。这样,你的 Raspberry Pi 和主机都能通过这个 IP 直接访问虚拟机内的服务。
    • 适配器类型通常选择 “Intel PRO/1000 MT 桌面” 即可,兼容性好。
    • 确保 “混杂模式” 设置为 “允许全部”。

实操心得:如果桥接模式遇到问题(例如无法获取IP),可以尝试先使用 “网络地址转换(NAT)” 模式安装系统并更新,然后再在虚拟机设置里添加第二块网卡,设置为 “仅主机(Host-Only)网络”,并在 Ubuntu 内配置静态IP。这种方式下,主机和虚拟机互通,但 Raspberry Pi 需要通过主机的端口转发才能访问虚拟机服务,稍复杂一些。

3.2 在虚拟机中安装Docker

通过 SSH 连接到你的 Ubuntu 虚拟机(命令如ssh youruser@虚拟机IP),然后执行以下命令安装 Docker。

# 1. 更新软件包索引并安装必要工具 sudo apt update sudo apt install -y apt-transport-https ca-certificates curl software-properties-common # 2. 添加 Docker 的官方 GPG 密钥和软件源 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 3. 安装 Docker 引擎 sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io # 4. 验证安装 sudo docker run hello-world

如果看到 “Hello from Docker!” 的信息,说明安装成功。

为了方便,我们通常希望非 root 用户也能直接运行docker命令,并且让 Docker 服务开机自启。

# 将当前用户加入 docker 用户组 sudo usermod -aG docker $USER # 重启 Docker 服务 sudo systemctl restart docker # 设置 Docker 开机自启 sudo systemctl enable docker

执行完usermod命令后,你需要完全退出当前 SSH 会话,然后重新登录,用户组变更才会生效。之后运行docker ps就不需要sudo了。

3.3 部署核心服务容器

我们将使用docker run命令来启动三个核心容器:Solace PubSub+ Broker, Node-RED, InfluxDB 和 Grafana。为了管理方便,我们可以创建一个docker-compose.yml文件,但为了理解每个参数的含义,我们先分开运行。

1. 启动 Solace PubSub+ 标准版容器Solace 的官方镜像比较大(约1GB),下载需要一些时间。

docker run -d \ -p 1883:1883 \ -p 8080:8080 \ -p 8008:8008 \ -p 9000:9000 \ --shm-size=2g \ --env username_admin_globalaccesslevel=admin \ --env username_admin_password=admin \ --name solace \ solace/solace-pubsub-standard

参数解析

  • -d:后台运行。
  • -p 主机端口:容器端口:端口映射。1883是 MQTT 协议默认端口,必须暴露8080是 Solace 的 Web 管理界面(SEMP)端口,8008是监控界面端口,9000是用于客户端连接的 WebSocket 端口。
  • --shm-size=2g:共享内存大小,Solace 需要较大的共享内存来保证性能。
  • --env:设置环境变量。这里设置了默认的管理员用户名和密码(均为admin),在生产环境中务必修改!
  • --name solace:给容器起个名字,方便后续管理。

2. 启动 Node-RED 容器

docker run -d \ -p 1880:1880 \ -v node_red_data:/data \ --name mynodered \ nodered/node-red
  • -p 1880:1880:Node-RED 的流编辑界面默认运行在 1880 端口。
  • -v node_red_data:/data:将容器内的/data目录挂载到 Docker 管理的名为node_red_data的卷(Volume)上。这样,你创建的流、安装的节点包都会持久化保存,即使容器被删除重建,数据也不会丢失。

3. 启动 InfluxDB 2.x 容器

docker run -d \ -p 8086:8086 \ -v influxdb2_data:/var/lib/influxdb2 \ -e DOCKER_INFLUXDB_INIT_MODE=setup \ -e DOCKER_INFLUXDB_INIT_USERNAME=admin \ -e DOCKER_INFLUXDB_INIT_PASSWORD=your_secure_password \ -e DOCKER_INFLUXDB_INIT_ORG=my-org \ -e DOCKER_INFLUXDB_INIT_BUCKET=my-bucket \ -e DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-super-secret-auth-token \ --name influxdb \ influxdb:2
  • 环境变量用于初始设置:用户名、密码、组织、存储桶(Bucket)和管理员令牌。请务必替换your_secure_passwordmy-super-secret-auth-token为强密码和令牌。

4. 启动 Grafana 容器

docker run -d \ -p 3000:3000 \ -v grafana_data:/var/lib/grafana \ --name=grafana \ grafana/grafana-enterprise
  • -p 3000:3000:Grafana 的 Web 界面运行在 3000 端口。
  • 默认登录账号密码为admin/admin,首次登录会要求修改。

启动后,你可以在浏览器中访问以下服务(将<虚拟机IP>替换为你的 Ubuntu 虚拟机 IP):

  • Solace 管理界面:http://<虚拟机IP>:8080
  • Node-RED:http://<虚拟机IP>:1880
  • InfluxDB:http://<虚拟机IP>:8086
  • Grafana:http://<虚拟机IP>:3000

4. Raspberry Pi端:传感器连接与数据发布

现在,我们把目光转向数据采集端。我使用的传感器是HC-SR04 超声波测距模块。它不仅能检测前方是否有物体(可用于人体感应),还能精确测量距离,比单纯的人体红外(PIR)传感器提供更多信息。当然,你可以根据需求替换为 DHT11/DHT22(温湿度)、MQ-2(烟雾)等任何兼容 3.3V/5V 的传感器。

4.1 硬件连接与电路原理

HC-SR04 有四个引脚:VCC(电源)、Trig(触发)、Echo(回响)、GND(地)。Raspberry Pi 的 GPIO 引脚工作电压是3.3V,而 HC-SR04 的 Echo 引脚输出是5V电平。直接连接可能会损坏树莓派的 GPIO 芯片!因此,我们需要一个简单的电平转换电路,通常使用两个电阻进行分压。

安全连接方案(分压电路)

  1. VCC-> 连接到 Raspberry Pi 的5V引脚(如物理引脚 2 或 4)。
  2. GND-> 连接到 Raspberry Pi 的GND引脚(如物理引脚 6)。
  3. Trig-> 连接到 Raspberry Pi 的任意GPIO引脚(如 GPIO23,物理引脚 16),并设置为输出模式。
  4. Echo-> 这是关键。先串联一个1kΩ电阻,再连接到 Raspberry Pi 的 GPIO 引脚(如 GPIO24,物理引脚 18)。然后,从该 GPIO 引脚处,连接一个2kΩ电阻到 GND。这样就构成了一个分压器,将 5V 信号降至约 3.3V (5V * (2k/(1k+2k)) ≈ 3.33V)。

重要提示:务必在通电前仔细检查连线。错误的电压是损坏树莓派的最常见原因。如果不确定,可以先使用面包板和杜邦线进行测试。网上也有现成的 HC-SR04 模块,其 Echo 引脚已内置分压电路,可直接连接 3.3V 系统,购买时请注意甄别。

4.2 软件环境与Python脚本

在 Raspberry Pi 上,我们使用 Python 来读取传感器数据并发布 MQTT 消息。首先确保系统已更新,并安装必要的库。

# 更新系统 sudo apt update sudo apt upgrade -y # 安装 Python3 和 pip(如果尚未安装) sudo apt install -y python3 python3-pip # 安装 GPIO 控制库和 MQTT 客户端库 sudo pip3 install RPi.GPIO paho-mqtt

接下来是核心的 Python 脚本sensor_mqtt.py。这个脚本会持续读取超声波传感器数据,并通过 MQTT 发布到 Solace Broker。

#!/usr/bin/env python3 import RPi.GPIO as GPIO import time import json import paho.mqtt.client as mqtt # ===== 配置区域 ===== # GPIO 引脚定义 (BCM 编号) TRIG_PIN = 23 ECHO_PIN = 24 # MQTT 配置 BROKER_IP = "192.168.1.100" # 替换为你的虚拟机 IP 地址 BROKER_PORT = 1883 MQTT_TOPIC = "home/room1/ultrasonic" CLIENT_ID = "raspberrypi_sensor_01" # ==================== # 初始化 GPIO GPIO.setmode(GPIO.BCM) GPIO.setup(TRIG_PIN, GPIO.OUT) GPIO.setup(ECHO_PIN, GPIO.IN) GPIO.output(TRIG_PIN, False) time.sleep(0.5) # 让传感器稳定一下 # 初始化 MQTT 客户端 client = mqtt.Client(client_id=CLIENT_ID, protocol=mqtt.MQTTv311) # 如果 Broker 需要用户名密码,在此处添加 # client.username_pw_set("username", "password") def measure_distance(): """测量一次距离,返回厘米值。如果超时或错误,返回 None。""" # 发送10us的高电平脉冲触发测距 GPIO.output(TRIG_PIN, True) time.sleep(0.00001) # 10微秒 GPIO.output(TRIG_PIN, False) pulse_start = time.time() pulse_end = time.time() # 等待 Echo 引脚变为高电平(开始接收回波) timeout_start = time.time() while GPIO.input(ECHO_PIN) == 0: pulse_start = time.time() if pulse_start - timeout_start > 0.1: # 100ms 超时 return None # 等待 Echo 引脚变为低电平(回波结束) timeout_start = time.time() while GPIO.input(ECHO_PIN) == 1: pulse_end = time.time() if pulse_end - timeout_start > 0.1: # 100ms 超时 return None # 计算声波往返时间 pulse_duration = pulse_end - pulse_start # 声速约 34300 cm/s,距离 = (时间 * 声速) / 2 distance = (pulse_duration * 34300) / 2 # 有效测距范围通常在 2cm - 400cm if 2 < distance < 400: return round(distance, 2) else: return None def on_connect(client, userdata, flags, rc): """MQTT 连接回调函数""" if rc == 0: print(f"成功连接到 MQTT Broker: {BROKER_IP}") else: print(f"连接失败,返回码: {rc}") def main(): # 连接 MQTT Broker client.on_connect = on_connect try: client.connect(BROKER_IP, BROKER_PORT, 60) client.loop_start() # 启动网络循环线程 except Exception as e: print(f"无法连接到 Broker: {e}") return print("传感器数据采集开始,按 Ctrl+C 停止...") try: while True: dist = measure_distance() timestamp = time.time() if dist is not None: # 构造 JSON 格式的消息体 payload = { "sensor_id": CLIENT_ID, "distance_cm": dist, "timestamp": timestamp, "location": "room1" } msg = json.dumps(payload) # 发布消息,QoS=1 确保至少送达一次 result = client.publish(MQTT_TOPIC, msg, qos=1) status = result.rc if status == mqtt.MQTT_ERR_SUCCESS: print(f"[已发送] 距离: {dist} cm, Topic: {MQTT_TOPIC}") else: print(f"[发送失败] 错误码: {status}") else: print("测量无效,跳过本次发布。") time.sleep(2) # 每2秒采集一次 except KeyboardInterrupt: print("\n程序被用户中断。") finally: client.loop_stop() client.disconnect() GPIO.cleanup() print("资源已清理,程序退出。") if __name__ == "__main__": main()

脚本关键点解析

  1. JSON 格式:消息体使用 JSON 格式,结构化数据便于后续处理。包含了传感器ID、测量值、时间戳和位置信息。
  2. QoS 等级client.publish(..., qos=1)设置了服务质量等级为 1。这意味着 Broker 必须确认收到消息,否则发送方会重试。这比 QoS 0(最多一次,可能丢失)更可靠,又比 QoS 2(确保只有一次,开销大)更轻量,是物联网数据采集的常用选择。
  3. 错误处理:包含了连接失败、测量超时等基本错误处理。
  4. 资源清理:在程序退出时(无论是正常结束还是被 Ctrl+C 中断),都会执行GPIO.cleanup()和 MQTT 断开连接,这是一个好习惯。

将脚本保存后,赋予执行权限并运行:chmod +x sensor_mqtt.py,然后python3 sensor_mqtt.py。如果一切正常,你应该能在终端看到数据发送成功的提示。

5. 数据流编排与可视化:Node-RED实战

当 Raspberry Pi 开始源源不断地发送数据后,我们需要在“中央处理机”(虚拟机)上接收并处理它们。Node-RED 的图形化流编排界面让这个工作变得异常简单。

5.1 配置MQTT输入与数据解析

  1. 打开浏览器,访问http://<虚拟机IP>:1880,进入 Node-RED 编辑器。
  2. 从左侧节点面板的 “network” 分类中,拖拽一个mqtt in节点到工作区。
  3. 双击该节点进行配置。
    • Server:点击右侧铅笔图标,添加一个新的 MQTT Broker 连接。
    • 在连接配置中,“Server” 填写localhost(因为 Node-RED 和 Solace 运行在同一台虚拟机上),端口1883。如果 Solace 设置了用户名密码,在此处填写。
    • Topic:填写 Raspberry Pi 发布消息的 Topic,即home/room1/ultrasonic。你可以使用+#通配符来订阅多个主题,例如home/+/ultrasonic可以订阅所有房间的超声波传感器。
    • QoS:设置为 1,与发布端匹配。
    • Output:选择 “a parsed JSON object”,这样 Node-RED 会自动将接收到的 JSON 字符串转换为 JavaScript 对象,方便后续节点直接使用msg.payload.distance_cm这样的方式访问数据。
  4. 点击 “Done” 保存。

5.2 将数据存入InfluxDB

我们需要将接收到的时序数据持久化存储,以便历史查询和趋势分析。

  1. 首先,确保左侧节点面板有 “storage” 分类下的influxdb out节点。如果没有,需要安装。点击右上角菜单 -> “Manage palette” -> “Install”,搜索node-red-contrib-influxdb并安装。
  2. 拖拽一个influxdb out节点到工作区,并将其连接到mqtt in节点之后。
  3. 双击influxdb out节点进行配置。
    • Server:点击铅笔图标添加 InfluxDB 2.x 服务器。
    • Version:选择 “2.x”。
    • URLhttp://localhost:8086(InfluxDB 容器端口)。
    • Token:填入你在启动 InfluxDB 容器时设置的DOCKER_INFLUXDB_INIT_ADMIN_TOKEN(即my-super-secret-auth-token)。
    • Organization:填入my-org
    • Bucket:填入my-bucket
    • Measurement:这是 InfluxDB 中的表名,可以填写sensor_distance
    • 在 “Data” 标签页下,我们需要定义如何将msg.payload映射到 InfluxDB 的数据点(Point)。
      • Tags:标签,用于索引和分组。我们可以添加sensor_idlocation。值可以设置为msg.payload.sensor_idmsg.payload.location
      • Fields:字段,即实际的测量值。添加一个字段,名称value,值设置为msg.payload.distance_cm。类型选择float
      • Timestamp:时间戳。设置为msg.payload.timestamp,并选择单位为seconds
  4. 点击 “Done” 保存。

5.3 创建简单的实时仪表盘

在数据存入数据库的同时,我们也可以创建一个简单的实时监视界面。

  1. 从左侧 “dashboard” 分类(需安装node-red-dashboard节点包)拖拽一个chart节点到工作区。
  2. 将其连接到mqtt in节点之后(与 influxdb out 节点并联)。
  3. 双击chart节点配置。
    • Group:创建一个新的 Dashboard 分组,例如 “Room Monitor”。
    • Size:选择图表大小,例如6x3
    • Label:设置为 “Room1 Distance”。
    • 在 “Series” 标签页,添加一个序列,其值设置为msg.payload.distance_cm
  4. 点击右上角的Deploy按钮,部署这个流。
  5. 现在,你可以访问http://<虚拟机IP>:1880/ui来查看这个简单的实时图表了。

5.4 添加简单的逻辑判断

我们可以让 Node-RED 做一些简单的智能判断。例如,当检测到距离小于 50cm(假设有人靠近)时,发送一个通知。

  1. 拖拽一个switch节点(在 “function” 分类下)到工作区,放在mqtt in节点之后。
  2. 配置switch节点:添加一条规则,属性为msg.payload.distance_cm,操作符<,值50
  3. 拖拽一个change节点,连接到switch节点的第一条输出(即条件满足时)。
  4. 配置change节点:将msg.payload设置为一个告警文本,例如{"alert": "Someone is approaching in room1!", "distance": msg.payload.distance_cm}
  5. 你可以再连接一个debug节点来在侧边栏输出这个告警,或者连接一个email节点(需配置)来发送邮件通知。

至此,一个从数据采集、传输、处理到存储、可视化的完整链路就搭建完成了。你的 Node-RED 流看起来应该像一条有多个分支的管道,数据从 MQTT 节点流入,然后根据你的逻辑被复制、转换、存储和展示。

6. 高级配置与生产环境考量

当原型跑通,考虑将其用于更严肃的场景或部署到生产环境时,有几个关键方面需要加强。

6.1 安全性加固

目前的配置使用了默认密码和令牌,这在互联网上是极其危险的。必须进行加固。

  1. 修改 Solace 默认密码
    • 登录 Solace 管理界面 (http://<虚拟机IP>:8080),用户名密码admin/admin
    • 进入 “Manage” -> “Users” -> “admin” 用户,点击编辑,修改密码。同时,考虑创建一个专属的、权限受限的 MQTT 客户端用户,而不是一直使用 admin。
  2. 修改 InfluxDB 令牌:在 InfluxDB 界面 (http://<虚拟机IP>:8086) 中,使用初始令牌登录后,立即生成一个新的、具有特定权限(例如只对my-bucket有读写权)的令牌,并在 Node-RED 配置中更新它。禁用或删除初始的管理员令牌。
  3. 使用 TLS/SSL 加密 MQTT 通信:在公网或不可信网络环境中,明文传输的 MQTT 消息可能被窃听。Solace 支持 MQTT over TLS (端口 8883)。你需要为 Solace 配置 SSL 证书,并在 Raspberry Pi 的 Python 客户端中,修改连接代码,指定 CA 证书路径。
    client.tls_set(ca_certs="/path/to/ca.crt") # 设置CA证书 client.connect(BROKER_IP, 8883, 60) # 连接端口改为8883
  4. 防火墙与端口管理:在虚拟机或宿主机防火墙中,只开放必要的端口(如 1883, 8080, 3000),并对来源 IP 进行限制(如果可能)。例如,可以设置只有家庭局域网 IP 段才能访问 1883 端口。

6.2 可靠性提升

  1. MQTT 客户端持久会话与遗嘱消息
    • 在 Raspberry Pi 的 Python 客户端中,创建 Client 时可以设置clean_session=False。这样,如果客户端意外断开,Broker 会为其保留订阅和未确认的 QoS 1/2 消息,待其重连后下发。
    • 设置遗嘱消息(Last Will):client.will_set(MQTT_TOPIC + “/status”, payload=”offline”, qos=1, retain=True)。这样当客户端非正常断开时,Broker 会自动发布一条“离线”消息到指定主题,通知其他订阅者。
  2. 数据缓冲与重试:在 Raspberry Pi 上,可以考虑使用像paho-mqtt库的loop_forever()自动重连机制,并在网络中断时,将数据临时缓存到本地文件或轻量级数据库(如 SQLite)中,待网络恢复后重新发布。
  3. 使用 Docker Compose 管理服务:创建docker-compose.yml文件来定义所有服务(Solace, Node-RED, InfluxDB, Grafana),并配置重启策略restart: unless-stopped。这样当虚拟机重启或容器意外退出时,所有服务会自动重新启动。
    version: '3.8' services: solace: image: solace/solace-pubsub-standard container_name: solace ports: - "1883:1883" - "8080:8080" # ... 其他配置 restart: unless-stopped nodered: image: nodered/node-red container_name: nodered ports: - "1880:1880" volumes: - node_red_data:/data restart: unless-stopped # ... 定义其他服务及 volumes
    启动只需一句命令:docker-compose up -d

6.3 扩展性与维护

  1. 多传感器与主题规划:当有多个传感器时,设计清晰的 MQTT 主题结构至关重要。例如:
    • home/groundfloor/livingroom/temperature
    • home/groundfloor/kitchen/motion
    • home/firstfloor/bedroom/humidity这种层次结构便于订阅(如home/groundfloor/#订阅整个一楼的数据)和权限管理。
  2. 数据下行(命令与控制):目前是单向数据上传。你完全可以利用 MQTT 实现下行控制。例如,从 Node-RED 或手机 App 向主题home/room1/light/switch发布ON消息,Raspberry Pi 订阅该主题并控制一个继电器开关电灯。
  3. 监控与告警:利用 Grafana 的告警功能,可以设置当某个传感器数据超过阈值时,发送邮件、Slack 或钉钉通知。InfluxDB 本身也支持任务(Task)和检查(Check)来监控数据。

7. 常见问题与故障排查实录

在实际搭建和运行过程中,你几乎一定会遇到一些问题。下面是我踩过的一些坑和解决方法。

7.1 网络连接类问题

问题1:Raspberry Pi 无法连接到虚拟机上的 MQTT Broker (Connection Refused / Timeout)。

  • 排查步骤
    1. 检查IP和端口:在 Raspberry Pi 上使用ping <虚拟机IP>测试网络连通性。再用telnet <虚拟机IP> 1883nc -zv <虚拟机IP> 1883测试 1883 端口是否开放。如果命令未找到,安装telnetnetcat
    2. 检查防火墙:在 Ubuntu 虚拟机上,运行sudo ufw status查看防火墙状态。如果启用,需要放行端口:sudo ufw allow 1883/tcp。同样检查宿主机(Windows/Mac)的防火墙。
    3. 检查 Docker 端口映射:在虚拟机上运行docker ps查看 Solace 容器是否运行,并确认0.0.0.0:1883->1883/tcp这样的映射存在。运行docker logs solace查看容器日志是否有错误。
    4. 检查 VirtualBox 网络设置:确保虚拟机网络适配器是“桥接模式”,并且能正确获取到与 Raspberry Pi 同网段的 IP 地址。可以在虚拟机内运行ip addr查看。

问题2:Node-RED 无法连接本地 InfluxDB。

  • 现象:InfluxDB out 节点显示 “Error: connect ECONNREFUSED 127.0.0.1:8086”。
  • 原因:在 Docker 容器内,localhost127.0.0.1指向容器自身,而不是宿主机。
  • 解决:在 Node-RED 的 InfluxDB 服务器配置中,URL 不能填localhost:8086,而应填写宿主机的Docker 网关 IP。通常为172.17.0.1。可以在 Node-RED 容器内运行ip route | grep default查看。更可靠的方法是使用 Docker 的 internal DNS,如果服务在同一个docker-compose.yml中定义,可以直接使用服务名作为主机名,例如http://influxdb:8086

7.2 数据流类问题

问题3:Node-RED 能收到 MQTT 消息,但 InfluxDB 没有数据。

  • 排查步骤
    1. 检查 InfluxDB out 节点配置:确认 Organization, Bucket, Token 完全正确,尤其是 Token 需要有写入目标 Bucket 的权限。
    2. 查看 Node-RED 调试信息:在 InfluxDB out 节点后连接一个debug节点,部署后查看侧边栏的 Debug 标签页,看是否有错误信息输出。
    3. 直接查询 InfluxDB:在 InfluxDB 的数据浏览器 (http://<IP>:8086) 中,手动切换到正确的 Bucket,运行查询from(bucket: "my-bucket") |> range(start: -1h),看是否有数据。
    4. 检查时间戳:如果时间戳格式错误(比如是字符串而非数字),可能导致写入失败。确保msg.payload.timestamp是 Unix 时间戳(秒或毫秒数)。

问题4:Grafana 连接 InfluxDB 失败,提示 “Bad Request” 或 “401 Unauthorized”。

  • 解决:在 Grafana 添加数据源时,确保:
    • URL正确:http://influxdb:8086(同 Docker 网络) 或http://<虚拟机IP>:8086
    • Auth部分:选择 “With Credentials”,并填写正确的Token(具有该 Bucket 读取权限的 Token,不一定非要用管理员 Token)。
    • InfluxDB Details:正确填写 Organization 和 Default Bucket。

7.3 Raspberry Pi 与传感器类问题

问题5:超声波传感器读数不稳定或总是返回超大/超小值。

  • 原因:可能是电源干扰、声波反射面不理想、或 GPIO 时序问题。
  • 解决
    1. 电源滤波:在传感器的 VCC 和 GND 之间并联一个 10uF 或 100uF 的电解电容,以稳定电源。
    2. 多次测量取平均:修改 Python 脚本中的measure_distance()函数,连续测量 5 次,去掉最大最小值后取平均,能有效滤除偶然误差。
    3. 检查物理连接:确保 Trig 和 Echo 引脚连接牢固,分压电阻值正确。
    4. 增加测量间隔:两次测量之间留出足够时间(如代码中的time.sleep(2)),避免声波干扰。

问题6:Python 脚本报错ModuleNotFoundError: No module named 'RPi.GPIO'

  • 解决:确认你是在 Raspberry Pi 上运行,并且使用了pip3安装。有时需要安装开发包:sudo apt install -y python3-dev,然后重新安装:sudo pip3 install RPi.GPIO

这套基于 Raspberry Pi、MQTT 和 Docker 生态的方案,其魅力在于将复杂的物联网系统分解为一个个职责清晰、易于理解和替换的模块。从最初被一根数据线困住,到如今可以在家中任何位置添加传感器,数据自动汇聚到中央看板,整个过程充满了从无到有搭建系统的乐趣和成就感。当你看到 Grafana 仪表盘上那条随着有人走过而跳动的曲线时,你会真切地感受到,物理世界和数字世界之间的那道墙,已经被你亲手打通了。

http://www.rkmt.cn/news/1442915.html

相关文章:

  • 从零开始:B站缓存视频合并工具的完整使用旅程 [特殊字符]
  • 91.开源跨平台刷机Bash脚本!自动识别设备+固件校验+分区刷写全自动化
  • 武汉圣擎航空:蒙特哥贝机票全攻略与GEO营销实战 - 土星买买买
  • Arduino红外传感与舵机控制:打造万圣节自动糖果分发器
  • 抖音无水印下载终极指南:3个超简单步骤搞定视频批量保存
  • 物理层 → 数据链路层 → 网络层 → 传输层 → 会话层 → 表示层 → 应用层
  • Java课程
  • Linux CIFSwitch 内核新漏洞允许攻击者获得 root 权限
  • 当AI开始驱动工作:从落地到实践的完整思考
  • 上海小程序开发服务商综合能力排行:帮你找到对的外包技术团队 - 新闻快传
  • 2026年GEO监测工具怎么选?一张表看清5大主流产品
  • 1M上下文 vs RAG:理性分析为什么Agent时代两者必须共存
  • Sora 2文件体积失控真相(2024最新v2.1.3内核解析):帧率/分辨率/比特率三维协同压缩法
  • 厦门钻戒闲置焕新,收的顶钻石回收小众彩钻也能高价变现 - 奢侈品回收测评
  • 2026烟台漏水检测靠谱公司选哪家-鑫辉漏水检测-全城上门检测服务 - 速递信息
  • 工业现场实录:CX5130+松下伺服调试,那些手册上没写的实用技巧
  • AI正“卷“疯了!不会用AI的人,正在被淘汰?高手都懂的4个提效秘诀,让你弯道超车!
  • Visual C++运行库:彻底解决Windows应用程序兼容性问题的完整指南
  • 中山B2B工厂的获客焦虑:当采购商开始用抖音找供应商 - 速递信息
  • Sora 2录制失败率骤降87%的秘密:基于217场真实虚拟发布会复盘的4类隐性崩溃场景及热修复补丁包
  • SDD(Spec-Driven Development)规范驱动开发规范
  • 2026年国内主流304不锈钢丝绳厂家实力排行盘点 - 奔跑123
  • 【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (5)--- 异步处理
  • 【Sora 2交互设计终极指南】:20年UX专家亲测的5大颠覆性交互范式与落地避坑清单
  • Hermes Agent 实战全解析:从安装避坑到成本控制,附 AI Skills 零代码落地方案
  • 2026 本地企业 AI 搜索优化排行榜:从城市词到推荐答案的增长路径 - 企业服务研究所
  • MAA明日方舟自动化助手:5个步骤实现游戏效率革命
  • 终极指南:3个秘诀让你成为虚幻引擎游戏修改大师
  • 别再瞎试了!用Quartus Prime的Design Space Explorer II,5分钟搞定FPGA时序优化种子筛选
  • WebRTC回声消除定位方法