☰
树莓派打造Edge IoT网关:基于MQTT与Node-RED的无人值守实践
2026/9/28 19:01:32 网站建设 项目流程

最近把一台树莓派打造成了代号为 PI KRITISH 的 Edge IoT Gateway,整个系统采用 Headless 无头模式运行,消息中枢走 MQTT 协议,业务编排全部交给 Node-RED。设备塞在弱电箱里已经稳定跑了两个多月,没有接显示器,没有键盘鼠标,重启全靠 SSH,状态监控走 Web 面板。这篇文章把从裸机到上线的完整过程、配置细节、参数计算和踩过的坑都整理了出来,目标是让零基础的朋友照着也能复现一台属于自己的边缘网关。

为什么要在边缘跑一个网关?因为家里或工位上传感器越来越多:温湿度、门磁、PM2.5、能耗表,每个都走云平台既费流量又延迟高,断网就全瞎。在边缘侧先把数据聚合、过滤、转成标准格式,再按需同步上云,这是 IoT 架构里很成熟的模式。PI KRITISH 干的就是这件事:MQTT 负责设备接入和消息路由,Node-RED 负责把消息变成业务逻辑,比如阈值告警、数据清洗、联动控制。这篇文章适合三类人:刚接触 MQTT 协议想动手实践的;想用树莓派做无人值守网关的;以及被 Node-RED 和 MQTT 之间各种暗坑折磨过的开发者。

1. 网关整体架构与选型思路

1.1 Edge IoT Gateway 到底解决了什么问题

先不急着上配置。我最初犯过一个错误,以为网关就是“设备数据转发到服务器”。实际跑了之后才明白,边缘网关的真正价值在三个地方。

第一个是数据聚合。传感器设备种类太杂,有的走 Modbus,有的走私有二进制协议,有的直接 HTTP 轮询。网关相当于一个翻译层,把这些乱七八糟的协议统一成 MQTT 消息,再交给上层平台。没有这一层,云端要面对的是几十种单品协议,对接成本会高到离谱。

第二个是本地自主决策。跟云端断开时,报警、联动、缓存都不能停。PI KRITISH 在 Node-RED 里跑了几个本地规则,比如“当温湿度超过阈值时从 MQTT 发出控制指令”,即使断网这条链路仍然工作。家里宽带或路由器出问题的时候,这套本地逻辑成了唯一还能干活的部分。

第三个是带宽与延迟控制。在一台几块钱成本的单片机上频繁上报所有原始数据很不划算,而且无意义的重复上报会占用路由器带宽。网关侧做过滤和聚合,只把变化量较大的数据上云,这个思路能省下大量流量,也让云端存储和展示的压力小很多。

注意:边缘网关不是“把数据搬上云”的中转器,而是“在数据源头处理掉一部分事情”的小型服务器。设计阶段就把这三个职责想清楚,后面才不会把网关做成一个只会透传的管道。

1.2 为什么选树莓派加 Headless 模式

树莓派在这个项目里最大的优势不是性能,而是生态。ARM 架构下所有需要的软件都有现成的包:Mosquitto、Node-RED、SQLite 以及各种传感器驱动库,apt 装完就能跑。如果换成 x86 小主机,性能更强但功耗和体积不合适;如果换成 ESP32 之类 MCU,性能和内存跑 Node-RED 又不现实。树莓派正好卡在中间,既能跑完整 Linux,功耗又控制得住。

Headless 无头模式的核心动机是可靠性和低功耗。没有显示器和桌面环境,系统里少跑一堆显卡驱动和窗口管理器,内存占用直接少了 300~400MB,整机功耗也降到 3~5W 左右。而且少了一个最不稳定的环节:桌面环境。我在生产环境见过太多因为桌面卡死导致整机失去响应的情况,无头之后稳定性提升非常明显。

维护上也更符合“基础设施”的定位。设备装进弱电箱之后,日常维护全部通过 SSH 完成,刷系统、改配置、查日志都是命令行的事。Node-RED 自带 Web 编辑器,浏览器打开就行,不需要在树莓派上接显示器。这套模式下“显示器”和“键盘”这两个硬件依赖被彻底拿掉了,设备可以藏到任何角落。

