ESP32+Web实现智能气象站

项目仓库源码:

https://gitee.com/vopo123/esp32-dev-kit/tree/master/ESP32S3-Weather-Station

基于 ESP32-S3 开发的智能气象站系统,核心功能是:通过多种传感器采集室内环境数据(温湿度、烟雾浓度、光照强度),结合高德天气 API 获取室外实时 / 预报天气数据,通过 Web 界面可视化展示所有数据,并支持前端实时配置报警阈值、联动规则,同时实现烟雾超标蜂鸣器报警、光照联动 WS2812 LED 灯变色的硬件交互。

一、项目概述

1、项目说明:

核心功能
  • 实时天气:基于高德API获取当前天气状况,包含温度、湿度、风向、风力等信息
  • 室内温湿度:通过DHT11传感器采集室内温度和湿度
  • 室内环境:通过MQ2传感器监测烟雾浓度,BH1750传感器监测光照强度
  • 天气预报:获取4天天气预报,包含白天和夜间天气信息
  • 智能联动:烟雾浓度超标时自动触发蜂鸣器报警,光照强度不同时LED显示不同颜色
技术亮点
  • 现代化Web界面:使用Tailwind CSS和Alpine.js实现响应式设计,优先适配手机屏幕
  • 高性能服务器:使用ESPAsyncWebServer实现异步非阻塞Web服务器
  • 实时数据更新:使用WebSocket实现传感器数据实时更新
  • 智能报警系统:PWM控制蜂鸣器实现间歇报警,避免噪音污染
  • 光照联动:根据光照强度自动调整WS2812 LED颜色

2、硬件需求

|--------|----------|----|---------|
| 组件 | 型号 | 数量 | 用途 |
| 开发板 | ESP32-S3 | 1 | 主控板 |
| 温湿度传感器 | DHT11 | 1 | 采集室内温湿度 |
| 烟雾传感器 | MQ2 | 1 | 监测烟雾浓度 |
| 光照传感器 | BH1750 | 1 | 监测光照强度 |
| 蜂鸣器 | 无源蜂鸣器 | 1 | 烟雾报警 |
| LED | WS2812 | 1 | 光照联动指示 |
| 面包板 | 通用 | 1 | 电路连接 |
| 杜邦线 | 公对公、公对母 | 若干 | 电路连接 |

3、软件需求

  • 开发环境:Arduino IDE 1.8.10+ 或 PlatformIO
  • 核心库
    • ESP32 Arduino Core
    • ESPAsyncWebServer
    • AsyncTCP
    • ArduinoJson
    • DHT sensor library
    • Adafruit NeoPixel
    • BH1750

4、硬件连接

|---------|------------|--------------|
| 引脚 | 功能 | 连接对象 |
| 13 | DHT11数据 | DHT11数据引脚 |
| 7 | MQ2模拟输入 | MQ2信号引脚 |
| 20 | 蜂鸣器PWM | 无源蜂鸣器正极 |
| 39 | BH1750 SCL | BH1750 SCL引脚 |
| 40 | BH1750 SDA | BH1750 SDA引脚 |
| 48 | WS2812数据 | WS2812数据引脚 |
| GND | 接地 | 所有组件接地 |
| 3.3V/5V | 电源 | 所有组件电源 |

5、技术架构

前端

  • Tailwind CSS:用于快速构建现代化的响应式界面
  • Alpine.js:用于轻量级的前端交互和数据绑定
  • Font Awesome:提供丰富的图标

后端

  • ESPAsyncWebServer:高性能异步Web服务器
  • WebSocket:实现实时双向通信
  • HTTPClient:调用高德天气API
  • ArduinoJson:解析和处理JSON数据

硬件控制

  • DHT11:通过单总线协议读取温湿度
  • MQ2:通过模拟输入读取烟雾浓度
  • BH1750:通过I2C协议读取光照强度
  • PWM:控制蜂鸣器频率和占空比
  • WS2812:控制LED颜色

二、天气API使用说明

免费天气 API 获取(高德开放平台)

