【项目实战】基于 STM32 + LoRa + Rust 网关的分布式温室监控系统
本文介绍了一种基于STM32F103和SX1278的LoRa智慧农业远程监测系统。系统采用LoRa时隙组网技术实现低功耗长距离通信,节点端完成数据采集和预处理,Rust+tokio网关负责数据转发和边缘计算。整体架构分为采集层、边缘层和云层,通过MQTT协议实现数据上行和策略下行,并利用InfluxDB存储时序数据,Grafana进行可视化展示。硬件设计包括STM32节点和树莓派网关,采用AES-
关键词:STM32F103、SX1278、LoRa 时隙组网、Rust + tokio 网关、MQTT、InfluxDB、Grafana、智慧农业、远程预警、边缘计算
一、项目概述
1.1 背景
丘陵山区分布式温室往往“点多面广”,传统 RS485/有线方案布线成本高且维护复杂;而 Wi-Fi/ZigBee 在金属骨架温室里穿透能力差,尤其是需要跨棚协同调度时几乎不可行。本项目设计了一套 LoRa 广域采集节点 + Rust 异步网关 + MQTT/InfluxDB 数据平台 的整体方案,用来实现温室的多维环境监测、阈值预警以及策略下发。
1.2 目标
- 低功耗长距离:单个 LoRa 节点供电功耗 < 80 mW,视距可达 3 km,温室内部穿透能力强。
- 边缘预处理:节点端完成一次滤波/限幅,Rust 网关负责阈值判断、降级策略和日志追踪。
- 全流程可复制:硬件 BOM、HAL 固件、Rust 网关、InfluxDB 库表和 Grafana 模板全部固化,方便快速落地。
- 双向控制:支持灌溉、补光等策略下发,节点完成 token 校验后执行。
1.3 系统功能
- 环境采集:空气温湿度、土壤温度/水势、光照、电源状态等共 8 路测点。
- LoRa 组网:时隙轮询、RSSI 反馈、AES-128 加密,支持集中器下发同步指令。
- Rust 网关:Tokio 并发接入、MQTT 推送、InfluxDB 批量写入、Redis 告警通道。
- 上位分析:Grafana 大屏、热力图、节点在线状态、短信/企业微信预警链路。
- 策略闭环:MQTT
control/+主题承载调度策略,Rust 网关转 LoRa Downlink,节点执行后上报结果。
配图参见:
architecture.png:三层架构(采集层 / 边缘层 / 云层)。dataflow.png:数据流与告警链路。cover.png:CSDN 封面,可直接作为文章头图。
二、系统架构
整体结构如下图所示:

- 数据上行:节点→LoRa→网关→MQTT/InfluxDB。
- 策略下行:调度平台→MQTT
control/#→网关转发→LoRa Downlink→节点执行。 - 告警链路:网关边缘判定阈值,写入 Redis 通道,告警服务推送短信/企业微信。
三、技术选型
| 层级 | 设备/技术 | 选择理由 |
|---|---|---|
| 采集 MCU | STM32F103C8T6 | 72 MHz、外设丰富、HAL 生态成熟,成本低易采购 |
| 无线链路 | SX1278 + PCB 天线 | LoRa 低功耗、抗干扰,433/470 MHz 频段可选 |
| 传感器 | SHT31、DS18B20、YL-69、BH1750、INA219 | 兼具 I2C/OneWire/ADC,覆盖温湿度/土壤/光照/电源监测 |
| 边缘集中器 | SX1302 (8 通道) + 树莓派 4B | 接入 100+ 节点仍稳定,Linux 环境易部署 Rust 服务 |
| 网关语言 | Rust + tokio + serde + rumqttc | 内存安全 + 异步高并发 + 零拷贝序列化 |
| 消息队列 | MQTT (EMQX) | 与现有工厂主站兼容、支持 ACL 和规则引擎 |
| 时序库 | InfluxDB 2.x | TAG/FIELD 清晰、Flux 查询、支持 Retention Policy |
| 可视化 | Grafana 10 | 大屏模板多、支持告警、可接企业微信/飞书/短信 |
四、硬件设计
4.1 节点 BOM 与引脚
| 模块 | 型号 | 接口 | 引脚 | 备注 |
|---|---|---|---|---|
| MCU | STM32F103C8T6 | - | - | 8MHz HSE、72MHz SYSCLK |
| LoRa | SX1278 + PA Booster | SPI1 | PA5/6/7, PB0(DIO0) | DIO0 → EXTI5,PA1 控制功放 |
| 温湿度 | SHT31 | I2C1 | PB6/PB7 | 1s 刷新一次 |
| 土壤温度 | DS18B20 | OneWire | PA1 | 支持多点挂载 |
| 土壤水势 | 4-20mA 变送器 | ADC1 | PA0 | 采样电阻 120 Ω |
| 光照 | BH1750 | I2C1 | PB6/PB7 | 与 SHT31 共总线 |
| 电流监测 | INA219 | I2C1 | PB6/PB7 | 监控节点功耗 |
| 执行输出 | MOSFET 驱动 | GPIO | PB12~PB15 | 控制电磁阀/补光灯 |
供电架构:24V → DCDC (LM2596) → 12V (执行器) / 5V (LoRa) / 3.3V (MCU)。LoRa 与 MCU 电源分离,串 LC 滤波并在 SX1278 旁增加 TVS,抗雷击浪涌。
4.2 网关硬件
- 树莓派 4B(4GB RAM),系统 Ubuntu Server 22.04。
- SX1302 LoRa Concentrator HAT(SPI0 接口)。
- 64GB 工业 TF 卡,外接 4G 路由或以太网。
- UPS HAT,断电可支撑 30 分钟并触发安全关机。
- 防水机箱 + 导轨电源,部署于温室中央控制柜。
五、LoRa 组网与协议
5.1 时隙规划
- 所有节点按 NodeID 统一分配 Slot(例如 1 秒帧长,8 节点共 8 个 Slot)。
- 网关先发送
POLL帧,并携带下一轮 Slot 基准时间;节点收到后 RTC 对齐。 - 节点只在自己 Slot 内上报,避免同频碰撞;若短期有紧急告警,使用
FAST_SLOT(保留时隙)。
5.2 数据帧格式
| 字段 | 长度 | 描述 |
|---|---|---|
| Header | 1B | 固定 0xAA |
| ProtoVer | 1B | 协议版本 |
| NodeID | 1B | 0~255 |
| Timestamp | 4B | UNIX 时间(秒) |
| PayloadLen | 1B | TLV 有效长度 |
| Payload | N | TLV 列表,每项 Type(1B)+Len(1B)+Value |
| RSSI Feedback | 1B | 上一次下行链路 RSSI |
| CRC16 | 2B | Modbus CRC16 |
TLV Type 定义:0x01-空气温度、0x02-空气湿度、0x03-土壤温度、0x04-光照、0x05-土壤电导、0x06-节点电流、0x10-执行器状态。
5.3 安全与降级
- AES-128 CTR 加密 Payload,密钥产线写入 OTP。
- 下行命令携带
Token+UTC,节点验证失败直接丢弃。 - 若 RSSI <-120 dBm 或连续 3 个周期未上报,节点自动切高功率档并拉长发送间隔;网关在 MQTT 中标记
status=degraded。
六、STM32 LoRa 节点固件
节点采用裸机 + HAL 实现轮询,也可根据需求移植 FreeRTOS。主循环结构:
- 采集各传感器 → 滤波 → TLV 编码。
- 根据 Slot 时间启动 SX1278 发送。
- 检测是否有 Downlink(DIO0 中断),并处理控制命令。
- 更新状态灯/本地日志。
6.1 传感器采集与 TLV 编码
typedef struct {
uint8_t type;
uint8_t len;
uint8_t bytes[8];
} tlv_t;
static uint8_t tlv_buf[96];
static uint8_t tlv_len = 0;
static uint8_t tlv_append_float(uint8_t type, float value) {
tlv_t item;
item.type = type;
item.len = sizeof(float);
memcpy(item.bytes, &value, sizeof(float));
memcpy(tlv_buf + tlv_len, &item, sizeof(item.type) + sizeof(item.len) + sizeof(float));
return sizeof(item.type) + sizeof(item.len) + sizeof(float);
}
void sensors_collect(void) {
tlv_len = 0;
float t_air = SHT31_ReadTemperature();
float h_air = SHT31_ReadHumidity();
float t_soil = DS18B20_Read();
float lux = BH1750_ReadLux();
float soil_mv = ADC_Read(ADC_CHANNEL_1);
float node_i = INA219_ReadCurrent();
tlv_len += tlv_append_float(0x01, t_air);
tlv_len += tlv_append_float(0x02, h_air);
tlv_len += tlv_append_float(0x03, t_soil);
tlv_len += tlv_append_float(0x04, lux);
tlv_len += tlv_append_float(0x05, soil_mv);
tlv_len += tlv_append_float(0x06, node_i);
}
6.2 LoRa 发送与接收
void lora_send_slot(void) {
uint8_t frame[160];
uint16_t offset = 0;
frame[offset++] = 0xAA;
frame[offset++] = PROTO_VER;
frame[offset++] = NODE_ID;
uint32_t ts = RTC_GetUnix();
memcpy(&frame[offset], &ts, 4); offset += 4;
frame[offset++] = tlv_len;
memcpy(&frame[offset], tlv_buf, tlv_len); offset += tlv_len;
frame[offset++] = last_rssi;
uint16_t crc = CRC16_Modbus(frame, offset);
frame[offset++] = crc & 0xFF;
frame[offset++] = crc >> 8;
SX1278_SetTx();
SX1278_SPIWrite(REG_FIFO, frame, offset);
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == LORA_DIO0_Pin) {
SX1278_HandleRxDone(rx_buffer, &rx_len);
if (rx_len > 0) {
process_downlink(rx_buffer, rx_len);
}
}
}
6.3 策略执行
typedef struct {
uint8_t cmd;
uint8_t token[8];
uint32_t expire;
uint8_t payload[16];
} ctrl_packet_t;
void process_downlink(uint8_t *buf, uint8_t len) {
ctrl_packet_t pkt;
if (!decrypt_and_verify(buf, len, &pkt)) return;
if (pkt.expire < RTC_GetUnix()) return; // 过期
switch (pkt.cmd) {
case CTRL_IRRIGATION_ON:
valve_set(VALVE_MAIN, true);
report_feedback(pkt.token, CTRL_OK);
break;
case CTRL_LIGHT_OFF:
light_set(false);
report_feedback(pkt.token, CTRL_OK);
break;
default:
report_feedback(pkt.token, CTRL_UNKNOWN);
}
}
七、Rust LoRa 网关
7.1 模块划分
sx1302:封装 Concentrator DMA 收发、RSSI/SNR 计算。parser:解出 TLV,转换成结构体。pipeline:Tokioselect!监听 LoRa、MQTT 控制、Redis 告警。mqtt_pub:rumqttc 异步客户端,处理 QoS1 发布与重试。influx_sink:批量写入 Influx(异步 channel 缓冲)。alarm:根据策略配置触发 Redis Pub/Sub。
7.2 主要代码
#[derive(Debug, Serialize)]
struct Telemetry {
node_id: u8,
ts: i64,
t_air: f32,
h_air: f32,
t_soil: f32,
lux: f32,
soil_mv: f32,
node_i: f32,
status: &'static str,
}
async fn handle_uplink(payload: &[u8]) -> Result<()> {
let parsed = parser::try_parse(payload)?;
let influx_line = format!(
"greenhouse,node={} t_air={},h_air={},t_soil={},lux={},soil_mv={},node_i={} {}",
parsed.node_id, parsed.t_air, parsed.h_air, parsed.t_soil,
parsed.lux, parsed.soil_mv, parsed.node_i, parsed.ts
);
mqtt_pub::publish(
format!("greenhouse/{}/telemetry", parsed.node_id),
serde_json::to_vec(&parsed)?
).await?;
influx_sink::write(influx_line).await?;
alarm::evaluate(&parsed).await;
Ok(())
}
#[tokio::main]
async fn main() -> Result<()> {
let mut lora = sx1302::open("/dev/spidev0.0", 470_300_000)?;
let mut ctrl_rx = mqtt_ctrl::subscribe("greenhouse/+/control").await?;
loop {
tokio::select! {
Ok(frame) = lora.recv_frame() => handle_uplink(&frame).await?,
Some(cmd) = ctrl_rx.recv() => {
let down = build_downlink(cmd)?;
lora.send_downlink(&down).await?;
}
}
}
}
7.3 异常处理
- Concentrator 线程意外退出 → systemd service 设置
Restart=always,并在 Rust 进程中启用tracing日志。 - Influx 批量写入失败 → 本地落盘到
/var/log/greenhouse-buffer/,定时补写。 - MQTT 断线 → rumqttc 内建断线重连,提供 backoff;必要时切换备用 Broker。
八、MQTT/InfluxDB/Grafana 配置
8.1 MQTT 主题规划
| 主题 | Payload | 说明 |
|---|---|---|
greenhouse/<node>/telemetry |
JSON(传感器数据) | QoS 1,供 Grafana / 其他系统订阅 |
greenhouse/<node>/status |
online/offline/degraded |
节点心跳与降级状态 |
greenhouse/<node>/control |
JSON(策略命令) | 调度系统下发,网关转 LoRa |
greenhouse/alarm |
JSON(告警事件) | 网关推送,供告警服务处理 |
8.2 InfluxDB Schema
- Bucket:
greenhouse - Measurement:
telemetry - Tags:
node,zone,crop - Fields:
t_air,h_air,t_soil,soil_mv,lux,node_i,status - Retention Policy:原始 7 天,
cq_downsample将 5 分钟均值写入telemetry_5m(保留 180 天)。
8.3 Grafana 看板
- 实时面板:每个温室一张卡片,展示最高/最低温度、设备状态。
- 热力图:使用
Geomap+ 自定义矢量图,直观显示不同温室差异。 - 节点健康度:Stat + Bar gauge 结合,来自
status标签。 - 告警列表:Table 组件显示最近触发的阈值事件,点击跳转日志。
- 值班人通知:Grafana Alert Rule → Webhook → Rust Alert Service → SMS/企微机器人。
九、部署与运维步骤
- 节点产测:CubeProgrammer 写入固件 → LoRa 参数校准 → 记录 NodeID 与密钥。
- 网关初始化:
- Ubuntu 安装
rustup、influxdb2-client、emqx。 systemd配置greenhouse-gateway.service,ExecStart 为./gateway --cfg config.toml。- 配置
watchdog定时检查 SX1302 进程。
- Ubuntu 安装
- 数据平台:
- InfluxDB 导入
schema/greenhouse.json。 - Grafana 导入
dashboards/greenhouse.json,替换数据源。 - EMQX 导入 ACL,用于区分只读和控制权限。
- InfluxDB 导入
- 告警服务:Rust Alert Service 监听 Redis
alarm:*,调用腾讯云 SMS/企业微信机器人。 - 日志采集:
- 节点:UART 打印接入 USB 转串口备用;必要时写入 FRAM。
- 网关:
journald+ Loki 收集。
十、调试与常见问题
| 问题 | 现象 | 排查/解决 |
|---|---|---|
| LoRa 丢包严重 | RSSI <-125 dBm、SNR <-10 dB | 检查天线连接;降低扩频因子(SF7→SF8)并增大发射功率;优化 Slot 表,避免节点集中在同一时间。 |
| MCU 随机重启 | 看门狗复位 | 开启 BOR Level 2;LoRa 和 MCU 分路供电;ADC 前端增加 TVS。 |
| Rust 网关 CPU 飙高 | influxdb 写阻塞 | 使用 influxdb2 async client + channel 批量写;出现异常时落盘缓冲。 |
| Downlink 不执行 | 节点日志显示 TOKEN_FAIL |
检查 MQTT 控制命令中 token/时间戳;确保网关没有重复使用旧 token。 |
| Grafana 数据断点 | Concentrator 卡死 | systemd watchdog 触发重启;同时在树莓派上增加风扇,保持芯片 < 60℃。 |
| 节点低电告警频繁 | INA219 读数偏差 | 在 0~2A 范围做多点标定;温度补偿;支持在 MQTT 中配置电流阈值。 |
十一、扩展方向
- 多模通信:关键温室增设 NB-IoT 或 4G DTU,LoRa 失联时自动切换。
- AI 预测:InfluxDB 数据定期导入 Rust Axum 服务,调用 ONNX Runtime 预测病虫害风险。
- 控制柜联动:网关判断策略后,通过 Modbus TCP 命令同步到 PLC,实现灌溉互锁联动。
- 一键部署脚本:使用 Ansible Pull 在多个网关自动更新 Rust 服务与 Grafana Dashboard。
- 节点 OTA:利用 LoRa Downlink 分片下发固件,结合 CRC/版本号实现远程升级。
十二、总结
本项目给出了一套 STM32 LoRa 终端 + Rust 异步网关 + MQTT/InfluxDB/Grafana 的端到端温室监控方案。LoRa 解决了大范围、复杂地形下的连接问题,Rust 网关提供高并发、可观测、易扩展的边缘处理能力,InfluxDB/Grafana 则让数据分析与告警上线瞬间落地。整套设计强调“可落地、可复制、模块化扩展”,不仅适用于温室,还可以快速迁移到林场、养殖场、蓄水池等需要分布式监控的场景。配合文中提供的 BOM、协议、固件、网关代码与配图,读者可以直接按照本文搭建出一个真正可上线的分布式智慧农业系统。
更多推荐
所有评论(0)