1.3 系统架构总览

PI KRITISH 的整体数据流是这样的:

传感器设备通过 WiFi、串口或以太网接入,先把数据发布到 Mosquitto 这个 MQTT Broker 上;Node-RED 订阅对应主题,完成格式解析、规则判断、联动控制,再把需要持久化的数据写入 SQLite,需要上云的数据按策略转发出去。

链路中最核心的两个服务分别是 MQTT Broker 与 Node-RED:

  • MQTT Broker 使用 Eclipse Mosquitto,负责所有主题的订阅与发布,是消息中枢。
  • Node-RED 作为边缘计算引擎,承接数据解析、规则判断、联动控制以及上云转发。

两个进程都注册成 systemd 服务,开机自启、崩溃自动拉起,这是网关“无人值守”的基础。后面所有章节都围绕这张架构图展开:先把系统底子打牢,再做消息层,再做业务编排层,最后加稳定性保障。

2. 树莓派 Headless 初始化与系统精简

2.1 无显示器烧录与预配置

树莓派官方烧录工具在写入镜像之前,会弹出“启用 SSH、设置用户名密码、配置 WiFi”的选项。Headless 开局全靠这个,不需要再手动创建 ssh 文件了。我这里用的是 Raspberry Pi OS Lite(64-bit),没有任何桌面组件。

烧录时我做了这些配置:

  • 主机名:pikritish
  • 用户名:pi
  • 开启 SSH,并使用密钥登录
  • 配置 2.4GHz WiFi(弱电箱里的实际网络环境)

这里有个细节:如果路由器开了访客网络隔离,IoT 设备可能无法互相访问,烧录前要确认设备跟手机连的是同一个网段。烧录完成后插电,等 1~2 分钟,从路由器后台找到 pikritish 的 IP,就能 SSH 登录了。

登录后第一件事是把系统更新到最新:

sudo apt update && sudo apt full-upgrade -y sudo rpi-eeprom-update

EEPROM 固件更新容易被忽略,但它决定树莓派的稳定性和启动兼容性。尤其是从旧系统迁移过来的存储卡,不更新固件可能出现随机重启的问题。

2.2 SSH 安全加固与日常管理

开机后的安全加固不能偷懒。我用的是 ed25519 密钥,并在 /etc/ssh/sshd_config 里做了以下配置:

PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes

改完记得重启 SSH 服务并开一个新终端验证密钥登录,确认没问题再关掉当前连接,避免把自己锁在外面。这个教训来自一次真实事故:我改完配置后直接关了当前会话,结果发现新终端连不上,只能跑一趟弱电箱插显示器和键盘救场。

如果局域网内有多台设备,建议给树莓派设置静态 DHCP 保留地址,避免重启后 IP 变动导致 SSH 连不上。这个坑我踩过:设备运行三天后突然登不上,排查发现是路由器分配的地址变了。固定 IP 之后,所有脚本、Webhook、MQTT 客户端配置都不用跟着改。

日常工具我只装了 build-essential、git、htop、vim 这些基础包,不装桌面和图形化监控软件。系统越精简,攻击面越小,出问题的可能性也越低。

2.3 网络稳定性调优

边缘网关的网络稳定性直接影响整个链路。我做了三个优化。

第一,关闭 WiFi 节能模式。树莓派的无线网卡默认可能有省电策略,会导致间歇性丢包。我在 /etc/rc.local 里加了一行:

iw dev wlan0 set power_save off

实测之后,ping 网关的延迟从偶尔 200ms 降到稳定在 2~3ms。如果用的是 systemd,也可以写成 systemd 服务,rc.local 在部分系统上默认不执行,需要先确认。

第二,修改 DHCP 触发策略。如果用的是 systemd-networkd,注意配置 DHCP 的租约持久化,避免路由器重启后网关拿不到 IP。我遇到过路由器断电重启、网关 IP 变成 169.254 开头的问题,排查了半小时才发现是 DHCP 租约丢失。

