关键词: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。主循环结构:

  1. 采集各传感器 → 滤波 → TLV 编码。
  2. 根据 Slot 时间启动 SX1278 发送。
  3. 检测是否有 Downlink(DIO0 中断),并处理控制命令。
  4. 更新状态灯/本地日志。

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:Tokio select! 监听 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

  • Bucketgreenhouse
  • Measurementtelemetry
  • Tagsnode, zone, crop
  • Fieldst_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/企微机器人。

九、部署与运维步骤

  1. 节点产测:CubeProgrammer 写入固件 → LoRa 参数校准 → 记录 NodeID 与密钥。
  2. 网关初始化
    • Ubuntu 安装 rustupinfluxdb2-clientemqx
    • systemd 配置 greenhouse-gateway.service,ExecStart 为 ./gateway --cfg config.toml
    • 配置 watchdog 定时检查 SX1302 进程。
  3. 数据平台
    • InfluxDB 导入 schema/greenhouse.json
    • Grafana 导入 dashboards/greenhouse.json,替换数据源。
    • EMQX 导入 ACL,用于区分只读和控制权限。
  4. 告警服务:Rust Alert Service 监听 Redis alarm:*,调用腾讯云 SMS/企业微信机器人。
  5. 日志采集
    • 节点: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 中配置电流阈值。

十一、扩展方向

  1. 多模通信:关键温室增设 NB-IoT 或 4G DTU,LoRa 失联时自动切换。
  2. AI 预测:InfluxDB 数据定期导入 Rust Axum 服务,调用 ONNX Runtime 预测病虫害风险。
  3. 控制柜联动:网关判断策略后,通过 Modbus TCP 命令同步到 PLC,实现灌溉互锁联动。
  4. 一键部署脚本:使用 Ansible Pull 在多个网关自动更新 Rust 服务与 Grafana Dashboard。
  5. 节点 OTA:利用 LoRa Downlink 分片下发固件,结合 CRC/版本号实现远程升级。

十二、总结

本项目给出了一套 STM32 LoRa 终端 + Rust 异步网关 + MQTT/InfluxDB/Grafana 的端到端温室监控方案。LoRa 解决了大范围、复杂地形下的连接问题,Rust 网关提供高并发、可观测、易扩展的边缘处理能力,InfluxDB/Grafana 则让数据分析与告警上线瞬间落地。整套设计强调“可落地、可复制、模块化扩展”,不仅适用于温室,还可以快速迁移到林场、养殖场、蓄水池等需要分布式监控的场景。配合文中提供的 BOM、协议、固件、网关代码与配图,读者可以直接按照本文搭建出一个真正可上线的分布式智慧农业系统。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