项目背景
一、动机
市面上的天气显示产品几乎没有自主预警功能,而自主预警可以让繁忙的群体无须花时间去了解天气是否达到预警情况。
二、需求场景
- 接送与上班:早中晚上班或接送小孩的人很忙,通常不会去花时间看天气预报。这个群体以骑电动车的居多,未准备充分的话,会遭受雨淋、降温等影响。
- 降温提醒:大幅度降温往往会引发感冒等。年老的长辈及小孩通常抵抗力弱,出门未花时间看天气预报。
- 骑行:骑行游玩的人群做攻略时往往关注目的地,对旅行途中的天气关注不够细致。长途骑行往往需要跨地区,所在地的天气预警对其游玩往往有较大影响。
三、核心功能需求
1. 核心功能
异常天气(有雨、低温)的自动预警,以物理可见的方式进行预警。尤其针对早上6:00-9:00,12:00-15:00,16:00-19:00三个接送小孩与上班高峰时间段进行预警。预警以多颗灯及颜色进行区分,不同的灯的位置情况代表不同的时段,不同的颜色代表不同的预警级别。
2. 其余功能
- 参数可设置:如降雨(或降温)的预警可依据降雨(或降温)的级别进行设置。
- 语音播报:针对老年群体等视力不好的群体,有物理按键实现预警的语音播报。
- 老黄历:一些群体喜欢翻看老黄历,可增加老黄历功能。可查看今日宜做、不宜做事项。
- 闹钟功能:集成基础闹钟功能,满足日常时间提醒需求。
四、价格及市场分析
- 该装置小巧,便宜实用,能精准满足用户对天气自动预警的核心需求。
- 主打价格低廉、功能实用的定位,覆盖大众消费群体。
- 具备拓展性,可满足DIY爱好者、技术开发发烧友的个性化探索需求。
五、应用场景
装置体积小巧,便携性强,可摆放于电动车、摩托车、汽车内,也可随身携带,适配多种出行场景,不占用过多空间。
开发文档
1. 项目概述
本项目旨在开发一个基于 ESP32 微控制器、0.96寸OLED显示屏、DFPlayer Mini 语音模块和 WS2812B RGB LED 灯带的智能天气预警系统。系统能够实时获取天气数据和老黄历信息,并在特定高峰时段(6:00-9:00, 12:00-15:00, 17:00-19:00)通过多色LED灯进行物理可见的异常天气预警,同时提供按键触发的语音播报功能。
2. 硬件清单
以下是完成本项目所需的硬件组件:
| 序号 | 组件名称 | 规格/型号 | 数量 | 备注 |
|---|---|---|---|---|
| 1 | 主控板 | ESP32 DevKitC 或其他ESP32开发板 | 1 | 带有Wi-Fi功能 |
| 2 | 显示屏 | 0.96寸OLED显示屏 | 1 | I2C接口 (SSD1306芯片) |
| 3 | 语音模块 | DFPlayer Mini | 1 | 带有SD卡槽 |
| 4 | 扬声器 | 8Ω 1W 小喇叭 | 1 | 配合DFPlayer Mini使用 |
| 5 | LED灯带 | WS2812B RGB LED (NeoPixel) | 9颗以上 | 用于多色预警灯 |
| 6 | 按键 | 轻触开关 | 1 | 用于语音播报触发 |
| 7 | 存储卡 | MicroSD卡 | 1 | 用于存储语音文件 (MP3格式) |
| 8 | 供电 | 5V 2A 电源适配器 | 1 | ESP32和WS2812B供电 |
| 9 | 附件 | 杜邦线、面包板、1000μF电容 (可选,用于WS2812B供电稳定) | 若干 |
3. 硬件接线说明
本项目中,我们使用了ESP32的特定GPIO引脚来连接各个模块。请严格按照下表进行接线。
| 模块 | 模块引脚 | ESP32 GPIO引脚 | 备注 |
|---|---|---|---|
| OLED (I2C) | VCC | 3.3V/5V | 供电 |
| GND | GND | 接地 | |
| SCL | GPIO 22 | I2C时钟线 | |
| SDA | GPIO 21 | I2C数据线 | |
| DFPlayer Mini | VCC | 5V | 供电 |
| GND | GND | 接地 | |
| RX | GPIO 17 (ESP32的RX2) | DFPlayer的RX接ESP32的TX2 | |
| TX | GPIO 16 (ESP32的TX2) | DFPlayer的TX接ESP32的RX2 | |
| SPK_1/SPK_2 | 扬声器 | 直接连接8Ω 1W扬声器 | |
| WS2812B LED | VCC | 5V | 注意:需独立供电,ESP32的5V引脚电流不足 |
| GND | GND | 接地 | |
| DIN | GPIO 13 | 数据输入 | |
| 语音按键 | 一端 | GPIO 34 | 内部上拉,接GND触发 |
| 另一端 | GND | 接地 |
重要提示:
- WS2812B供电: 9颗WS2812B全亮时电流较大,强烈建议为WS2812B灯带单独提供5V电源,并确保其GND与ESP32的GND共地。
- DFPlayer Mini: DFPlayer Mini的RX/TX引脚直接连接ESP32的硬件串口2 (GPIO16/17) 以确保通信稳定。
4. 开发环境搭建
本项目基于Arduino IDE或VS Code + PlatformIO进行开发。
4.1 安装ESP32开发板支持
- 打开Arduino IDE,进入 文件 -> 首选项。
- 在“附加开发板管理器网址”中添加以下链接:
1
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
- 进入 工具 -> 开发板 -> 开发板管理器,搜索
esp32并安装最新版本。 - 在 工具 -> 开发板 中选择ESP32型号(如
ESP32 Dev Module)。
4.2 安装所需库文件
本项目需要以下库文件。在Arduino IDE中,进入 工具 -> 管理库,搜索并安装:
| 库名称 | 作用 |
|---|---|
U8g2 |
驱动OLED显示屏 |
DFRobotDFPlayerMini |
驱动DFPlayer Mini语音模块 |
Adafruit NeoPixel |
驱动WS2812B RGB LED灯带 |
ArduinoJson |
解析API返回的JSON数据 |
HTTPClient |
用于发起HTTP请求获取天气和老黄历数据 |
NTPClient |
用于网络时间同步 |
5. API密钥获取与配置
本项目需要两个外部API服务:
5.1 和风天气 (QWeather)
用于获取实时和逐小时天气预报(包括温度和降水概率)。
- 访问 和风天气开发服务官网 注册账号。
- 在控制台创建项目,获取 Public ID 和 Key。
- 使用 GeoAPI 或在控制台查询所在城市的 Location ID。
- 将获取到的信息替换源代码 (
weather_alert_system.ino) 中的占位符:1
2const char* QWEATHER_KEY = "YOUR_QWEATHER_KEY";
const char* QWEATHER_LOCATION = "YOUR_CITY_LOCATION_ID";
5.2 老黄历 API (以聚合数据为例)
用于获取每日宜忌信息。
- 访问 聚合数据官网 或 Free-API 注册账号。
- 申请老黄历API服务,获取 AppKey。
- 将获取到的信息替换源代码中的占位符:
1
const char* ALMANAC_KEY = "YOUR_ALMANAC_KEY";
6. DFPlayer Mini 语音文件准备
DFPlayer Mini 通过播放SD卡中特定路径和名称的MP3文件来实现语音播报。
- 准备一张MicroSD卡,格式化为FAT32格式。
- 在SD卡根目录下创建名为
mp3的文件夹。 - 将语音文件命名为
0001.mp3,0002.mp3,0003.mp3等,并存入mp3文件夹。
本项目中,我们预设了以下语音文件内容:
| 文件名 | 对应预警级别 | 建议播报内容 |
|---|---|---|
0001.mp3 |
低温预警 (Level 1) | “请注意,有低温预警,请多穿衣物。” |
0002.mp3 |
降雨预警 (Level 2) | “请注意,有降雨预警,请携带雨具。” |
0003.mp3 |
双重预警 (Level 3) | “请注意,有低温和降雨双重预警,请做好防范。” |
0004.mp3 |
无预警/老黄历播报 | “当前无异常天气预警。” |
0005.mp3 |
闹钟铃声 | (自定义闹钟铃声) |
7. 源代码配置与烧录
- 打开
weather_alert_system.ino源代码文件。 - 修改 Wi-Fi 配置:
1
2const char* ssid = "Wi-Fi名称";
const char* password = "Wi-Fi密码"; - 修改 API 密钥和 Location ID (参考 5. API密钥获取与配置)。
- 将ESP32开发板连接到电脑。
- 在Arduino IDE中,点击 上传 按钮,将代码烧录到ESP32中。
8. 核心功能实现逻辑
8.1 异常天气预警 (LED)
系统通过 WS2812B LED 实现物理可见的预警,共使用9颗LED,分为3组,每组3颗,对应3个高峰时段:
| 时段编号 | 时间范围 | LED位置 (索引) | 预警颜色定义 |
|---|---|---|---|
| 1 | 6:00 - 9:00 | 0, 1, 2 | 蓝色 (低温), 黄色 (降雨), 红色 (双重) |
| 2 | 12:00 - 15:00 | 3, 4, 5 | 蓝色 (低温), 黄色 (降雨), 红色 (双重) |
| 3 | 17:00 - 19:00 | 6, 7, 8 | 蓝色 (低温), 黄色 (降雨), 红色 (双重) |
预警逻辑:
- 低温预警: 当预报温度低于
TEMP_LOW_THRESHOLD(默认为5°C) 时触发。 - 降雨预警: 当预报降水概率 (
pop) 高于RAIN_PROB_THRESHOLD(默认为50%) 时触发。 - 在当前时段内,如果触发预警,对应的3颗LED将亮起相应的颜色。
8.2 语音播报
用户按下 GPIO 34 连接的按键后,系统将根据当前时间点的天气预警级别,播放对应的语音文件(0001.mp3 - 0003.mp3)。如果当前无预警,则播放无预警提示(0004.mp3)。
8.3 OLED显示
OLED显示屏用于展示核心信息:
- 顶部: 实时时间 (NTP同步)。
- 左侧: 当前天气(温度、降水概率、天气描述)。
- 右侧: 老黄历信息(今日宜、忌)。
- 底部: 当前是否处于预警时段的提示。
8.4 参数可设置性
在源代码中,可以轻松修改以下参数以自定义预警逻辑:
1 | // 预警阈值设置 |
9. 总结
本系统结合了物联网、嵌入式编程和多媒体交互技术,实现了对异常天气的智能监控和多感官预警。通过模块化的设计,可以根据需求进一步扩展功能,例如增加贪睡按键、通过Web界面设置闹钟和预警阈值等。
工程解析
1. 工程目标与功能概览
这是一个基于 ESP32 的嵌入式天气预警终端,核心思路是:
- 通过 Wi-Fi 联网,周期性拉取天气预报与老黄历信息
- 通过 NTP 同步当前时间,用于显示与预警时段判断
- 通过 OLED 实时展示时间、当前小时天气、老黄历(宜/忌)
- 在特定时段(早/中/晚)根据“低温/降雨概率”点亮 WS2812B 指示灯
- 通过 DFPlayer Mini 播放对应的语音提示(按键触发播报)
2. 工程结构(架构视图)
当前工程只有一个 Arduino 草图文件,整体架构可以按“分层/模块”理解:
- 配置层:Wi-Fi/接口 Key/阈值/引脚定义
- 驱动层:OLED(U8g2)、NeoPixel、DFPlayer、网络(WiFi/HTTPClient)、时间(NTPClient)
- 数据层:WeatherData / AlmanacData 两个结构体缓存 API 数据
- 业务层:
- 定时拉取数据(3 小时一次)
- 预警判定(温度与降水概率阈值)
- 时段映射(早/中/晚三个监控窗口)
- 表现层:OLED UI 渲染、LED 灯光显示、语音播报
数据流示意:
- NTPClient → 当前时间 → OLED 显示 & 时段判断
- HTTPClient → JSON → 解析到结构体 → 预警逻辑 → LED/语音/OLED
3. 依赖库与职责(“用什么库做什么”)
在 weather_alert_system.ino 的开头引入了这些库:
WiFi.h:连接 Wi-Fi、获取连接状态与 IPHTTPClient.h:向 HTTP/HTTPS API 发起 GET 请求ArduinoJson.h:解析 JSON 响应并提取字段U8g2lib.h:驱动 SSD1306 OLED,负责绘制文本/UIDFRobotDFPlayerMini.h:控制 DFPlayer Mini 播放语音文件Adafruit_NeoPixel.h:驱动 WS2812B RGB LED 灯NTPClient.h+WiFiUdp.h:通过 UDP 从 NTP 服务器同步时间
4. 配置区解析(你需要改哪些)
4.1 Wi-Fi 参数
在文件顶部定义:
ssid、password:Wi-Fi 名称与密码
建议:不要把真实 Wi-Fi 密码提交到仓库或公开分享工程。
4.2 和风天气 API(QWeather)
QWEATHER_KEY:和风天气 KeyQWEATHER_LOCATION:城市 Location ID(需要替换为真实值)QWEATHER_URL:逐小时预报接口地址前缀
代码里会拼出完整 URL:
https://devapi.qweather.com/v7/weather/24h?location=<LOCATION>&key=<KEY>&hours=24
注意:/v7/weather/24h 是否支持 hours=24 取决于实际 API 规范;如果接口不支持该参数,可能会被忽略或导致错误(以实际返回为准)。
4.3 老黄历 API(聚合数据)
ALMANAC_KEY:聚合数据 KeyALMANAC_URL:接口前缀
完整 URL 类似:
http://v.juhe.cn/laohuangli/d?key=<KEY>&date=YYYY-MM-DD
4.4 硬件引脚与阈值
- OLED:
GPIO21(SDA) / GPIO22(SCL) - DFPlayer:
GPIO16/17使用硬件串口 2 - NeoPixel:
GPIO13,灯珠数量9 - Button:
GPIO34(ESP32 输入专用脚之一) - 阈值:
TEMP_LOW_THRESHOLD = 5:低温阈值(°C)RAIN_PROB_THRESHOLD = 50:降雨概率阈值(%)
5. 关键数据结构(数据怎么存)
5.1 WeatherData
用于缓存未来 24 小时逐小时预报,包含:
fxTime[24]:每小时的时间字符串(代码截取为HH:MM)temp[24]:温度(int)pop[24]:降水概率(int)text[24]:天气描述(String)isValid:数据是否有效(成功拉取并解析)
作用:把“网络请求”与“业务计算/UI 显示”解耦,避免每次显示都请求网络。
5.2 AlmanacData
yi/ji:当天宜/忌isValid:是否有效
6. Setup 初始化流程(上电后做什么)
入口函数 setup() 的逻辑顺序非常关键,整体是“先把外设点亮 → 再联网 → 再取数据”:
- 串口调试初始化:
Serial.begin(115200) - OLED 初始化:
u8g2.begin()并显示 “System Init…” - 按键初始化:
pinMode(BUTTON_PIN, INPUT_PULLUP) - NeoPixel 初始化:
pixels.begin()、亮度、清屏 - 连接 Wi-Fi:调用
connectWiFi() - 初始化 DFPlayer:调用
initDFPlayer() - 初始化 NTP:
timeClient.begin()、timeClient.update() - 首次拉取数据:
getWeatherData()getAlmanacData()
7. Loop 主循环(系统如何持续运行)
loop() 是典型的 Arduino “轮询式调度”:
- 更新时间:
timeClient.update(),读取newHour/newMinute - 定时拉数据:每
DATA_FETCH_INTERVAL(3 小时)触发一次 - 预警检查:调用
checkWarning()决定 LED 状态 - 刷新 OLED:调用
updateOLED() - 按键触发语音:按下按键后调用
playWarningVoice("0001")(当前写法固定传 “0001”,但函数内部会再计算预警级别并选择播放文件) delay(1000):主循环每秒跑一次
7.1 一个重要逻辑细节(当前代码会导致“每秒检查预警”)
loop() 里先把 currentMinute = newMinute,后面又用:
if (newMinute != currentMinute || (currentHour == newHour && currentMinute == newMinute))
由于前面已经赋值,newMinute != currentMinute 永远为假,而第二个条件基本永远为真,因此 checkWarning() 会在每次循环(每秒)被调用。
这不会“功能错误”,但会导致串口输出更频繁、LED 更频繁刷新,整体更耗电/更“吵”。
8. 各函数代码片段解析(做什么、输入输出、关键点)
8.1 connectWiFi()
职责:
- 发起 Wi-Fi 连接并等待最多约 15 秒(30 次 * 500ms)
- OLED 显示连接状态
- 串口打印连接结果
关键点:
- 连接失败后只是提示,不会进入重试策略/休眠策略
8.2 initDFPlayer()
职责:
- 在硬件串口 2 上以 9600 初始化 DFPlayer
- 设置音量并播放一个“测试音/欢迎语”
关键点:
- 若初始化失败,仅打印错误,不会重试
8.3 getWeatherData()
职责:
- 拼接 QWeather API URL 并 GET
- 使用 ArduinoJson 解析
hourly[] - 将 24 条数据写入
hourlyWeather并置isValid
关键点:
- JSON 解析使用
DynamicJsonDocument capacity=10240,属于“经验估算”,若返回内容更大可能解析失败 fxTime只保留T和+之间的HH:MM,方便显示/匹配小时
8.4 getAlmanacData()
职责:
- 通过 NTP 时间生成
YYYY-MM-DD - 请求老黄历接口并解析
result.yi/result.ji
关键点:
- 使用
gmtime()生成日期:由于 NTPClient 已设置时区偏移,得到的 epochTime 已经是本地时间偏移后的值,gmtime()输出的“UTC 结构体”恰好对应本地时间的日期,这种写法在很多项目中可用,但要注意时区/夏令时需求时可能需要更严谨的处理
8.5 getTimeSlot(hour)
职责:
- 将小时映射为 3 个“重点提醒时段”
返回值:
1:06-092:12-153:17-190:其他时间
8.6 checkWarning()
职责:
- 根据当前小时在
hourlyWeather里找到对应的那条逐小时数据 - 判断低温、降雨,组合出
warningLevel - 调用
updateLEDs(currentSlot, warningLevel)
关键点:
warningLevel使用位运算组合:- 低温:
warningLevel |= 1 - 降雨:
warningLevel |= 2 - 双重:最终值为
3
- 低温:
- 若找不到对应小时的数据,会打印提示,但不会回退到“最近小时”或“默认策略”
8.7 updateLEDs(time_slot, warning_level)
职责:
- 将 9 颗灯分成 3 组,每组 3 颗对应一个时段
- 当前时段点亮对应颜色,其余灯清空
颜色规则:
- 低温:蓝
- 降雨:黄
- 双重:红
- 无预警:灭
关键点:
- 只显示“当前时段”的状态,而不是同时显示三个时段各自的预警(这是当前设计选择)
8.8 updateOLED()
职责:
- 顶部:当前时间(HH:MM 居中)
- 左侧:当前小时的天气(温度、降水概率、天气描述)
- 右侧:老黄历(宜/忌,截断为较短字符串)
- 底部:当前是否处在监控时段(Slot Active / Normal Time)
关键点:
- 截断宜/忌时使用
indexOf(' ')找第一个空格。如果返回 -1(没有空格),substring(0, -1)在 Arduino String 上的行为可能导致显示异常,建议后续改为更稳健的截断逻辑
8.9 playWarningVoice(message)
职责:
- 再次计算当前小时的
warningLevel - 根据级别选择播放的文件编号:
- 1:低温
- 2:降雨
- 3:双重
- 4:无预警
- 调用
myDFPlayer.play(file_number)
关键点:
- 函数入参
message目前没有参与选择逻辑(调用处传 “0001”,但内部最终播放的并不一定是 1) if (myDFPlayer.available())的用途通常是“是否有来自 DFPlayer 的消息/状态可读”,并不等价于“忙/不可用”,这段判断可能与作者意图不一致(以实际运行表现为准)
8.10 checkAlarm()(闹钟框架)
职责:
- 当到达
ALARM_HOUR:ALARM_MINUTE时播放文件 5 alarmTriggeredToday避免当天重复触发- 在 00:01 重置状态
当前状态:
- 该函数没有被
loop()调用,属于预留功能框架
9. 工程“可扩展点”(后续怎么演进)
- 把“每秒刷新 OLED”改为“每 N 秒刷新”或“分钟级刷新”,降低功耗并减少 I2C 负载
- 把三个时段的预警分别显示为三组 LED(而不是只点亮当前时段)
- 增加“Wi-Fi 断线重连策略”和“请求失败退避重试”
- 把 Key/密码移出代码(例如编译时配置或本地私有文件),避免泄露