第三,如果条件允许,直接用有线以太网。WiFi 信号再稳定也不如有线可靠。我的最终方案是给树莓派插了一根超五类网线,WiFi 只作为备用通道。传感器数据链路对丢包很敏感,有线是最省心的选择。

3. MQTT 消息总线:Mosquitto 部署与配置

3.1 MQTT 协议核心概念速览

这里写给刚开始接触 MQTT 的朋友。MQTT 是基于发布/订阅模型的轻量级消息协议,它和 HTTP 最大的不同是:发送方不用知道谁在接收,只把消息发到“主题”上;接收方也不直接跟发送方打交道,只看订阅哪些主题。就像小区公告栏:发通知的人不用知道每家每户是谁,贴在公告栏(Broker)上,有兴趣的住户自己来看。

MQTT 的三个核心概念需要理解透彻:

  • Broker:消息中转站,也就是服务器,我们这里指的是 Mosquitto。
  • Topic:带层级结构的话题名,比如 devices/kitchen/temperature。可以用加号(+)匹配单层,用井号(#)匹配多层。
  • QoS:消息交付等级。0 表示最多一次,1 表示至少一次,2 表示恰好一次。等级越高开销越大,实际使用中大部分场景选 0 或 1 就够了。

还有两个容易被忽略的机制:KeepAlive 保活心跳。客户端与 Broker 之间通过心跳维持长连接,Broker 才能在设备突然断网时快速感知异常;遗嘱消息(Last Will)可以在设备异常离线时向指定主题发布一条预设消息,这对状态监控非常重要。

3.2 安装 Mosquitto 与基础配置

在树莓派上安装 Mosquitto 非常省事:

sudo apt update sudo apt install -y mosquitto mosquitto-clients

装好之后默认监听 1883 端口。我在 /etc/mosquitto/conf.d/local.conf 里加了以下配置:

listener 1883 allow_anonymous false password_file /etc/mosquitto/passwd persistence true persistence_location /var/lib/mosquitto/

其中 allow_anonymous false 是安全底线。不开放匿名访问,意味着没有账号密码的设备无法接入,可以挡住绝大多数局域网内的误连设备。persistence true 开启持久化,Broker 重启后可以恢复内存中的 QoS 1 和 QoS 2 消息状态,尽量避免停机丢消息。

创建用户名密码用:

sudo mosquitto_passwd -c /etc/mosquitto/passwd pi

注意 -c 参数会覆盖整个密码文件。如果之后再添加其他用户,必须去掉 -c,否则之前创建的用户全没了。这是命令行工具的一个小陷阱,我朋友就踩过,一次手误把所有设备账号都清了。

3.3 ACL 访问控制与权限隔离

光有密码不够,如果所有设备都共用同一个账号,一台设备被攻破就意味着整个消息总线暴露。需要 ACL 做权限隔离。我建了一个 acl 文件:

user admin topic readwrite # user sensor_bridge topic write devices/# topic read commands/# user node-red topic read devices/# topic readwrite commands/# topic readwrite local/#

然后在 Mosquitto 配置里指定 acl_file 路径。这里的权限模型是按角色拆的:admin 拥有全部权限,适合调试;sensor_bridge 只能写入设备数据、读取指令,这样传感器数据被篡改也不会影响控制链路;node-red 账号可以读取所有设备数据,也能下发指令和读写本地内部主题。

权限隔离的价值在于把故障爆炸半径缩小:即使某个接入设备异常,能影响的范围也被限制住了。生产环境如果设备种类更多,还可以进一步按设备 ID 或者机房维度拆分 ACL。

3.4 验证订阅与发布全流程

Mosquitto 装好后,验证闭环用的是 mosquitto_sub 和 mosquitto_pub 两个命令行工具。

在终端 A 订阅:

mosquitto_sub -h localhost -p 1883 -u admin -P 你的密码 -t 'devices/#' -v

在终端 B 发布:

mosquitto_pub -h localhost -p 1883 -u admin -P 你的密码 -t 'devices/kitchen/temperature' -m '{"value":23.5}'

如果终端 A 里立刻打印出这行 JSON,说明 Broker 的发布订阅闭环已经通了。这个验证很重要,后续 Node-RED 接入时用的账号和主题都依赖这一步确认方向无误。

我在踩坑中发现,主题命名规范比想象中重要。建议用 devices/ 和 commands/ 分开业务数据与控制指令,再往下一层按空间或设备类型细分。这个习惯在后端的动态订阅和多级主题设计里能少踩很多坑,因为通配符匹配建立在清晰的层级结构之上。

4. Node-RED 边缘编排:从订阅到自动化逻辑

4.1 安装 Node-RED 与安全认证

Node-RED 官方推荐直接跑安装脚本,也可以从 npm 安装:

sudo npm install -g --unsafe-perm node-red

树莓派官方源里也有包,但版本往往偏老,我建议直接用 npm 装最新版。装完先别急着登录 Web 面板,第一步要设置管理员认证。编辑 ~/.node-red/settings.js,生成密码哈希:

node -e "console.log(require('bcryptjs').hashSync('你的密码', 8))"

把生成的哈希填入 adminAuth 配置段。如果不做这一步,局域网内任何能访问 1880 端口的人都可以直接修改你的流,这对边缘网关来说是不可接受的。

Node-RED 默认监听所有网卡接口。如果只打算内网访问,可以把它绑定到内网 IP 或者 localhost,再用 SSH 隧道远程访问。我个人更推荐 SSH 隧道方案,这样 Web 面板不需要直接暴露到局域网。

4.2 核心流实战:订阅、解析、阈值判断、发布

我以一个室内温湿度监控和越界告警流为例,拆解整个链路。

这个流从三个节点开始:

  1. MQTT In 节点,Broker 指向 Mosquitto,主题 devices/kitchen/#
  2. JSON 解析节点,把负载转成 JavaScript 对象
  3. Function 节点,做规则判断,比如温度大于 28 度时输出告警消息

Function 节点里的代码很简单:

let msg = {...msg}; if (msg.payload.temp > 28) { msg.payload = { type: 'alert', level: 'high_temperature', room: 'kitchen', value: msg.payload.temp }; return msg; } return null;

返回 null 表示消息不向后传递,这是一个很实用的技巧。再往下接一个 MQTT Out 节点,发布到 commands/actuator。这样温度超过阈值时,Node-RED 自动发一条控制指令到执行器,全程不经过云平台。

告警这类消息我设成 QoS 1,保证至少送达一次。但要注意,QoS 1 可能产生重复消息,执行器端必须做幂等处理,同一个指令重复执行不能产生二次动作。这个细节在本地自动化里尤其容易被忽略。

4.3 数据调理与格式转换

IoT 场景下,数据并不天然干净。我遇到过传感器发过来的是华氏度、湿度带偏移、偶尔还有空值的情况,这些都在 Node-RED 里做统一调理。

我常用的节点组合是 Change、Function 和 Switch。Change 节点适合修改字段值和类型,Function 节点做单位换算和非法值剔除,Switch 节点按条件分流。下面这段是典型的换算逻辑:

if (typeof msg.payload.f_temp === 'number') { msg.payload.c_temp = Math.round((msg.payload.f_temp - 32) * 5 / 9 * 10) / 10; delete msg.payload.f_temp; } if (msg.payload.humidity === null || isNaN(msg.payload.humidity)) { return null; } msg.payload.humidity = Math.round(msg.payload.humidity * 10) / 10;

这段逻辑就是把“边缘计算”四个字落在实处:数据在边缘先变成可用的标准格式,再决定是否上云。云端拿到的永远是干净的、已经过初步加工的数据。

4.4 Node-RED 流的备份与版本管理

很多人把 Node-RED 流当成一次性配置,改完就不管了。这是一个大坑。流本身是一份 JSON,我强烈建议把它纳入 git 版本管理。

我在 ~/.node-red/ 下建了一个项目目录,每次改完流就用菜单里的 Export 功能导出 JSON,保存到 git 仓库并提交。有一次我改流时误删了一个核心节点,还没记住原来结构,如果没有 git 备份,整个业务逻辑就丢了。

另外,Node-RED 的流在部署时是整体替换的,如果当前运行的流和新部署的流有冲突,一部分节点会先停止再启动,这个过程中可能会出现短暂的消息丢失。所以大的改动我习惯先导出一份备份再部署,给自己留一条退路。

5. 网关长期运行的稳定性加固

5.1 systemd 托管服务与开机自启

边缘网关不需要人按电源键,必须开机自己把服务拉起来。装完 Mosquitto 和 Node-RED,我用 systemd 托管它们。

Mosquitto 的 apt 包装好就有系统服务,直接:

sudo systemctl enable mosquitto sudo systemctl start mosquitto

Node-RED 官方提供了一个 node-red.service 模板,放在 /lib/systemd/system/ 下后执行 enable。核心配置就三行:

[Service] ExecStart=/usr/bin/node-red Restart=on-failure User=pi WorkingDirectory=/home/pi Environment=NODE_RED_HOME=/home/pi/.node-red

Restart=on-failure 是无头模式的关键配置。进程崩了自动拉起,看似不起眼,但在没人维护的边缘场景里能救命。我还加了 RestartSec=10,给系统一点缓冲时间,避免崩溃后立即重启导致循环崩溃。

5.2 断电自恢复与看门狗

树莓派没有硬件看门狗,但可以启用内核的 watchdog 模块。在 /boot/config.txt 里加一行 dtoverlay=watchdog,然后安装 watchdog 服务:

sudo apt install watchdog

在 /etc/watchdog.conf 里把 watchdog-device = /dev/watchdog 打开。系统 hang 住时,硬件看门狗会强制重启整机。这个机制对无人值守设备来说相当于最后一道保险。

我实测过完整的断电恢复流程:拔电两周后重新上电,几分钟内 SSH 可进、Mosquitto 在线、Node-RED 自动拉起所有订阅,整体恢复耗时不到 3 分钟。这种“插电即活”的体验,才是边缘网关该有的表现。

5.3 本地数据落盘与轻量存储

如果网关只做转发,价值会打折扣。我给 PI KRITISH 加了 SQLite,用来记录最近的传感器历史和告警事件。

我用的节点是 node-red-contrib-sqlite,也可以直接在 Function 节点里用 better-sqlite3 这类同步库。日志表结构简单直接:

CREATE TABLE IF NOT EXISTS sensor_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, topic TEXT NOT NULL, payload TEXT NOT NULL, ts DATETIME DEFAULT CURRENT_TIMESTAMP );

