小型气象站上报网页——基于ESP32的阳台气象监测与远程查看

💡 划重点

  • 用 ESP32 自建 Wi-Fi 热点与 Web 服务器,无需依赖任何云平台或手机 App。
  • 一颗 BME280 同时完成温度、湿度、大气压三要素采集,精度完全满足居家种花养草。
  • 支持温控 PWM 风扇,气温超过阈值自动开启温柔送风,避免阳台植物闷热。
  • 整机硬件成本控制在 70 元以内,只需一把烙铁加一根 USB 线就能跑起来。

需求洞察

目标用户:阳台种植爱好者、多肉/兰花玩家、刚接触物联网硬件的学生或创客。他们需要掌握自家阳台的真实小气候,又不想花几百块买商业气象站,更不希望家庭数据传输到陌生的云平台。

真实痛点:阳台温度常比天气预报高出 5–8℃,特别是封闭阳台在午间容易形成“蒸笼效应”,导致植物黄叶、烂根。而市售蓝牙温湿度计屏幕太小、历史数据不可查、无远程查看能力,往往只是摆设。

为什么值得做:ESP32 自带 2.4 GHz Wi-Fi,可以同时充当传感器采集中心与轻量级 Web 服务器。手机、电脑直接连上设备热点,浏览器里就能看到实时气象卡片、风扇状态和历史曲线。整个项目用到的只有开源库和基础 Arduino 代码,学习成本低,又是真实可用的居家工具,非常适合作为硬件编程入门第一站。

方案设计

感知层:BME280 通过 I2C 总线挂载在 ESP32 的 GPIO21(SDA)/22(SCL) 上,每 2 秒完成一次采样,原始数据经片上补偿算法换算为实际温度、相对湿度和气压值。

控制层:ESP32 运行 FreeRTOS 任务,核心逻辑分为传感器轮询任务、Web 服务器任务和 PWM 控制任务。温度超过 30℃ 时自动递增风扇占空比,避免继电器式的突风;低于 28℃ 则逐步降速直至完全停止。LED 心跳灯每 500ms 翻转一次,作为系统活体标志。

执行层:PWM 风扇驱动模块采用 NPN 三极管(如 S8050)驱动 5V 直流风扇,ESP32 的 GPIO25 输出 1kHz PWM 信号经 1kΩ 电阻连接到基极,风扇转速随占空比线性变化。

日志/联网层:设备默认工作在 AP 模式,Wi-Fi SSID 为 阳台小站,密码 12345678。内置 Web 服务器同时提供 JSON 数据接口 GET /api/sensor,方便后续对接 Grafana 或手机小组件。页面每隔 5 秒自动刷新。

物料清单

名称 数量 用途 估算单价(元) 小计(元)
ESP32-WROOM-32 开发板 1 主控 18 18
BME280 模块 (GY-BME280) 1 温湿压传感器 9 9
5V 微型风扇 (30×30mm) 1 植物通风 8 8
S8050 三极管 + 1kΩ 电阻 1套 风扇驱动 1 1
红色 LED + 220Ω 限流电阻 1套 心跳指示 1 1
面包板 + 跳线包 1套 原型搭建 10 10
Micro-USB 数据线 1 供电与烧录 4 4
3.3V/5V 适配器(可复用手机充电头) 0 用已有 0 0
合计 51

接线与结构

ESP32 引脚 连接对象 说明
3V3 BME280 VCC 3.3V 供电
GND BME280 GND、风扇负极、LED 负极 共地
GPIO21 BME280 SDA I2C 数据(默认)
GPIO22 BME280 SCL I2C 时钟(默认)
GPIO25 1kΩ 电阻 → S8050 基极 PWM 信号
5V 风扇正极 5V 风扇供电
S8050 集电极 风扇负极 低端开关
GPIO2 220Ω 电阻 → LED 正极 心跳灯
微调说明:GPIO2 在部分 ESP32 开发板上内置 LED,该脚高电平点亮,可以省去外接 LED,但仍按物料清单外接以保证所有板型正常工作。

核心代码

下方为基于 Arduino 框架和 PlatformIO 的完整骨架,主文件 src/main.cpp 可直接编译运行。