注意:高德 API 有免费限流(每日 10 万次),个人使用完全足够,无需担心超量。

选择高德开放平台天气 API(免费、国内节点快、支持 54天预报 + 空气质量),步骤如下:

  1. 打开高德开放平台,注册并登录账号
  2. 进入「控制台」→「应用管理」→「创建新应用」,添加「Web 服务 API」类型的 Key
  3. 记录你的Key 和目标城市的城市 ADCODE (如北京 110000、上海 310000,可在高德 ADCODE 查询获取)
  4. API 接口说明(后续代码直接调用):
    • 实时天气 + 5 天预报:https://restapi.amap.com/v3/weather/weatherInfo?key=你的Key&city=城市ADCODE&extensions=all&output=json
    • 空气质量:接口已包含在上述请求中,无需单独调用

1、PC端测试源码

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>高德天气API PC端测试工具</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
      }
      body {
        max-width: 1200px;
        margin: 20px auto;
        padding: 0 20px;
        background-color: #f5f7fa;
      }
      .header {
        margin-bottom: 20px;
        padding-bottom: 15px;
        border-bottom: 1px solid #e5e7eb;
      }
      .header h1 {
        font-size: 24px;
        color: #165DFF;
        margin-bottom: 10px;
      }
      .config-panel {
        background: #fff;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 2px 8px rgba(0,0,0,0.08);
        margin-bottom: 20px;
      }
      .config-item {
        margin-bottom: 15px;
        display: flex;
        align-items: center;
        gap: 10px;
      }
      .config-item label {
        width: 120px;
        font-weight: 500;
        color: #333;
      }
      .config-item input, .config-item select {
        flex: 1;
        padding: 8px 12px;
        border: 1px solid #dcdfe6;
        border-radius: 4px;
        font-size: 14px;
        outline: none;
      }
      .config-item input:focus, .config-item select:focus {
        border-color: #165DFF;
        box-shadow: 0 0 0 2px rgba(22,93,255,0.1);
      }
      .btn-group {
        display: flex;
        gap: 10px;
        margin-top: 10px;
      }
      .btn {
        padding: 10px 20px;
        border: none;
        border-radius: 4px;
        font-size: 14px;
        font-weight: 500;
        cursor: pointer;
        transition: all 0.2s;
      }
      .btn-primary {
        background-color: #165DFF;
        color: #fff;
      }
      .btn-primary:hover {
        background-color: #0d4ed9;
      }
      .btn-reset {
        background-color: #fff;
        color: #666;
        border: 1px solid #dcdfe6;
      }
      .btn-reset:hover {
        background-color: #f5f7fa;
      }
      .result-panel {
        background: #fff;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 2px 8px rgba(0,0,0,0.08);
      }
      .result-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 15px;
        padding-bottom: 10px;
        border-bottom: 1px solid #e5e7eb;
      }
      .result-status {
        font-size: 14px;
      }
      .status-success {
        color: #52c41a;
      }
      .status-error {
        color: #ff4d4f;
        }
        .result-content {
            width: 100%;
            min-height: 300px;
            padding: 15px;
            background-color: #f9fafc;
            border: 1px solid #e5e7eb;
            border-radius: 4px;
            font-family: "Consolas", "Monaco", monospace;
            font-size: 14px;
            line-height: 1.6;
            color: #333;
            white-space: pre-wrap;
            word-wrap: break-word;
        }
        .tip {
            margin-top: 10px;
            font-size: 12px;
            color: #909399;
            line-height: 1.5;
        }
        .tip a {
            color: #165DFF;
            text-decoration: none;
        }
        .tip a:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>高德开放平台天气API 测试工具(PC端)</h1>
        <p class="tip">用于调试extensions=base(实时)/all(实时+预报)模式,展示完整原始JSON数据 | 数据来源:<a href="https://lbs.amap.com/api/webservice/guide/api/weatherinfo" target="_blank">高德天气API官方文档</a></p>
    </div>

    <div class="config-panel">
        <div class="config-item">
            <label>高德API Key:</label>
            <input type="text" id="amapKey" placeholder="请输入你的高德Web服务API Key" value="">
        </div>
        <div class="config-item">
            <label>城市ADCODE:</label>
            <input type="text" id="cityAdcode" placeholder="6位数字,如北京110000、上海310000" value="310000">
        </div>
        <div class="config-item">
            <label>请求模式:</label>
            <select id="extensionsMode">
                <option value="base">base - 仅实时天气</option>
                <option value="all" selected>all - 实时+未来预报</option>
            </select>
        </div>
        <div class="btn-group">
            <button class="btn btn-primary" id="sendRequest">发送请求</button>
            <button class="btn btn-reset" id="resetForm">重置</button>
        </div>
    </div>

    <div class="result-panel">
        <div class="result-header">
            <h3>API返回原始数据(格式化)</h3>
            <div class="result-status" id="requestStatus">未发起请求</div>
        </div>
        <div class="result-content" id="resultContent">请点击「发送请求」获取高德天气API数据...</div>
    </div>

    <script>
        // 获取页面元素
        const amapKey = document.getElementById('amapKey');
        const cityAdcode = document.getElementById('cityAdcode');
        const extensionsMode = document.getElementById('extensionsMode');
        const sendRequest = document.getElementById('sendRequest');
        const resetForm = document.getElementById('resetForm');
        const requestStatus = document.getElementById('requestStatus');
        const resultContent = document.getElementById('resultContent');

        // 重置表单
        resetForm.addEventListener('click', () => {
            amapKey.value = '';
            cityAdcode.value = '310000';
            extensionsMode.value = 'all';
            requestStatus.innerText = '未发起请求';
            requestStatus.className = 'result-status';
            resultContent.innerText = '请点击「发送请求」获取高德天气API数据...';
        });

        // 发送API请求核心逻辑
        sendRequest.addEventListener('click', async () => {
            // 表单验证
            const key = amapKey.value.trim();
            const adcode = cityAdcode.value.trim();
            const mode = extensionsMode.value;

            if (!key) {
                alert('请输入你的高德Web服务API Key!');
                amapKey.focus();
                return;
            }
            if (!/^\d{6}$/.test(adcode)) {
                alert('城市ADCODE必须是6位数字!如北京110000、上海310000');
                cityAdcode.focus();
                return;
            }

            // 初始化请求状态
            sendRequest.disabled = true;
            sendRequest.innerText = '请求中...';
            requestStatus.innerText = '请求中,请稍候...';
            requestStatus.className = 'result-status';
            resultContent.innerText = '加载中...';

            try {
                // 记录请求开始时间(计算耗时)
                const startTime = Date.now();
                // 高德天气API请求地址(官方接口)
                const apiUrl = `https://restapi.amap.com/v3/weather/weatherInfo`;
                // 拼接请求参数
                const params = new URLSearchParams({
                    key: key,
                    city: adcode,
                    extensions: mode,
                    output: 'json', // 固定返回JSON格式
                    lang: 'zh-CN'   // 中文返回
                });

                // 发送GET请求(跨域由高德API自动支持,无需额外处理)
                const response = await fetch(`${apiUrl}?${params.toString()}`, {
                    method: 'GET',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    timeout: 10000 // 10秒超时
                });

                // 计算请求耗时
                const costTime = (Date.now() - startTime) / 1000;
                // 获取响应数据
                const result = await response.json();
                // 格式化JSON(缩进2个空格,易读)
                const formatResult = JSON.stringify(result, null, 2);

                // 更新请求状态
                requestStatus.innerText = `请求成功 | 响应码:${response.status} | 耗时:${costTime.toFixed(2)}s | 模式:${mode}`;
                requestStatus.className = 'result-status status-success';
                resultContent.innerText = formatResult;

                // 控制台打印原始数据(方便二次调试)
                console.log(`高德天气API-${mode}模式返回数据:`, result);

            } catch (error) {
                // 捕获请求错误(网络错误、超时、解析错误等)
                requestStatus.innerText = `请求失败:${error.message}`;
                requestStatus.className = 'result-status status-error';
                resultContent.innerText = `错误详情:\n${JSON.stringify(error, null, 2)}\n\n排查建议:\n1. 检查API Key是否正确(需为Web服务类型)\n2. 检查网络是否能访问高德API\n3. 检查城市ADCODE是否有效\n4. 确认API Key未超出每日调用限额`;
            } finally {
                // 恢复按钮状态
                sendRequest.disabled = false;
                sendRequest.innerText = '发送请求';
            }
        });
    </script>