写库时有两点注意。第一,插入操作要快速返回,不能在 Function 节点里长时间占用事件循环。第二,定期清理过期数据。我的策略是保留 7 天,用 Node-RED 的定时触发节点每天凌晨删一次旧数据,避免存储卡被日志塞满。

建议:树莓派用的是 SD 卡,频繁写入会缩短寿命。SQLite 日志最好放在内存文件系统(/tmp)或外接 SSD 上,定期同步到磁盘。别把高频率传感器数据直接写到 SD 卡,一张卡撑不了一年。

5.4 网络中断时的本地自治

我在前面强调过,网关的价值是本地自治。实际实现中,上云节点断线时不能影响本地规则。

Node-RED 里有两种做法。第一种是在 MQTT Out 节点里观察 Broker 连接状态,连接失败时把消息切换到本地分支。第二种是把上云动作封成独立分支,只有云端 MQTT 节点在线时才执行发送,否则把数据写入 SQLite 的 offline_buffer 表。

我是在一次断网事故后认真做这套逻辑的:当时家里的路由器重启了大约 10 分钟,本地上云分支因为断线疯狂堆积,把 Node-RED 的内存打满了。后来加上状态判断和缓冲机制,问题才彻底解决。本地自治不只是一句口号,它是边缘网关在真实环境里区别于普通转发服务的关键能力。