#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <Adafruit_BME280.h>

const char *ssid = "阳台小站";
const char *password = "12345678";

Adafruit_BME280 bme;
WebServer server(80);
#define PWM_CHANNEL 0
#define PWM_FREQ 1000
#define PWM_RES 8
#define FAN_PIN 25
#define LED_PIN 2

bool ledState = false;
unsigned long lastLedBlink = 0;
const unsigned long ledInterval = 500;
float temperature = 0, humidity = 0, pressure = 0;
int fanDuty = 0;
const float tempHigh = 30.0, tempLow = 28.0;

void updateFan(float temp) {
  if (temp > tempHigh) fanDuty = map((int)(temp*10), 300, 450, 128, 255);
  else if (temp < tempLow) fanDuty = 0;
  // 过渡区保持占空比不变
  fanDuty = constrain(fanDuty, 0, 255);
}

void handleRoot() {
  char html[800];
  snprintf(html, sizeof(html),
    "<!DOCTYPE html><html><head><meta charset='utf-8'><title>阳台气象站</title>"
    "<meta http-equiv='refresh' content='5'>"
    "<style>body{font-family:sans-serif;background:#eef5e4;text-align:center;}"
    ".card{background:white;border-radius:12px;padding:20px;margin:20px auto;max-width:350px;box-shadow:0 4px 8px rgba(0,0,0,0.1);}"
    "</style></head><body><h1>🌿 阳台小站</h1>"
    "<div class='card'><p>🌡️ 温度: %.2f °C</p><p>💧 湿度: %.2f %%</p><p>🌀 气压: %.2f hPa</p>"
    "<p>🌀 风扇占空比: %d / 255</p></div></body></html>",
  server.send(200, "text/html", html);
}

void setup() {
  pinMode(LED_PIN, OUTPUT);
  ledcWrite(PWM_CHANNEL, 0);
  
  Serial.begin(115200);
  Wire.begin(21, 22);
  if (!bme.begin(0x76)) {
    Serial.println("BME280 未找到");
    while(1) { digitalWrite(LED_PIN, HIGH); delay(200); digitalWrite(LED_PIN, LOW); delay(200); }
  }
  
  WiFi.softAP(ssid, password);
  server.on("/", handleRoot);
  server.on("/api/sensor", [](){
    char json[250];
    snprintf(json, sizeof(json), "{\"temp\":%.2f,\"humidity\":%.2f,\"pressure\":%.2f,\"fan\":%d}",
    server.send(200, "application/json", json);
  });
  server.begin();
}

void loop() {
  server.handleClient();
  unsigned long now = millis();
  if (now - lastLedBlink >= ledInterval) {
    lastLedBlink = now;
    ledState = !ledState;
  }
  static unsigned long lastSensor = 0;
  if (now - lastSensor >= 2000) {
    lastSensor = now;
    temperature = bme.readTemperature();
    humidity = bme.readHumidity();
    pressure = bme.readPressure() / 100.0F;
    updateFan(temperature);
  }
}

依赖库:在 platformio.ini 中添加 lib_deps = adafruit/Adafruit BME280 Library @ ^2.2.2

调试步骤

  1. 硬件检查:只连接 BME280 和 LED,上电后观察心跳灯是否以 500ms 间隔闪烁。若无,检查 GPIO2 电平或杜邦线接触。
  2. I2C 扫描:烧录 I2C 扫描例程,确认 BME280 地址为 0x76,串口打印无误。
  3. Wi-Fi 热点:烧录主程序后手机搜索 WiFi,连接 “阳台小站”,浏览器访问 http://192.168.4.1/,应出现卡片界面。
  4. 风扇联动:捂住 BME280 或用吹风机轻微加热,待温度超过 30℃,风扇启动并随温度升高转速加快;移开热源温度降至 28℃ 以下风扇停转。
  5. 数据校验:对比手机天气数据,阳台气温通常高 3–6℃,湿度受植物蒸腾影响可能偏高,属正常现象。

设计图

小型气象站上报网页结构flow
1
感知输入
2
控制处理
3
执行反馈
4
数据上报