</body>
</html>

2、快速使用步骤

步骤 1:准备高德 API Key
  1. 打开高德开放平台,登录后在「控制台」→「应用管理」获取Web 服务 API Key(必须是该类型,其他类型如 Android/iOS 会请求失败);
  2. 城市 ADCODE 获取:高德 ADCODE 官方查询工具,输入城市名获取 6 位数字 ADCODE(如北京 110000、广州 440100、深圳 440300)。
步骤 2:运行测试程序
  1. 将上述代码保存为.html文件后,直接用 PC 浏览器双击打开(无需部署服务器,纯前端运行);
  2. 在页面中输入「高德 API Key」和「城市 ADCODE」,选择请求模式(base/all);
  3. 点击「发送请求」,等待 1-3 秒即可看到 API 返回的完整格式化 JSON 数据

3、关键使用说明

两种模式数据差异对比

|----------|------------------------|----------------------------------------------------|
| 请求模式 | extensions=base | extensions=all |
| 核心数据 | 仅实时天气lives 数组) | 实时天气(lives )+未来预报forecasts 数组) |
| 数据项 | 实时温湿度、风力、风向、空气质量、发布时间等 | 包含 base 所有数据 + 未来 3-7 天预报(日期、天气状况、最高 / 最低温、风力、风向等) |
| 适用场景 | 仅需实时天气数据 | 需要实时 + 未来多天预报数据 |