6. 常见问题与排查技巧实录

6.1 SSH 连不上与 IP 漂移

Headless 设备突然连不上,这是被问得最多的问题。我的排查顺序是:先看路由器后台有没有这个设备,如果没有,大概率是网络配置问题;如果有且 DHCP 分配正常,再看 SSH 服务是否在跑。

如果使用过程中 IP 变了,最省心的方案是启用 mDNS。树莓派 OS 默认装了 avahi-daemon,这样可以直接用pikritish.local连,不需要关心 IP 变化。我在家访问网关基本都用主机名,只有跨网段远程访问时才依赖固定 IP。

6.2 Node-RED 内存占用与单线程模型

Node-RED 本质上是 Node.js,单线程事件循环是它的基础模型。很多人以为并发是靠多线程实现的,其实 Node-RED 用的是异步事件循环。这意味着网络 IO、MQTT 收发都是异步的,可以同时处理很多请求;但如果在 Function 节点里写了 while(true) 死循环,或者同步执行大量 JSON 解析,整个流程会被阻塞。

我踩过的坑是在一个流里用同步方法做了一次巨大的数组排序,导致 CPU 单核满载、其他节点全部超时。用 top 能看到 node-red 进程占满一个核。解决办法是拆分成小片段处理,或者用 async 队列机制把负载分散开。大多数边缘业务场景根本不需要引入多线程扩展,先把单个 CPU 的使用效率优化好更实际。