三、核心功能模块说明

1、 硬件层模块

|--------|-------------------|----------------------------------------------------|
| 模块 | 硬件型号 / 引脚 | 功能说明 |
| 温湿度采集 | DHT11(引脚 13) | 每秒读取温度 / 湿度,异常值置 0 |
| 烟雾检测 | MQ2(模拟引脚 7) | 读取模拟 ADC 值(0-4095),超过阈值触发报警 |
| 光照检测 | BH1750(I2C 39/40) | 读取环境光照强度(lux),按阈值控制 LED 颜色 |
| 报警输出 | 无源蜂鸣器(引脚 20) | PWM 通道 0,4500Hz 频率,超标时间歇鸣响(500ms 响 / 500ms 停) |
| 视觉反馈 | WS2812(引脚 48) | 光照 <100lux 蓝灯、100-500lux 绿灯、>500lux 黄灯,自动模式关闭则关灯 |

2、网络层模块

  • WiFi 管理:连接指定 SSID/PWD,连接成功后打印设备 IP 地址,前端通过该 IP 访问 Web 界面;
  • 异步 Web 服务器 :基于 ESPAsyncWebServer,非阻塞式处理 HTTP 请求,支持高并发;
  • WebSocket 通信 :路径 /ws,实现前端 - 设备双向通信:
    • 设备→前端:每秒推送传感器实时数据;
    • 前端→设备:接收阈值 / 开关配置,立即更新设备参数并应用;
  • 天气 API 调用 :通过 HTTPClient 调用高德天气 API,支持:
    • extensions=base:获取实时天气(城市 / 温度 / 湿度 / 风向 / 更新时间);
    • extensions=all:获取 4 天预报(日期 / 星期 / 昼夜天气 / 温度 / 风力);
    • 缓存机制:10 分钟更新一次,避免频繁请求 API。

3、前端交互模块

  • 基于 TailwindCSS + Alpine.js 开发的响应式 Web 界面,适配移动端;
  • 核心展示区域:
    • 实时天气卡片:城市 / 温度 / 天气 / 风向 / 湿度 / 更新时间;
    • 室内温湿度卡片:数值可视化展示;
    • 天气预报卡片:4 天昼夜天气 / 温度列表;
    • 室内环境卡片:烟雾浓度(超标标红)、光照强度(按阈值变色);
    • 配置区域:滑动条调整烟雾 / 光照阈值,开关控制自动报警 / LED 联动。