6.3 MQTT 动态订阅与重复订阅问题

Node-RED 的 MQTT In 节点通常配置固定主题,但你在运行时也可以动态增加订阅。这里有个经典问题:动态订阅后如果不手动取消旧主题,会导致重复订阅越来越频繁,同一消息被处理多次。

我遇到过流里使用函数节点动态订阅devices/+/status通配主题的情况,运行一段时间后消息被重复处理。原因是每次部署都会重新初始化订阅。解决方法是动态订阅前先取消已订阅的旧主题,并且利用全局上下文判断是否已经订阅,避免重复操作。需要记住的是:MQTT 的订阅是会话级的,服务重启或重新连接后订阅状态会重建,所以动态订阅逻辑最好放在连接成功的事件回调里执行。

6.4 进程守护与 systemd 环境变量问题

有一次我用 systemd 启动 node-red 后,Web 面板能正常打开,但之前保存的所有流都不见了。折腾半天发现是 HOME 环境变量没设置,Node-RED 没有读取到 /home/pi/.node-red 目录,而是在 /root 下新建了一套空白配置。

这提醒我,在 systemd 服务文件里必须明确 User=、WorkingDirectory=、Environment= 这些参数,尤其是从命令行能跑但服务跑不起来的情况,十有八九是环境变量差异。还有一点,如果在服务里手工启动了子进程,要确保父进程退出时子进程能被正确回收,否则会成为孤儿进程,甚至引发资源泄漏。

6.5 非树莓派环境的 MQTT 服务注册插曲

有人问过“如何在 Windows 中把 MQTT 服务 zip 包设置成本地服务”,这个我在备用 PC 上弄过一次。如果是免安装的 zip 包,可以用 NSSM 或者 sc.exe 注册:

sc.exe create Mosquitto binPath= "C:\mosquitto\mosquitto.exe -c C:\mosquitto\mosquitto.conf" sc.exe config Mosquitto start= auto

这条只是备用方案。我的主力环境仍然是树莓派,但 Windows 下同样要注意配置文件的权限和服务账户,让 Broker 以普通用户身份运行,避免管理权限过大。说白了,没有 systemd 的环境就得自己想办法做守护,但核心逻辑是一样的:服务要能自启、能重启、配置和日志要分开放。

最后说点个人体会。PI KRITISH 这个项目最大的收获,不是我调通了 Mosquitto 和 Node-RED,而是理解了边缘网关这种“无人值守”基础设施的核心逻辑:系统要足够精简、服务要有自恢复能力、所有配置最好都代码化。把树莓派当作一台永远不关机的小服务器来对待,你会被迫在稳定性上花更多心思,而这份心思最终会还给你一个几乎不用管的网关。

如果你也打算搭类似的设备,我建议从最小的闭环开始:先让一台传感器把 MQTT 消息发到树莓派,Node-RED 收到后打印到调试窗口,再逐步加规则、加存储、加上云。别一开始就想着一步到位,边跑边迭代,避坑速度会快很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询