4、数据处理模块

  • 传感器数据封装SensorData 结构体存储温湿度、烟雾、光照、更新时间;
  • 报警配置封装AlarmConfig 结构体存储阈值、自动开关、报警状态,默认值可直接修改;
  • JSON 序列化 / 反序列化 :基于 ArduinoJson
    • 设备→前端:getSensorJson() 序列化传感器数据为 JSON;
    • 前端→设备:onWsEvent 反序列化配置消息,更新本地参数;
    • 天气数据:动态解析高德 API 返回的 JSON,提取核心字段并二次封装。

四、重点功能与程序流程分析

1、主循环流程

2、web请求处理流程

3. WebSocket 双向通信(核心函数:onWsEvent()

复制代码
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
               void *arg, uint8_t *data, size_t len) {
    if (type == WS_EVT_DATA) {
        String msg = String((char*)data, len);
        StaticJsonDocument<256> doc;
        DeserializationError error = deserializeJson(doc, msg);
        if (error) { return; }

        // 更新前端传递的配置参数
        if (doc.containsKey("mq2Threshold")) {
            alarmConfig.mq2Threshold = doc["mq2Threshold"].as<int>();
        }
        // 其他配置更新...

        // 立即应用配置+广播给所有客户端
        handleMQ2Alarm();
        handleLightLED();
        server->textAll(getSensorJson());
    }
}

关键说明

  • WebSocket 是实时通信的核心,相比 HTTP 轮询,减少网络开销;
  • server->textAll() 实现 "一对多" 广播,所有连接的前端都会同步最新配置和数据。

4. 天气 API 解析(核心函数:updateWeatherData()

复制代码
void updateWeatherData() {
    if (WiFi.status() != WL_CONNECTED) { return; }
    DynamicJsonDocument weatherDoc(3072);
    bool isLiveOk = false, isForecastOk = false;

    // 第一步:获取实时天气(base模式)
    HTTPClient httpLive;
    String liveUrl = getAmapApiUrl("base");
    httpLive.begin(liveUrl);
    int httpCodeLive = httpLive.GET();
    if (httpCodeLive == HTTP_CODE_OK) {
        // 解析实时天气字段:城市/温度/湿度/风向等
        // 关键:docLive["lives"][0] 取第一个实时数据对象
    }
    httpLive.end(); // 必须关闭连接,释放资源

    // 第二步:获取预报天气(all模式)
    // 类似实时天气逻辑,解析4天预报数据,转换星期为中文

    // 第三步:整合数据并序列化
    if (isLiveOk || isForecastOk) {
        serializeJson(weatherDoc, weatherData);
    }
}

关键说明

  • 采用 HTTPClient 分两次请求(base/all),避免单次请求数据过大;
  • 中文星期转换:getWeekText() 函数将数字(1-7)转为 "周一 - 周日",提升用户体验;
  • 缓存天气数据到全局变量 weatherData,避免每次前端请求都调用 API。
相关推荐
coderYYY2 小时前
VSCode终端启动报错
前端·ide·vscode·npm·编辑器
tod1133 小时前
Redis 数据类型与 C++ 客户端实践指南(redis-plus-plus)
前端·c++·redis·bootstrap·html
Sylvia33.3 小时前
火星数据:棒球数据API
java·前端·人工智能
weixin199701080163 小时前
1688商品详情页前端性能优化实战
前端·性能优化
DEMO派3 小时前
前端常用XSS攻击演示与防御方案解析
前端·xss
问今域中4 小时前
Vue的computed用法解析
前端·javascript·vue.js
扶苏10024 小时前
详解Vue3的provide和inject
前端·javascript·vue.js
武帝为此5 小时前
【Shell 函数库介绍】
前端·chrome
yuki_uix5 小时前
GraphQL 重塑:从 API 语言到 AI 时代的"逻辑神经系统"
前端·graphql